github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/derive/frame.go (about) 1 package derive 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "fmt" 8 "io" 9 ) 10 11 // Frames cannot be larger than 1 MB. 12 // Data transactions that carry frames are generally not larger than 128 KB due to L1 network conditions, 13 // but we leave space to grow larger anyway (gas limit allows for more data). 14 const MaxFrameLen = 1_000_000 15 16 // Data Format 17 // 18 // frame = channel_id ++ frame_number ++ frame_data_length ++ frame_data ++ is_last 19 // 20 // channel_id = bytes16 21 // frame_number = uint16 22 // frame_data_length = uint32 23 // frame_data = bytes 24 // is_last = bool 25 26 type Frame struct { 27 ID ChannelID `json:"id"` 28 FrameNumber uint16 `json:"frame_number"` 29 Data []byte `json:"data"` 30 IsLast bool `json:"is_last"` 31 } 32 33 // MarshalBinary writes the frame to `w`. 34 // It returns any errors encountered while writing, but 35 // generally expects the writer very rarely fail. 36 func (f *Frame) MarshalBinary(w io.Writer) error { 37 _, err := w.Write(f.ID[:]) 38 if err != nil { 39 return err 40 } 41 if err := binary.Write(w, binary.BigEndian, f.FrameNumber); err != nil { 42 return err 43 } 44 if err := binary.Write(w, binary.BigEndian, uint32(len(f.Data))); err != nil { 45 return err 46 } 47 _, err = w.Write(f.Data) 48 if err != nil { 49 return err 50 } 51 if f.IsLast { 52 if _, err = w.Write([]byte{1}); err != nil { 53 return err 54 } 55 } else { 56 if _, err = w.Write([]byte{0}); err != nil { 57 return err 58 } 59 } 60 return nil 61 } 62 63 type ByteReader interface { 64 io.Reader 65 io.ByteReader 66 } 67 68 // UnmarshalBinary consumes a full frame from the reader. 69 // If `r` fails a read, it returns the error from the reader 70 // The reader will be left in a partially read state. 71 // 72 // If r doesn't return any bytes, returns io.EOF. 73 // If r unexpectedly stops returning data half-way, returns io.ErrUnexpectedEOF. 74 func (f *Frame) UnmarshalBinary(r ByteReader) error { 75 if _, err := io.ReadFull(r, f.ID[:]); err != nil { 76 // Forward io.EOF here ok, would mean not a single byte from r. 77 return fmt.Errorf("reading channel_id: %w", err) 78 } 79 if err := binary.Read(r, binary.BigEndian, &f.FrameNumber); err != nil { 80 return fmt.Errorf("reading frame_number: %w", eofAsUnexpectedMissing(err)) 81 } 82 83 var frameLength uint32 84 if err := binary.Read(r, binary.BigEndian, &frameLength); err != nil { 85 return fmt.Errorf("reading frame_data_length: %w", eofAsUnexpectedMissing(err)) 86 } 87 88 // Cap frame length to MaxFrameLen (currently 1MB) 89 if frameLength > MaxFrameLen { 90 return fmt.Errorf("frame_data_length is too large: %d", frameLength) 91 } 92 f.Data = make([]byte, int(frameLength)) 93 if _, err := io.ReadFull(r, f.Data); err != nil { 94 return fmt.Errorf("reading frame_data: %w", eofAsUnexpectedMissing(err)) 95 } 96 97 if isLastByte, err := r.ReadByte(); err != nil { 98 return fmt.Errorf("reading final byte (is_last): %w", eofAsUnexpectedMissing(err)) 99 } else if isLastByte == 0 { 100 f.IsLast = false 101 } else if isLastByte == 1 { 102 f.IsLast = true 103 } else { 104 return errors.New("invalid byte as is_last") 105 } 106 return nil 107 } 108 109 // eofAsUnexpectedMissing converts an io.EOF in the error chain of err into an 110 // io.ErrUnexpectedEOF. It should be used to convert intermediate io.EOF errors 111 // in unmarshalling code to achieve idiomatic error behavior. 112 // Other errors are passed through unchanged. 113 func eofAsUnexpectedMissing(err error) error { 114 if errors.Is(err, io.EOF) { 115 return fmt.Errorf("fully missing: %w", io.ErrUnexpectedEOF) 116 } 117 return err 118 } 119 120 // Frames are stored in L1 transactions with the following format: 121 // data = DerivationVersion0 ++ Frame(s) 122 // Where there is one or more frames concatenated together. 123 124 // ParseFrames parse the on chain serialization of frame(s) in 125 // an L1 transaction. Currently only version 0 of the serialization 126 // format is supported. 127 // All frames must be parsed without error and there must not be 128 // any left over data and there must be at least one frame. 129 func ParseFrames(data []byte) ([]Frame, error) { 130 if len(data) == 0 { 131 return nil, errors.New("data array must not be empty") 132 } 133 if data[0] != DerivationVersion0 { 134 return nil, fmt.Errorf("invalid derivation format byte: got %d", data[0]) 135 } 136 buf := bytes.NewBuffer(data[1:]) 137 var frames []Frame 138 for buf.Len() > 0 { 139 var f Frame 140 if err := f.UnmarshalBinary(buf); err != nil { 141 return nil, fmt.Errorf("parsing frame %d: %w", len(frames), err) 142 } 143 frames = append(frames, f) 144 } 145 if buf.Len() != 0 { 146 return nil, fmt.Errorf("did not fully consume data: have %d frames and %d bytes left", len(frames), buf.Len()) 147 } 148 if len(frames) == 0 { 149 return nil, errors.New("was not able to find any frames") 150 } 151 return frames, nil 152 }