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  }