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 }