@@ -2,6 +2,7 @@ package derive
22
33import (
44 "bytes"
5+ "fmt"
56 "io"
67 "math/big"
78 "math/rand"
@@ -36,63 +37,93 @@ func (s *nonCompressor) FullErr() error {
3637 return nil
3738}
3839
39- func (t * nonCompressor ) TargetOutputSize () uint64 {
40- return 0
40+ // channelTypes allows tests to run against different channel types
41+ var channelTypes = []struct {
42+ ChannelOut func (t * testing.T ) ChannelOut
43+ Name string
44+ }{
45+ {
46+ Name : "Singular" ,
47+ ChannelOut : func (t * testing.T ) ChannelOut {
48+ cout , err := NewSingularChannelOut (& nonCompressor {})
49+ require .NoError (t , err )
50+ return cout
51+ },
52+ },
53+ {
54+ Name : "Span" ,
55+ ChannelOut : func (t * testing.T ) ChannelOut {
56+ cout , err := NewSpanChannelOut (0 , big .NewInt (0 ), 128_000 )
57+ require .NoError (t , err )
58+ return cout
59+ },
60+ },
4161}
4262
4363func TestChannelOutAddBlock (t * testing.T ) {
44- cout , err := NewChannelOut ( & nonCompressor {})
45- require . NoError ( t , err )
46-
47- t . Run ( "returns err if first tx is not an l1info tx" , func ( t * testing. T ) {
48- header := & types.Header { Number : big . NewInt ( 1 ), Difficulty : big . NewInt ( 100 )}
49- block := types .NewBlockWithHeader ( header ). WithBody (
50- [] * types.Transaction {
51- types . NewTx ( & types. DynamicFeeTx {}) ,
52- } ,
53- nil ,
54- )
55- _ , err := cout . AddBlock ( & rollupCfg , block )
56- require .Error ( t , err )
57- require . Equal ( t , ErrNotDepositTx , err )
58- })
64+ for _ , tcase := range channelTypes {
65+ t . Run ( fmt . Sprintf ( "%s - returns err if first tx is not an l1info tx" , tcase . Name ), func ( t * testing. T ) {
66+ cout := tcase . ChannelOut ( t )
67+ header := & types. Header { Number : big . NewInt ( 1 ), Difficulty : big . NewInt ( 100 )}
68+ block := types .NewBlockWithHeader ( header ). WithBody (
69+ [] * types.Transaction {
70+ types .NewTx ( & types. DynamicFeeTx {}),
71+ } ,
72+ nil ,
73+ )
74+ _ , err := cout . AddBlock ( & rollupCfg , block )
75+ require . Error ( t , err )
76+ require .Equal ( t , ErrNotDepositTx , err )
77+ } )
78+ }
5979}
6080
6181// TestOutputFrameSmallMaxSize tests that calling [OutputFrame] with a small
6282// max size that is below the fixed frame size overhead of 23, will return
6383// an error.
6484func TestOutputFrameSmallMaxSize (t * testing.T ) {
65- cout , err := NewChannelOut (& nonCompressor {})
66- require .NoError (t , err )
67-
68- // Call OutputFrame with the range of small max size values that err
69- var w bytes.Buffer
70- for i := 0 ; i < 23 ; i ++ {
71- fid , err := cout .OutputFrame (& w , uint64 (i ))
72- require .ErrorIs (t , err , ErrMaxFrameSizeTooSmall )
73- require .Zero (t , fid )
85+ for _ , tcase := range channelTypes {
86+ t .Run (fmt .Sprintf ("%s - returns ErrMaxFrameSizeTooSmall" , tcase .Name ), func (t * testing.T ) {
87+ cout := tcase .ChannelOut (t )
88+ // Call OutputFrame with the range of small max size values that err
89+ var w bytes.Buffer
90+ for i := 0 ; i < 23 ; i ++ {
91+ fid , err := cout .OutputFrame (& w , uint64 (i ))
92+ require .ErrorIs (t , err , ErrMaxFrameSizeTooSmall )
93+ require .Zero (t , fid )
94+ }
95+ })
7496 }
7597}
7698
7799func TestOutputFrameNoEmptyLastFrame (t * testing.T ) {
78- cout , err := NewChannelOut (& nonCompressor {})
79- require .NoError (t , err )
100+ for _ , tcase := range channelTypes {
101+ t .Run (fmt .Sprintf ("%s - no empty last frame" , tcase .Name ), func (t * testing.T ) {
102+ cout := tcase .ChannelOut (t )
80103
81- rng := rand .New (rand .NewSource (0x543331 ))
82- chainID := big .NewInt (rng . Int63n ( 1000 ) )
83- txCount := 1
84- singularBatch := RandomSingularBatch (rng , txCount , chainID )
104+ rng := rand .New (rand .NewSource (0x543331 ))
105+ chainID := big .NewInt (0 )
106+ txCount := 1
107+ singularBatch := RandomSingularBatch (rng , txCount , chainID )
85108
86- written , err := cout .AddSingularBatch (singularBatch , 0 )
87- require .NoError (t , err )
109+ written , err := cout .AddSingularBatch (singularBatch , 0 )
110+ require .NoError (t , err )
88111
89- require .NoError (t , cout .Close ())
90-
91- var buf bytes.Buffer
92- // Output a frame which needs exactly `written` bytes. This frame is expected to be the last frame.
93- _ , err = cout .OutputFrame (& buf , written + FrameV0OverHeadSize )
94- require .ErrorIs (t , err , io .EOF )
112+ require .NoError (t , cout .Close ())
113+ // span batches return the length of the RLP structure, not the compressed length
114+ // so we need to collect the compressed length from the span batch
115+ // no production code relies on the written value from either type of batch
116+ // (span batches don't always compress their data, and can't always determine the compressed length for this reason)
117+ if span , ok := cout .(* SpanChannelOut ); ok {
118+ written = uint64 (span .compressed .Len ())
119+ }
95120
121+ var buf bytes.Buffer
122+ // Output a frame which needs exactly `written` bytes. This frame is expected to be the last frame.
123+ _ , err = cout .OutputFrame (& buf , written + FrameV0OverHeadSize )
124+ require .ErrorIs (t , err , io .EOF )
125+ })
126+ }
96127}
97128
98129// TestRLPByteLimit ensures that stream encoder is properly limiting the length.
@@ -188,3 +219,94 @@ func TestBlockToBatchValidity(t *testing.T) {
188219 _ , _ , err := BlockToSingularBatch (& rollupCfg , block )
189220 require .ErrorContains (t , err , "has no transactions" )
190221}
222+
223+ // TestSpanChannelOutCompressionOnlyOneBatch tests that the SpanChannelOut compression works as expected when there is only one batch
224+ // and it is larger than the target size. The single batch should be compressed, and the channel should now be full
225+ func TestSpanChannelOutCompressionOnlyOneBatch (t * testing.T ) {
226+ // target is larger than one batch, but smaller than two batches
227+ target := uint64 (300 )
228+ rng := rand .New (rand .NewSource (0x543331 ))
229+ chainID := big .NewInt (rng .Int63n (1000 ))
230+ txCount := 1
231+ cout , err := NewSpanChannelOut (0 , chainID , target )
232+ require .NoError (t , err )
233+
234+ compressedLens := []int {}
235+
236+ // adding the first batch should not cause an error
237+ singularBatch := RandomSingularBatch (rng , txCount , chainID )
238+ _ , err = cout .AddSingularBatch (singularBatch , 0 )
239+ compressedLens = append (compressedLens , cout .compressed .Len ())
240+ require .NoError (t , err )
241+
242+ // confirm compression was not skipped
243+ require .Greater (t , compressedLens [0 ], 0 )
244+ // confirm the channel is full
245+ require .ErrorIs (t , cout .FullErr (), ErrCompressorFull )
246+ }
247+
248+ // TestSpanChannelOutCompressionUndo tests that the SpanChannelOut compression works as expected to not accept a second batch
249+ // if that batch would cause the channel to exceed the target size. The first batch should be compressed only
250+ // the channel should be compressed, full, and should not have the second batch
251+ func TestSpanChannelOutCompressionUndo (t * testing.T ) {
252+ // target is larger than one batch, but smaller than two batches
253+ target := uint64 (750 )
254+ rng := rand .New (rand .NewSource (0x543331 ))
255+ chainID := big .NewInt (rng .Int63n (1000 ))
256+ txCount := 1
257+ cout , err := NewSpanChannelOut (0 , chainID , target )
258+ require .NoError (t , err )
259+
260+ compressedLens := []int {}
261+ rlpLens := []int {}
262+
263+ // adding the first batch should not cause an error
264+ singularBatch := RandomSingularBatch (rng , txCount , chainID )
265+ _ , err = cout .AddSingularBatch (singularBatch , 0 )
266+ compressedLens = append (compressedLens , cout .compressed .Len ())
267+ rlpLens = append (rlpLens , cout .activeRLP ().Len ())
268+ require .NoError (t , err )
269+
270+ // adding the second batch should cause an error
271+ singularBatch = RandomSingularBatch (rng , txCount , chainID )
272+ _ , err = cout .AddSingularBatch (singularBatch , 0 )
273+ compressedLens = append (compressedLens , cout .compressed .Len ())
274+ rlpLens = append (rlpLens , cout .activeRLP ().Len ())
275+ require .ErrorIs (t , err , ErrCompressorFull )
276+
277+ // confirm that the first compression was skipped
278+ require .Equal (t , 0 , compressedLens [0 ])
279+ // confirm that the second compression was not skipped
280+ require .Greater (t , compressedLens [1 ], 0 )
281+ // confirm that the second rlp is tht same size as the first (because the second batch was not added)
282+ require .Equal (t , rlpLens [0 ], rlpLens [1 ])
283+ }
284+
285+ // TestSpanChannelOutClose tests that the SpanChannelOut compression works as expected when the channel is closed.
286+ // it should compress the batch even if it is smaller than the target size because the channel is closing
287+ func TestSpanChannelOutClose (t * testing.T ) {
288+ // target is larger than one batch
289+ target := uint64 (600 )
290+ rng := rand .New (rand .NewSource (0x543331 ))
291+ chainID := big .NewInt (rng .Int63n (1000 ))
292+ txCount := 1
293+ cout , err := NewSpanChannelOut (0 , chainID , target )
294+ require .NoError (t , err )
295+
296+ singularBatch := RandomSingularBatch (rng , txCount , chainID )
297+ _ , err = cout .AddSingularBatch (singularBatch , 0 )
298+ require .NoError (t , err )
299+
300+ rlpLen := cout .activeRLP ().Len ()
301+
302+ // confirm no compression has happened yet
303+ require .Equal (t , 0 , cout .compressed .Len ())
304+ require .Less (t , uint64 (rlpLen ), target )
305+
306+ // close the channel
307+ require .NoError (t , cout .Close ())
308+
309+ // confirm that the only batch was compressed, and that the RLP did not change
310+ require .Greater (t , cout .compressed .Len (), 0 )
311+ require .Equal (t , rlpLen , cout .activeRLP ().Len ())
312+ }
0 commit comments