channel_assembler_test.go 4.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
package derive

import (
	"context"
	"io"
	"log/slog"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/ethereum-optimism/optimism/op-node/metrics"
	"github.com/ethereum-optimism/optimism/op-node/rollup"
	rolluptest "github.com/ethereum-optimism/optimism/op-node/rollup/test"
	"github.com/ethereum-optimism/optimism/op-service/testlog"
)

func TestChannelStage_NextData(t *testing.T) {
	for _, tc := range []struct {
		desc        string
		frames      [][]testFrame
		expErr      []error
		expData     []string
		expChID     []string
		rlpOverride *uint64
	}{
		{
			desc: "simple",
			frames: [][]testFrame{
				{"a:0:first!"},
			},
			expErr:  []error{nil},
			expData: []string{"first"},
			expChID: []string{""},
		},
		{
			desc: "simple-two",
			frames: [][]testFrame{
				{"a:0:first", "a:1:second!"},
			},
			expErr:  []error{nil},
			expData: []string{"firstsecond"},
			expChID: []string{""},
		},
		{
			desc: "drop-other",
			frames: [][]testFrame{
				{"a:0:first", "b:1:foo"},
				{"a:1:second", "c:1:bar!"},
				{"a:2:third!"},
			},
			expErr:  []error{io.EOF, io.EOF, nil},
			expData: []string{"", "", "firstsecondthird"},
			expChID: []string{"a", "a", ""},
		},
		{
			desc: "drop-non-first",
			frames: [][]testFrame{
				{"a:1:foo"},
			},
			expErr:  []error{io.EOF},
			expData: []string{""},
			expChID: []string{""},
		},
		{
			desc: "first-discards",
			frames: [][]testFrame{
				{"b:0:foo"},
				{"a:0:first!"},
			},
			expErr:  []error{io.EOF, nil},
			expData: []string{"", "first"},
			expChID: []string{"b", ""},
		},
		{
			desc: "already-closed",
			frames: [][]testFrame{
				{"a:0:foo"},
				{"a:1:bar!", "a:2:baz!"},
			},
			expErr:  []error{io.EOF, nil},
			expData: []string{"", "foobar"},
			expChID: []string{"a", ""},
		},
		{
			desc: "max-size",
			frames: [][]testFrame{
				{"a:0:0123456789!"},
			},
			expErr:      []error{nil},
			expData:     []string{"0123456789"},
			expChID:     []string{""},
			rlpOverride: ptr[uint64](frameOverhead + 10),
		},
		{
			desc: "oversized",
			frames: [][]testFrame{
				{"a:0:0123456789x!"},
			},
			expErr:      []error{io.EOF},
			expData:     []string{""},
			expChID:     []string{""},
			rlpOverride: ptr[uint64](frameOverhead + 10),
		},
	} {
		t.Run(tc.desc, func(t *testing.T) {
			fq := &fakeChannelBankInput{}
			lgr := testlog.Logger(t, slog.LevelWarn)
			spec := &rolluptest.ChainSpec{
				ChainSpec: rollup.NewChainSpec(&rollup.Config{}),

				MaxRLPBytesPerChannelOverride: tc.rlpOverride,
			}
113
			cs := NewChannelAssembler(lgr, spec, fq, metrics.NoopMetrics)
114 115 116

			for i, fs := range tc.frames {
				fq.AddFrames(fs...)
117
				data, err := cs.NextRawChannel(context.Background())
118 119 120 121 122 123 124 125 126 127 128 129 130 131
				require.Equal(t, tc.expData[i], string(data))
				require.ErrorIs(t, tc.expErr[i], err)
				// invariant: never holds a ready channel
				require.True(t, cs.channel == nil || !cs.channel.IsReady())

				cid := tc.expChID[i]
				if cid == "" {
					require.Nil(t, cs.channel)
				} else {
					require.Equal(t, strChannelID(cid), cs.channel.ID())
				}
			}

			// final call should always be io.EOF after exhausting frame queue
132
			data, err := cs.NextRawChannel(context.Background())
133 134 135 136 137 138 139 140 141 142 143
			require.Nil(t, data)
			require.Equal(t, io.EOF, err)
		})
	}
}

func TestChannelStage_NextData_Timeout(t *testing.T) {
	require := require.New(t)
	fq := &fakeChannelBankInput{}
	lgr := testlog.Logger(t, slog.LevelWarn)
	spec := rollup.NewChainSpec(&rollup.Config{GraniteTime: ptr(uint64(0))}) // const channel timeout
144
	cs := NewChannelAssembler(lgr, spec, fq, metrics.NoopMetrics)
145 146

	fq.AddFrames("a:0:foo")
147
	data, err := cs.NextRawChannel(context.Background())
148 149 150 151 152 153 154 155
	require.Nil(data)
	require.Equal(io.EOF, err)
	require.NotNil(cs.channel)
	require.Equal(strChannelID("a"), cs.channel.ID())

	// move close to timeout
	fq.origin.Number = spec.ChannelTimeout(0)
	fq.AddFrames("a:1:bar")
156
	data, err = cs.NextRawChannel(context.Background())
157 158 159 160 161 162 163 164
	require.Nil(data)
	require.Equal(io.EOF, err)
	require.NotNil(cs.channel)
	require.Equal(strChannelID("a"), cs.channel.ID())

	// timeout channel by moving origin past timeout
	fq.origin.Number = spec.ChannelTimeout(0) + 1
	fq.AddFrames("a:2:baz!")
165
	data, err = cs.NextRawChannel(context.Background())
166 167 168 169
	require.Nil(data)
	require.Equal(io.EOF, err)
	require.Nil(cs.channel)
}