github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/derive/span_channel_out.go (about)

     1  package derive
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"fmt"
     7  	"io"
     8  
     9  	"github.com/ethereum/go-ethereum/core/types"
    10  	"github.com/ethereum/go-ethereum/rlp"
    11  
    12  	"github.com/ethereum-optimism/optimism/op-node/rollup"
    13  )
    14  
    15  type SpanChannelOut struct {
    16  	id ChannelID
    17  	// Frame ID of the next frame to emit. Increment after emitting
    18  	frame uint64
    19  	// rlpLength is the uncompressed size of the channel. Must be less than MAX_RLP_BYTES_PER_CHANNEL
    20  	rlpLength int
    21  
    22  	// Compressor stage. Write input data to it
    23  	compress Compressor
    24  	// closed indicates if the channel is closed
    25  	closed bool
    26  	// spanBatchBuilder contains information requires to build SpanBatch
    27  	spanBatchBuilder *SpanBatchBuilder
    28  	// reader contains compressed data for making output frames
    29  	reader *bytes.Buffer
    30  }
    31  
    32  func (co *SpanChannelOut) ID() ChannelID {
    33  	return co.id
    34  }
    35  
    36  func NewSpanChannelOut(compress Compressor, spanBatchBuilder *SpanBatchBuilder) (*SpanChannelOut, error) {
    37  	c := &SpanChannelOut{
    38  		id:               ChannelID{},
    39  		frame:            0,
    40  		rlpLength:        0,
    41  		compress:         compress,
    42  		spanBatchBuilder: spanBatchBuilder,
    43  		reader:           &bytes.Buffer{},
    44  	}
    45  	_, err := rand.Read(c.id[:])
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	return c, nil
    51  }
    52  
    53  func (co *SpanChannelOut) Reset() error {
    54  	co.frame = 0
    55  	co.rlpLength = 0
    56  	co.compress.Reset()
    57  	co.reader.Reset()
    58  	co.closed = false
    59  	co.spanBatchBuilder.Reset()
    60  	_, err := rand.Read(co.id[:])
    61  	return err
    62  }
    63  
    64  // AddBlock adds a block to the channel. It returns the RLP encoded byte size
    65  // and an error if there is a problem adding the block. The only sentinel error
    66  // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel
    67  // should be closed and a new one should be made.
    68  func (co *SpanChannelOut) AddBlock(rollupCfg *rollup.Config, block *types.Block) (uint64, error) {
    69  	if co.closed {
    70  		return 0, ErrChannelOutAlreadyClosed
    71  	}
    72  
    73  	batch, l1Info, err := BlockToSingularBatch(rollupCfg, block)
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  	return co.AddSingularBatch(batch, l1Info.SequenceNumber)
    78  }
    79  
    80  // AddSingularBatch adds a batch to the channel. It returns the RLP encoded byte size
    81  // and an error if there is a problem adding the batch. The only sentinel error
    82  // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel
    83  // should be closed and a new one should be made.
    84  //
    85  // AddSingularBatch should be used together with BlockToSingularBatch if you need to access the
    86  // BatchData before adding a block to the channel. It isn't possible to access
    87  // the batch data with AddBlock.
    88  //
    89  // SingularBatch is appended to the channel's SpanBatch.
    90  // A channel can have only one SpanBatch. And compressed results should not be accessible until the channel is closed, since the prefix and payload can be changed.
    91  // So it resets channel contents and rewrites the entire SpanBatch each time, and compressed results are copied to reader after the channel is closed.
    92  // It makes we can only get frames once the channel is full or closed, in the case of SpanBatch.
    93  func (co *SpanChannelOut) AddSingularBatch(batch *SingularBatch, seqNum uint64) (uint64, error) {
    94  	if co.closed {
    95  		return 0, ErrChannelOutAlreadyClosed
    96  	}
    97  	if co.FullErr() != nil {
    98  		// channel is already full
    99  		return 0, co.FullErr()
   100  	}
   101  	var buf bytes.Buffer
   102  	// Append Singular batch to its span batch builder
   103  	co.spanBatchBuilder.AppendSingularBatch(batch, seqNum)
   104  	// Convert Span batch to RawSpanBatch
   105  	rawSpanBatch, err := co.spanBatchBuilder.GetRawSpanBatch()
   106  	if err != nil {
   107  		return 0, fmt.Errorf("failed to convert SpanBatch into RawSpanBatch: %w", err)
   108  	}
   109  	// Encode RawSpanBatch into bytes
   110  	if err = rlp.Encode(&buf, NewBatchData(rawSpanBatch)); err != nil {
   111  		return 0, fmt.Errorf("failed to encode RawSpanBatch into bytes: %w", err)
   112  	}
   113  	// Ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL
   114  	if buf.Len() > MaxRLPBytesPerChannel {
   115  		return 0, fmt.Errorf("could not take %d bytes as replacement of channel of %d bytes, max is %d. err: %w",
   116  			buf.Len(), co.rlpLength, MaxRLPBytesPerChannel, ErrTooManyRLPBytes)
   117  	}
   118  	co.rlpLength = buf.Len()
   119  
   120  	if co.spanBatchBuilder.GetBlockCount() > 1 {
   121  		// Flush compressed data into reader to preserve current result.
   122  		// If the channel is full after this block is appended, we should use preserved data.
   123  		if err := co.compress.Flush(); err != nil {
   124  			return 0, fmt.Errorf("failed to flush compressor: %w", err)
   125  		}
   126  		_, err = io.Copy(co.reader, co.compress)
   127  		if err != nil {
   128  			// Must reset reader to avoid partial output
   129  			co.reader.Reset()
   130  			return 0, fmt.Errorf("failed to copy compressed data to reader: %w", err)
   131  		}
   132  	}
   133  
   134  	// Reset compressor to rewrite the entire span batch
   135  	co.compress.Reset()
   136  	// Avoid using io.Copy here, because we need all or nothing
   137  	written, err := co.compress.Write(buf.Bytes())
   138  	if co.compress.FullErr() != nil {
   139  		err = co.compress.FullErr()
   140  		if co.spanBatchBuilder.GetBlockCount() == 1 {
   141  			// Do not return CompressorFullErr for the first block in the batch
   142  			// In this case, reader must be empty. then the contents of compressor will be copied to reader when the channel is closed.
   143  			err = nil
   144  		}
   145  		// If there are more than one blocks in the channel, reader should have data that preserves previous compression result before adding this block.
   146  		// So, as a result, this block is not added to the channel and the channel will be closed.
   147  		return uint64(written), err
   148  	}
   149  
   150  	// If compressor is not full yet, reader must be reset to avoid submitting invalid frames
   151  	co.reader.Reset()
   152  	return uint64(written), err
   153  }
   154  
   155  // InputBytes returns the total amount of RLP-encoded input bytes.
   156  func (co *SpanChannelOut) InputBytes() int {
   157  	return co.rlpLength
   158  }
   159  
   160  // ReadyBytes returns the number of bytes that the channel out can immediately output into a frame.
   161  // Use `Flush` or `Close` to move data from the compression buffer into the ready buffer if more bytes
   162  // are needed. Add blocks may add to the ready buffer, but it is not guaranteed due to the compression stage.
   163  func (co *SpanChannelOut) ReadyBytes() int {
   164  	return co.reader.Len()
   165  }
   166  
   167  // Flush flushes the internal compression stage to the ready buffer. It enables pulling a larger & more
   168  // complete frame. It reduces the compression efficiency.
   169  func (co *SpanChannelOut) Flush() error {
   170  	if err := co.compress.Flush(); err != nil {
   171  		return err
   172  	}
   173  	if co.closed && co.ReadyBytes() == 0 && co.compress.Len() > 0 {
   174  		_, err := io.Copy(co.reader, co.compress)
   175  		if err != nil {
   176  			// Must reset reader to avoid partial output
   177  			co.reader.Reset()
   178  			return fmt.Errorf("failed to flush compressed data to reader: %w", err)
   179  		}
   180  	}
   181  	return nil
   182  }
   183  
   184  func (co *SpanChannelOut) FullErr() error {
   185  	return co.compress.FullErr()
   186  }
   187  
   188  func (co *SpanChannelOut) Close() error {
   189  	if co.closed {
   190  		return ErrChannelOutAlreadyClosed
   191  	}
   192  	co.closed = true
   193  	if err := co.Flush(); err != nil {
   194  		return err
   195  	}
   196  	return co.compress.Close()
   197  }
   198  
   199  // OutputFrame writes a frame to w with a given max size and returns the frame
   200  // number.
   201  // Use `ReadyBytes`, `Flush`, and `Close` to modify the ready buffer.
   202  // Returns an error if the `maxSize` < FrameV0OverHeadSize.
   203  // Returns io.EOF when the channel is closed & there are no more frames.
   204  // Returns nil if there is still more buffered data.
   205  // Returns an error if it ran into an error during processing.
   206  func (co *SpanChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error) {
   207  	// Check that the maxSize is large enough for the frame overhead size.
   208  	if maxSize < FrameV0OverHeadSize {
   209  		return 0, ErrMaxFrameSizeTooSmall
   210  	}
   211  
   212  	f := createEmptyFrame(co.id, co.frame, co.ReadyBytes(), co.closed, maxSize)
   213  
   214  	if _, err := io.ReadFull(co.reader, f.Data); err != nil {
   215  		return 0, err
   216  	}
   217  
   218  	if err := f.MarshalBinary(w); err != nil {
   219  		return 0, err
   220  	}
   221  
   222  	co.frame += 1
   223  	fn := f.FrameNumber
   224  	if f.IsLast {
   225  		return fn, io.EOF
   226  	} else {
   227  		return fn, nil
   228  	}
   229  }