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

     1  package derive
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/rand"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/ethereum-optimism/optimism/op-node/rollup"
    11  	"github.com/ethereum/go-ethereum/common/hexutil"
    12  	"github.com/ethereum/go-ethereum/core/types"
    13  	"github.com/ethereum/go-ethereum/rlp"
    14  )
    15  
    16  var ErrMaxFrameSizeTooSmall = errors.New("maxSize is too small to fit the fixed frame overhead")
    17  var ErrNotDepositTx = errors.New("first transaction in block is not a deposit tx")
    18  var ErrTooManyRLPBytes = errors.New("batch would cause RLP bytes to go over limit")
    19  var ErrChannelOutAlreadyClosed = errors.New("channel-out already closed")
    20  
    21  // FrameV0OverHeadSize is the absolute minimum size of a frame.
    22  // This is the fixed overhead frame size, calculated as specified
    23  // in the [Frame Format] specs: 16 + 2 + 4 + 1 = 23 bytes.
    24  //
    25  // [Frame Format]: https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/derivation.md#frame-format
    26  const FrameV0OverHeadSize = 23
    27  
    28  var CompressorFullErr = errors.New("compressor is full")
    29  
    30  type Compressor interface {
    31  	// Writer is used to write uncompressed data which will be compressed. Should return
    32  	// CompressorFullErr if the compressor is full and no more data should be written.
    33  	io.Writer
    34  	// Closer Close function should be called before reading any data.
    35  	io.Closer
    36  	// Reader is used to Read compressed data; should only be called after Close.
    37  	io.Reader
    38  	// Reset will reset all written data
    39  	Reset()
    40  	// Len returns an estimate of the current length of the compressed data; calling Flush will
    41  	// increase the accuracy at the expense of a poorer compression ratio.
    42  	Len() int
    43  	// Flush flushes any uncompressed data to the compression buffer. This will result in a
    44  	// non-optimal compression ratio.
    45  	Flush() error
    46  	// FullErr returns CompressorFullErr if the compressor is known to be full. Note that
    47  	// calls to Write will fail if an error is returned from this method, but calls to Write
    48  	// can still return CompressorFullErr even if this does not.
    49  	FullErr() error
    50  }
    51  
    52  type ChannelOut interface {
    53  	ID() ChannelID
    54  	Reset() error
    55  	AddBlock(*rollup.Config, *types.Block) (uint64, error)
    56  	AddSingularBatch(*SingularBatch, uint64) (uint64, error)
    57  	InputBytes() int
    58  	ReadyBytes() int
    59  	Flush() error
    60  	FullErr() error
    61  	Close() error
    62  	OutputFrame(*bytes.Buffer, uint64) (uint16, error)
    63  }
    64  
    65  func NewChannelOut(batchType uint, compress Compressor, spanBatchBuilder *SpanBatchBuilder) (ChannelOut, error) {
    66  	switch batchType {
    67  	case SingularBatchType:
    68  		return NewSingularChannelOut(compress)
    69  	case SpanBatchType:
    70  		return NewSpanChannelOut(compress, spanBatchBuilder)
    71  	default:
    72  		return nil, fmt.Errorf("unrecognized batch type: %d", batchType)
    73  	}
    74  }
    75  
    76  type SingularChannelOut struct {
    77  	id ChannelID
    78  	// Frame ID of the next frame to emit. Increment after emitting
    79  	frame uint64
    80  	// rlpLength is the uncompressed size of the channel. Must be less than MAX_RLP_BYTES_PER_CHANNEL
    81  	rlpLength int
    82  
    83  	// Compressor stage. Write input data to it
    84  	compress Compressor
    85  
    86  	closed bool
    87  }
    88  
    89  func (co *SingularChannelOut) ID() ChannelID {
    90  	return co.id
    91  }
    92  
    93  func NewSingularChannelOut(compress Compressor) (*SingularChannelOut, error) {
    94  	c := &SingularChannelOut{
    95  		id:        ChannelID{},
    96  		frame:     0,
    97  		rlpLength: 0,
    98  		compress:  compress,
    99  	}
   100  	_, err := rand.Read(c.id[:])
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	return c, nil
   106  }
   107  
   108  func (co *SingularChannelOut) Reset() error {
   109  	co.frame = 0
   110  	co.rlpLength = 0
   111  	co.compress.Reset()
   112  	co.closed = false
   113  	_, err := rand.Read(co.id[:])
   114  	return err
   115  }
   116  
   117  // AddBlock adds a block to the channel. It returns the RLP encoded byte size
   118  // and an error if there is a problem adding the block. The only sentinel error
   119  // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel
   120  // should be closed and a new one should be made.
   121  func (co *SingularChannelOut) AddBlock(rollupCfg *rollup.Config, block *types.Block) (uint64, error) {
   122  	if co.closed {
   123  		return 0, ErrChannelOutAlreadyClosed
   124  	}
   125  
   126  	batch, l1Info, err := BlockToSingularBatch(rollupCfg, block)
   127  	if err != nil {
   128  		return 0, err
   129  	}
   130  	return co.AddSingularBatch(batch, l1Info.SequenceNumber)
   131  }
   132  
   133  // AddSingularBatch adds a batch to the channel. It returns the RLP encoded byte size
   134  // and an error if there is a problem adding the batch. The only sentinel error
   135  // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel
   136  // should be closed and a new one should be made.
   137  //
   138  // AddSingularBatch should be used together with BlockToBatch if you need to access the
   139  // BatchData before adding a block to the channel. It isn't possible to access
   140  // the batch data with AddBlock.
   141  func (co *SingularChannelOut) AddSingularBatch(batch *SingularBatch, _ uint64) (uint64, error) {
   142  	if co.closed {
   143  		return 0, ErrChannelOutAlreadyClosed
   144  	}
   145  
   146  	// We encode to a temporary buffer to determine the encoded length to
   147  	// ensure that the total size of all RLP elements is less than or equal to MAX_RLP_BYTES_PER_CHANNEL
   148  	var buf bytes.Buffer
   149  	if err := rlp.Encode(&buf, NewBatchData(batch)); err != nil {
   150  		return 0, err
   151  	}
   152  	if co.rlpLength+buf.Len() > MaxRLPBytesPerChannel {
   153  		return 0, fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w",
   154  			buf.Len(), co.rlpLength, MaxRLPBytesPerChannel, ErrTooManyRLPBytes)
   155  	}
   156  	co.rlpLength += buf.Len()
   157  
   158  	// avoid using io.Copy here, because we need all or nothing
   159  	written, err := co.compress.Write(buf.Bytes())
   160  	return uint64(written), err
   161  }
   162  
   163  // InputBytes returns the total amount of RLP-encoded input bytes.
   164  func (co *SingularChannelOut) InputBytes() int {
   165  	return co.rlpLength
   166  }
   167  
   168  // ReadyBytes returns the number of bytes that the channel out can immediately output into a frame.
   169  // Use `Flush` or `Close` to move data from the compression buffer into the ready buffer if more bytes
   170  // are needed. Add blocks may add to the ready buffer, but it is not guaranteed due to the compression stage.
   171  func (co *SingularChannelOut) ReadyBytes() int {
   172  	return co.compress.Len()
   173  }
   174  
   175  // Flush flushes the internal compression stage to the ready buffer. It enables pulling a larger & more
   176  // complete frame. It reduces the compression efficiency.
   177  func (co *SingularChannelOut) Flush() error {
   178  	return co.compress.Flush()
   179  }
   180  
   181  func (co *SingularChannelOut) FullErr() error {
   182  	return co.compress.FullErr()
   183  }
   184  
   185  func (co *SingularChannelOut) Close() error {
   186  	if co.closed {
   187  		return ErrChannelOutAlreadyClosed
   188  	}
   189  	co.closed = true
   190  	return co.compress.Close()
   191  }
   192  
   193  // OutputFrame writes a frame to w with a given max size and returns the frame
   194  // number.
   195  // Use `ReadyBytes`, `Flush`, and `Close` to modify the ready buffer.
   196  // Returns an error if the `maxSize` < FrameV0OverHeadSize.
   197  // Returns io.EOF when the channel is closed & there are no more frames.
   198  // Returns nil if there is still more buffered data.
   199  // Returns an error if it ran into an error during processing.
   200  func (co *SingularChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint16, error) {
   201  	// Check that the maxSize is large enough for the frame overhead size.
   202  	if maxSize < FrameV0OverHeadSize {
   203  		return 0, ErrMaxFrameSizeTooSmall
   204  	}
   205  
   206  	f := createEmptyFrame(co.id, co.frame, co.ReadyBytes(), co.closed, maxSize)
   207  
   208  	if _, err := io.ReadFull(co.compress, f.Data); err != nil {
   209  		return 0, err
   210  	}
   211  
   212  	if err := f.MarshalBinary(w); err != nil {
   213  		return 0, err
   214  	}
   215  
   216  	co.frame += 1
   217  	fn := f.FrameNumber
   218  	if f.IsLast {
   219  		return fn, io.EOF
   220  	} else {
   221  		return fn, nil
   222  	}
   223  }
   224  
   225  // BlockToSingularBatch transforms a block into a batch object that can easily be RLP encoded.
   226  func BlockToSingularBatch(rollupCfg *rollup.Config, block *types.Block) (*SingularBatch, *L1BlockInfo, error) {
   227  	if len(block.Transactions()) == 0 {
   228  		return nil, nil, fmt.Errorf("block %v has no transactions", block.Hash())
   229  	}
   230  
   231  	opaqueTxs := make([]hexutil.Bytes, 0, len(block.Transactions()))
   232  	for i, tx := range block.Transactions() {
   233  		if tx.Type() == types.DepositTxType {
   234  			continue
   235  		}
   236  		otx, err := tx.MarshalBinary()
   237  		if err != nil {
   238  			return nil, nil, fmt.Errorf("could not encode tx %v in block %v: %w", i, tx.Hash(), err)
   239  		}
   240  		opaqueTxs = append(opaqueTxs, otx)
   241  	}
   242  
   243  	l1InfoTx := block.Transactions()[0]
   244  	if l1InfoTx.Type() != types.DepositTxType {
   245  		return nil, nil, ErrNotDepositTx
   246  	}
   247  	l1Info, err := L1BlockInfoFromBytes(rollupCfg, block.Time(), l1InfoTx.Data())
   248  	if err != nil {
   249  		return nil, l1Info, fmt.Errorf("could not parse the L1 Info deposit: %w", err)
   250  	}
   251  
   252  	return &SingularBatch{
   253  		ParentHash:   block.ParentHash(),
   254  		EpochNum:     rollup.Epoch(l1Info.Number),
   255  		EpochHash:    l1Info.BlockHash,
   256  		Timestamp:    block.Time(),
   257  		Transactions: opaqueTxs,
   258  	}, l1Info, nil
   259  }
   260  
   261  // ForceCloseTxData generates the transaction data for a transaction which will force close
   262  // a channel. It should be given every frame of that channel which has been submitted on
   263  // chain. The frames should be given in order that they appear on L1.
   264  func ForceCloseTxData(frames []Frame) ([]byte, error) {
   265  	if len(frames) == 0 {
   266  		return nil, errors.New("must provide at least one frame")
   267  	}
   268  	frameNumbers := make(map[uint16]struct{})
   269  	id := frames[0].ID
   270  	closeNumber := uint16(0)
   271  	closed := false
   272  	for i, frame := range frames {
   273  		if !closed && frame.IsLast {
   274  			closeNumber = frame.FrameNumber
   275  		}
   276  		closed = closed || frame.IsLast
   277  		frameNumbers[frame.FrameNumber] = struct{}{}
   278  		if frame.ID != id {
   279  			return nil, fmt.Errorf("invalid ID in list: first ID: %v, %vth ID: %v", id, i, frame.ID)
   280  		}
   281  	}
   282  
   283  	var out bytes.Buffer
   284  	out.WriteByte(DerivationVersion0)
   285  
   286  	if !closed {
   287  		f := Frame{
   288  			ID:          id,
   289  			FrameNumber: 0,
   290  			Data:        nil,
   291  			IsLast:      true,
   292  		}
   293  		if err := f.MarshalBinary(&out); err != nil {
   294  			return nil, err
   295  		}
   296  	} else {
   297  		for i := uint16(0); i <= closeNumber; i++ {
   298  			if _, ok := frameNumbers[i]; ok {
   299  				continue
   300  			}
   301  			f := Frame{
   302  				ID:          id,
   303  				FrameNumber: i,
   304  				Data:        nil,
   305  				IsLast:      false,
   306  			}
   307  			if err := f.MarshalBinary(&out); err != nil {
   308  				return nil, err
   309  			}
   310  		}
   311  	}
   312  
   313  	return out.Bytes(), nil
   314  }
   315  
   316  // createEmptyFrame creates new empty Frame with given information. Frame data must be copied from ChannelOut.
   317  func createEmptyFrame(id ChannelID, frame uint64, readyBytes int, closed bool, maxSize uint64) *Frame {
   318  	f := Frame{
   319  		ID:          id,
   320  		FrameNumber: uint16(frame),
   321  	}
   322  
   323  	// Copy data from the local buffer into the frame data buffer
   324  	maxDataSize := maxSize - FrameV0OverHeadSize
   325  	if maxDataSize >= uint64(readyBytes) {
   326  		maxDataSize = uint64(readyBytes)
   327  		// If we are closed & will not spill past the current frame
   328  		// mark it as the final frame of the channel.
   329  		if closed {
   330  			f.IsLast = true
   331  		}
   332  	}
   333  	f.Data = make([]byte, maxDataSize)
   334  	return &f
   335  }