diff --git a/op-e2e/actions/garbage_channel_out.go b/op-e2e/actions/garbage_channel_out.go index b2888d28b470..41a290570ea9 100644 --- a/op-e2e/actions/garbage_channel_out.go +++ b/op-e2e/actions/garbage_channel_out.go @@ -207,10 +207,10 @@ func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint1 // Fixed overhead: 32 + 8 + 2 + 4 + 1 = 47 bytes. // Add one extra byte for the version byte (for the entire L1 tx though) maxDataSize := maxSize - 47 - 1 - if maxDataSize > uint64(co.buf.Len()) { + if maxDataSize >= uint64(co.buf.Len()) { maxDataSize = uint64(co.buf.Len()) // If we are closed & will not spill past the current frame - // mark it is the final frame of the channel. + // mark it as the final frame of the channel. if co.closed { f.IsLast = true } diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index a4ea102f2d9a..52fd2fb36da0 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -322,10 +322,10 @@ func createEmptyFrame(id ChannelID, frame uint64, readyBytes int, closed bool, m // Copy data from the local buffer into the frame data buffer maxDataSize := maxSize - FrameV0OverHeadSize - if maxDataSize > uint64(readyBytes) { + if maxDataSize >= uint64(readyBytes) { maxDataSize = uint64(readyBytes) // If we are closed & will not spill past the current frame - // mark it is the final frame of the channel. + // mark it as the final frame of the channel. if closed { f.IsLast = true } diff --git a/op-node/rollup/derive/channel_out_test.go b/op-node/rollup/derive/channel_out_test.go index a46a7a333397..7e6bc04cb06c 100644 --- a/op-node/rollup/derive/channel_out_test.go +++ b/op-node/rollup/derive/channel_out_test.go @@ -2,7 +2,9 @@ package derive import ( "bytes" + "io" "math/big" + "math/rand" "testing" "github.com/ethereum/go-ethereum/common" @@ -68,6 +70,27 @@ func TestOutputFrameSmallMaxSize(t *testing.T) { } } +func TestOutputFrameNoEmptyLastFrame(t *testing.T) { + cout, err := NewChannelOut(SingularBatchType, &nonCompressor{}, nil) + require.NoError(t, err) + + rng := rand.New(rand.NewSource(0x543331)) + chainID := big.NewInt(rng.Int63n(1000)) + txCount := 1 + singularBatch := RandomSingularBatch(rng, txCount, chainID) + + written, err := cout.AddSingularBatch(singularBatch, 0) + require.NoError(t, err) + + require.NoError(t, cout.Close()) + + var buf bytes.Buffer + // Output a frame which needs exactly `written` bytes. This frame is expected to be the last frame. + _, err = cout.OutputFrame(&buf, written+FrameV0OverHeadSize) + require.ErrorIs(t, err, io.EOF) + +} + // TestRLPByteLimit ensures that stream encoder is properly limiting the length. // It will decode the input if `len(input) <= inputLimit`. func TestRLPByteLimit(t *testing.T) {