github.com/ethereum-optimism/optimism@v1.7.2/op-node/rollup/derive/frame_test.go (about) 1 package derive 2 3 import ( 4 "bytes" 5 "io" 6 "math" 7 "math/rand" 8 "strconv" 9 "testing" 10 "time" 11 12 "github.com/ethereum-optimism/optimism/op-service/testutils" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func FuzzFrameUnmarshalBinary(f *testing.F) { 17 f.Fuzz(func(t *testing.T, data []byte) { 18 buf := bytes.NewBuffer(data) 19 var f Frame 20 _ = (&f).UnmarshalBinary(buf) 21 }) 22 } 23 24 func FuzzParseFrames(f *testing.F) { 25 f.Fuzz(func(t *testing.T, data []byte) { 26 frames, err := ParseFrames(data) 27 if err != nil && len(frames) != 0 { 28 t.Fatal("non-nil error with an amount of return data") 29 } else if err == nil && len(frames) == 0 { 30 t.Fatal("must return data with a non-nil error") 31 } 32 }) 33 } 34 35 func TestFrameMarshaling(t *testing.T) { 36 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 37 for i := 0; i < 16; i++ { 38 t.Run(strconv.Itoa(i), func(t *testing.T) { 39 frame := randomFrame(rng) 40 var data bytes.Buffer 41 require.NoError(t, frame.MarshalBinary(&data)) 42 43 frame0 := new(Frame) 44 require.NoError(t, frame0.UnmarshalBinary(&data)) 45 require.Equal(t, frame, frame0) 46 }) 47 } 48 } 49 50 func TestFrameUnmarshalNoData(t *testing.T) { 51 frame0 := new(Frame) 52 err := frame0.UnmarshalBinary(bytes.NewReader([]byte{})) 53 require.Error(t, err) 54 require.ErrorIs(t, err, io.EOF) 55 } 56 57 func TestFrameUnmarshalTruncated(t *testing.T) { 58 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 59 60 // 16 (channel_id) ++ 2 (frame_number) ++ 4 (frame_data_length) ++ 61 // frame_data_length (frame_data) ++ 1 (is_last) 62 for _, tr := range []struct { 63 desc string 64 truncate func([]byte) []byte 65 genData bool // whether data should be generated 66 }{ 67 { 68 desc: "truncate-channel_id-half", 69 truncate: func(data []byte) []byte { 70 return data[:8] 71 }, 72 }, 73 { 74 desc: "truncate-frame_number-full", 75 truncate: func(data []byte) []byte { 76 return data[:16] 77 }, 78 }, 79 { 80 desc: "truncate-frame_number-half", 81 truncate: func(data []byte) []byte { 82 return data[:17] 83 }, 84 }, 85 { 86 desc: "truncate-frame_data_length-full", 87 truncate: func(data []byte) []byte { 88 return data[:18] 89 }, 90 }, 91 { 92 desc: "truncate-frame_data_length-half", 93 truncate: func(data []byte) []byte { 94 return data[:20] 95 }, 96 genData: true, // for non-zero frame_data_length 97 }, 98 { 99 desc: "truncate-frame_data-full", 100 truncate: func(data []byte) []byte { 101 return data[:22] 102 }, 103 genData: true, // for non-zero frame_data_length 104 }, 105 { 106 desc: "truncate-frame_data-last-byte", 107 truncate: func(data []byte) []byte { 108 return data[:len(data)-2] 109 }, 110 genData: true, 111 }, 112 { 113 desc: "truncate-is_last", 114 truncate: func(data []byte) []byte { 115 return data[:len(data)-1] 116 }, 117 genData: true, 118 }, 119 } { 120 t.Run(tr.desc, func(t *testing.T) { 121 var opts []frameOpt 122 if !tr.genData { 123 opts = []frameOpt{frameWithDataLen(0)} 124 } 125 frame := randomFrame(rng, opts...) 126 var data bytes.Buffer 127 require.NoError(t, frame.MarshalBinary(&data)) 128 129 tdata := tr.truncate(data.Bytes()) 130 131 frame0 := new(Frame) 132 err := frame0.UnmarshalBinary(bytes.NewReader(tdata)) 133 require.Error(t, err) 134 require.ErrorIs(t, err, io.ErrUnexpectedEOF) 135 }) 136 } 137 } 138 139 func TestFrameUnmarshalInvalidIsLast(t *testing.T) { 140 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 141 frame := randomFrame(rng, frameWithDataLen(16)) 142 var data bytes.Buffer 143 require.NoError(t, frame.MarshalBinary(&data)) 144 145 idata := data.Bytes() 146 idata[len(idata)-1] = 2 // invalid is_last 147 148 frame0 := new(Frame) 149 err := frame0.UnmarshalBinary(bytes.NewReader(idata)) 150 require.Error(t, err) 151 require.ErrorContains(t, err, "invalid byte") 152 } 153 154 func TestParseFramesNoData(t *testing.T) { 155 frames, err := ParseFrames(nil) 156 require.Empty(t, frames) 157 require.Error(t, err) 158 } 159 160 func TestParseFramesInvalidVer(t *testing.T) { 161 frames, err := ParseFrames([]byte{42}) 162 require.Empty(t, frames) 163 require.Error(t, err) 164 } 165 166 func TestParseFrames(t *testing.T) { 167 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 168 numFrames := rng.Intn(16) + 1 169 frames := make([]Frame, 0, numFrames) 170 for i := 0; i < numFrames; i++ { 171 frames = append(frames, *randomFrame(rng)) 172 } 173 data, err := txMarshalFrames(frames) 174 require.NoError(t, err) 175 176 frames0, err := ParseFrames(data) 177 require.NoError(t, err) 178 require.Equal(t, frames, frames0) 179 } 180 181 func TestParseFramesTruncated(t *testing.T) { 182 rng := rand.New(rand.NewSource(time.Now().UnixNano())) 183 numFrames := rng.Intn(16) + 1 184 frames := make([]Frame, 0, numFrames) 185 for i := 0; i < numFrames; i++ { 186 frames = append(frames, *randomFrame(rng)) 187 } 188 data, err := txMarshalFrames(frames) 189 require.NoError(t, err) 190 data = data[:len(data)-2] // truncate last 2 bytes 191 192 frames0, err := ParseFrames(data) 193 require.Error(t, err) 194 require.ErrorIs(t, err, io.ErrUnexpectedEOF) 195 require.Empty(t, frames0) 196 } 197 198 // txMarshalFrames creates the tx payload for the given frames, i.e., it first 199 // writes the version byte to a buffer and then appends all binary-marshaled 200 // frames. 201 func txMarshalFrames(frames []Frame) ([]byte, error) { 202 var data bytes.Buffer 203 if err := data.WriteByte(DerivationVersion0); err != nil { 204 return nil, err 205 } 206 for _, frame := range frames { 207 if err := frame.MarshalBinary(&data); err != nil { 208 return nil, err 209 } 210 } 211 return data.Bytes(), nil 212 } 213 214 func randomFrame(rng *rand.Rand, opts ...frameOpt) *Frame { 215 var id ChannelID 216 _, err := rng.Read(id[:]) 217 if err != nil { 218 panic(err) 219 } 220 221 frame := &Frame{ 222 ID: id, 223 FrameNumber: uint16(rng.Int31n(math.MaxUint16 + 1)), 224 IsLast: testutils.RandomBool(rng), 225 } 226 227 // evaulaute options 228 for _, opt := range opts { 229 opt(rng, frame) 230 } 231 232 // default if no option set field 233 if frame.Data == nil { 234 datalen := int(rng.Intn(MaxFrameLen + 1)) 235 frame.Data = testutils.RandomData(rng, datalen) 236 } 237 238 return frame 239 } 240 241 type frameOpt func(*rand.Rand, *Frame) 242 243 func frameWithDataLen(l int) frameOpt { 244 return func(rng *rand.Rand, frame *Frame) { 245 frame.Data = testutils.RandomData(rng, l) 246 } 247 }