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 }