Commit 6dfe4b02 authored by Emiliano Bonassi's avatar Emiliano Bonassi Committed by GitHub

fix(op-batcher): support new fjord maxRLPBytesPerChannelFjord via rollup chain spec (#11169)

* fix(op-batcher): support new fjord maxRLPBytesPerChannelFjord

* fix(op-batcher): resolve pr comments

* test(op-batcher): check post-Fjord using correct MaxRLPBytesPerChannel
parent 8b910d7a
......@@ -90,11 +90,13 @@ func NewChannelBuilder(cfg ChannelConfig, rollupCfg rollup.Config, latestL1Origi
if err != nil {
return nil, err
}
chainSpec := rollup.NewChainSpec(&rollupCfg)
var co derive.ChannelOut
if cfg.BatchType == derive.SpanBatchType {
co, err = derive.NewSpanChannelOut(rollupCfg.Genesis.L2Time, rollupCfg.L2ChainID, cfg.CompressorConfig.TargetOutputSize, cfg.CompressorConfig.CompressionAlgo)
co, err = derive.NewSpanChannelOut(rollupCfg.Genesis.L2Time, rollupCfg.L2ChainID, cfg.CompressorConfig.TargetOutputSize, cfg.CompressorConfig.CompressionAlgo, chainSpec)
} else {
co, err = derive.NewSingularChannelOut(c)
co, err = derive.NewSingularChannelOut(c, chainSpec)
}
if err != nil {
return nil, fmt.Errorf("creating channel out: %w", err)
......
......@@ -82,18 +82,19 @@ func newMiniL2BlockWithNumberParentAndL1Information(numTx int, l2Number *big.Int
// addTooManyBlocks adds blocks to the channel until it hits an error,
// which is presumably ErrTooManyRLPBytes.
func addTooManyBlocks(cb *ChannelBuilder) error {
func addTooManyBlocks(cb *ChannelBuilder, blockCount int) (int, error) {
rng := rand.New(rand.NewSource(1234))
t := time.Now()
for i := 0; i < 10_000; i++ {
for i := 0; i < blockCount; i++ {
block := dtest.RandomL2BlockWithChainIdAndTime(rng, 1000, defaultTestRollupConfig.L2ChainID, t.Add(time.Duration(i)*time.Second))
_, err := cb.AddBlock(block)
if err != nil {
return err
return i + 1, err
}
}
return nil
return blockCount, nil
}
// FuzzDurationTimeoutZeroMaxChannelDuration ensures that when whenever the MaxChannelDuration
......@@ -292,6 +293,7 @@ func TestChannelBuilderBatchType(t *testing.T) {
f func(t *testing.T, batchType uint)
}{
{"ChannelBuilder_MaxRLPBytesPerChannel", ChannelBuilder_MaxRLPBytesPerChannel},
{"ChannelBuilder_MaxRLPBytesPerFjord", ChannelBuilder_MaxRLPBytesPerChannelFjord},
{"ChannelBuilder_OutputFramesMaxFrameIndex", ChannelBuilder_OutputFramesMaxFrameIndex},
{"ChannelBuilder_AddBlock", ChannelBuilder_AddBlock},
{"ChannelBuilder_PendingFrames_TotalFrames", ChannelBuilder_PendingFrames_TotalFrames},
......@@ -367,7 +369,7 @@ func ChannelBuilder_OutputWrongFramePanic(t *testing.T, batchType uint) {
// the type of batch does not matter here because we are using it to construct a broken frame
c, err := channelConfig.CompressorConfig.NewCompressor()
require.NoError(t, err)
co, err := derive.NewSingularChannelOut(c)
co, err := derive.NewSingularChannelOut(c, rollup.NewChainSpec(&defaultTestRollupConfig))
require.NoError(t, err)
var buf bytes.Buffer
fn, err := co.OutputFrame(&buf, channelConfig.MaxFrameSize)
......@@ -505,7 +507,8 @@ func ChannelBuilder_OutputFrames_SpanBatch(t *testing.T, algo derive.Compression
func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) {
t.Parallel()
channelConfig := defaultTestChannelConfig()
channelConfig.MaxFrameSize = rollup.SafeMaxRLPBytesPerChannel * 2
chainSpec := rollup.NewChainSpec(&defaultTestRollupConfig)
channelConfig.MaxFrameSize = chainSpec.MaxRLPBytesPerChannel(latestL1BlockOrigin) * 2
channelConfig.InitNoneCompressor()
channelConfig.BatchType = batchType
......@@ -514,10 +517,54 @@ func ChannelBuilder_MaxRLPBytesPerChannel(t *testing.T, batchType uint) {
require.NoError(t, err)
// Add a block that overflows the [ChannelOut]
err = addTooManyBlocks(cb)
_, err = addTooManyBlocks(cb, 10_000)
require.ErrorIs(t, err, derive.ErrTooManyRLPBytes)
}
// ChannelBuilder_MaxRLPBytesPerChannelFjord tests the [ChannelBuilder.OutputFrames]
// function works as intended postFjord.
// strategy:
// check preFjord how many blocks to fill the channel
// then check postFjord w/ double the amount of blocks
func ChannelBuilder_MaxRLPBytesPerChannelFjord(t *testing.T, batchType uint) {
t.Parallel()
channelConfig := defaultTestChannelConfig()
chainSpec := rollup.NewChainSpec(&defaultTestRollupConfig)
channelConfig.MaxFrameSize = chainSpec.MaxRLPBytesPerChannel(latestL1BlockOrigin) * 2
channelConfig.InitNoneCompressor()
channelConfig.BatchType = batchType
// Construct the channel builder
cb, err := NewChannelBuilder(channelConfig, defaultTestRollupConfig, latestL1BlockOrigin)
require.NoError(t, err)
// Count how many a block that overflows the [ChannelOut]
blockCount, err := addTooManyBlocks(cb, 10_000)
require.ErrorIs(t, err, derive.ErrTooManyRLPBytes)
// Create a new channel builder with fjord fork
now := time.Now()
fjordTime := uint64(now.Add(-1 * time.Second).Unix())
rollupConfig := rollup.Config{
Genesis: rollup.Genesis{L2: eth.BlockID{Number: 0}},
L2ChainID: big.NewInt(1234),
FjordTime: &fjordTime,
}
chainSpec = rollup.NewChainSpec(&rollupConfig)
channelConfig.MaxFrameSize = chainSpec.MaxRLPBytesPerChannel(uint64(now.Unix())) * 2
channelConfig.InitNoneCompressor()
channelConfig.BatchType = batchType
cb, err = NewChannelBuilder(channelConfig, rollupConfig, latestL1BlockOrigin)
require.NoError(t, err)
// try add double the amount of block, it should not error
_, err = addTooManyBlocks(cb, 2*blockCount)
require.NoError(t, err)
}
// ChannelBuilder_OutputFramesMaxFrameIndex tests the [ChannelBuilder.OutputFrames]
// function errors when the max frame index is reached.
func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) {
......
......@@ -159,9 +159,12 @@ func (co *GarbageChannelOut) AddBlock(rollupCfg *rollup.Config, block *types.Blo
buf.Reset()
buf.Write(bufBytes)
}
if co.rlpLength+buf.Len() > rollup.SafeMaxRLPBytesPerChannel {
chainSpec := rollup.NewChainSpec(rollupCfg)
maxRLPBytesPerChannel := chainSpec.MaxRLPBytesPerChannel(block.Time())
if co.rlpLength+buf.Len() > int(maxRLPBytesPerChannel) {
return fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w",
buf.Len(), co.rlpLength, rollup.SafeMaxRLPBytesPerChannel, derive.ErrTooManyRLPBytes)
buf.Len(), co.rlpLength, maxRLPBytesPerChannel, derive.ErrTooManyRLPBytes)
}
co.rlpLength += buf.Len()
......
......@@ -199,12 +199,13 @@ func (s *L2Batcher) Buffer(t Testing) error {
if s.l2BatcherCfg.ForceSubmitSingularBatch && s.l2BatcherCfg.ForceSubmitSpanBatch {
t.Fatalf("ForceSubmitSingularBatch and ForceSubmitSpanBatch cannot be set to true at the same time")
} else {
chainSpec := rollup.NewChainSpec(s.rollupCfg)
// use span batch if we're forcing it or if we're at/beyond delta
if s.l2BatcherCfg.ForceSubmitSpanBatch || s.rollupCfg.IsDelta(block.Time()) {
ch, err = derive.NewSpanChannelOut(s.rollupCfg.Genesis.L2Time, s.rollupCfg.L2ChainID, target, derive.Zlib)
ch, err = derive.NewSpanChannelOut(s.rollupCfg.Genesis.L2Time, s.rollupCfg.L2ChainID, target, derive.Zlib, chainSpec)
// use singular batches in all other cases
} else {
ch, err = derive.NewSingularChannelOut(c)
ch, err = derive.NewSingularChannelOut(c, chainSpec)
}
}
}
......
......@@ -20,6 +20,7 @@ import (
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
engine2 "github.com/ethereum-optimism/optimism/op-node/rollup/engine"
"github.com/ethereum-optimism/optimism/op-node/rollup/event"
......@@ -32,7 +33,7 @@ import (
)
func newSpanChannelOut(t StatefulTesting, e e2eutils.SetupData) derive.ChannelOut {
channelOut, err := derive.NewSpanChannelOut(e.RollupCfg.Genesis.L2Time, e.RollupCfg.L2ChainID, 128_000, derive.Zlib)
channelOut, err := derive.NewSpanChannelOut(e.RollupCfg.Genesis.L2Time, e.RollupCfg.L2ChainID, 128_000, derive.Zlib, rollup.NewChainSpec(e.RollupCfg))
require.NoError(t, err)
return channelOut
}
......
......@@ -8,6 +8,7 @@ import (
"time"
"github.com/ethereum-optimism/optimism/op-batcher/compressor"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/stretchr/testify/require"
)
......@@ -87,13 +88,14 @@ var (
// channelOutByType returns a channel out of the given type as a helper for the benchmarks
func channelOutByType(b *testing.B, batchType uint, cd compressorDetails) (derive.ChannelOut, error) {
chainID := big.NewInt(333)
rollupConfig := new(rollup.Config)
if batchType == derive.SingularBatchType {
compressor, err := cd.Compressor()
require.NoError(b, err)
return derive.NewSingularChannelOut(compressor)
return derive.NewSingularChannelOut(compressor, rollup.NewChainSpec(rollupConfig))
}
if batchType == derive.SpanBatchType {
return derive.NewSpanChannelOut(0, chainID, cd.config.TargetOutputSize, cd.config.CompressionAlgo)
return derive.NewSpanChannelOut(0, chainID, cd.config.TargetOutputSize, cd.config.CompressionAlgo, rollup.NewChainSpec(rollupConfig))
}
return nil, fmt.Errorf("unsupported batch type: %d", batchType)
}
......
......@@ -20,11 +20,6 @@ const (
maxRLPBytesPerChannelFjord = 100_000_000
)
// SafeMaxRLPBytesPerChannel is a limit of RLP Bytes per channel that is valid across every OP Stack chain.
// The limit on certain chains at certain times may be higher
// TODO(#10428) Remove this parameter
const SafeMaxRLPBytesPerChannel = maxRLPBytesPerChannelBedrock
// Fjord changes the max sequencer drift to a protocol constant. It was previously configurable via
// the rollup config.
// From Fjord, the max sequencer drift for a given block timestamp should be learned via the
......
......@@ -74,18 +74,21 @@ type SingularChannelOut struct {
compress Compressor
closed bool
chainSpec *rollup.ChainSpec
}
func (co *SingularChannelOut) ID() ChannelID {
return co.id
}
func NewSingularChannelOut(compress Compressor) (*SingularChannelOut, error) {
func NewSingularChannelOut(compress Compressor, chainSpec *rollup.ChainSpec) (*SingularChannelOut, error) {
c := &SingularChannelOut{
id: ChannelID{},
frame: 0,
rlpLength: 0,
compress: compress,
chainSpec: chainSpec,
}
_, err := rand.Read(c.id[:])
if err != nil {
......@@ -139,9 +142,15 @@ func (co *SingularChannelOut) AddSingularBatch(batch *SingularBatch, _ uint64) e
if err := rlp.Encode(&buf, NewBatchData(batch)); err != nil {
return err
}
if co.rlpLength+buf.Len() > rollup.SafeMaxRLPBytesPerChannel {
// Fjord increases the max RLP bytes per channel. Activation of this change in the derivation pipeline
// is dependent on the timestamp of the L1 block that this channel got included in. So using the timestamp
// of the current batch guarantees that this channel will be included in an L1 block with a timestamp well after
// the Fjord activation.
maxRLPBytesPerChannel := co.chainSpec.MaxRLPBytesPerChannel(batch.Timestamp)
if co.rlpLength+buf.Len() > int(maxRLPBytesPerChannel) {
return fmt.Errorf("could not add %d bytes to channel of %d bytes, max is %d. err: %w",
buf.Len(), co.rlpLength, rollup.SafeMaxRLPBytesPerChannel, ErrTooManyRLPBytes)
buf.Len(), co.rlpLength, maxRLPBytesPerChannel, ErrTooManyRLPBytes)
}
co.rlpLength += buf.Len()
......
......@@ -36,21 +36,21 @@ func (s *nonCompressor) FullErr() error {
// channelTypes allows tests to run against different channel types
var channelTypes = []struct {
ChannelOut func(t *testing.T) ChannelOut
ChannelOut func(t *testing.T, rcfg *rollup.Config) ChannelOut
Name string
}{
{
Name: "Singular",
ChannelOut: func(t *testing.T) ChannelOut {
cout, err := NewSingularChannelOut(&nonCompressor{})
ChannelOut: func(t *testing.T, rcfg *rollup.Config) ChannelOut {
cout, err := NewSingularChannelOut(&nonCompressor{}, rollup.NewChainSpec(rcfg))
require.NoError(t, err)
return cout
},
},
{
Name: "Span",
ChannelOut: func(t *testing.T) ChannelOut {
cout, err := NewSpanChannelOut(0, big.NewInt(0), 128_000, Zlib)
ChannelOut: func(t *testing.T, rcfg *rollup.Config) ChannelOut {
cout, err := NewSpanChannelOut(0, big.NewInt(0), 128_000, Zlib, rollup.NewChainSpec(rcfg))
require.NoError(t, err)
return cout
},
......@@ -60,7 +60,7 @@ var channelTypes = []struct {
func TestChannelOutAddBlock(t *testing.T) {
for _, tcase := range channelTypes {
t.Run(tcase.Name, func(t *testing.T) {
cout := tcase.ChannelOut(t)
cout := tcase.ChannelOut(t, &rollupCfg)
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
block := types.NewBlockWithHeader(header).WithBody(
[]*types.Transaction{
......@@ -81,7 +81,7 @@ func TestChannelOutAddBlock(t *testing.T) {
func TestOutputFrameSmallMaxSize(t *testing.T) {
for _, tcase := range channelTypes {
t.Run(tcase.Name, func(t *testing.T) {
cout := tcase.ChannelOut(t)
cout := tcase.ChannelOut(t, &rollupCfg)
// Call OutputFrame with the range of small max size values that err
var w bytes.Buffer
for i := 0; i < FrameV0OverHeadSize; i++ {
......@@ -96,7 +96,7 @@ func TestOutputFrameSmallMaxSize(t *testing.T) {
func TestOutputFrameNoEmptyLastFrame(t *testing.T) {
for _, tcase := range channelTypes {
t.Run(tcase.Name, func(t *testing.T) {
cout := tcase.ChannelOut(t)
cout := tcase.ChannelOut(t, &rollupCfg)
rng := rand.New(rand.NewSource(0x543331))
chainID := big.NewInt(0)
......@@ -223,7 +223,7 @@ func SpanChannelAndBatches(t *testing.T, target uint64, len int, algo Compressio
rng := rand.New(rand.NewSource(0x543331))
chainID := big.NewInt(rng.Int63n(1000))
txCount := 1
cout, err := NewSpanChannelOut(0, chainID, target, algo)
cout, err := NewSpanChannelOut(0, chainID, target, algo, rollup.NewChainSpec(&rollupCfg))
require.NoError(t, err)
batches := make([]*SingularBatch, len)
// adding the first batch should not cause an error
......
......@@ -36,6 +36,8 @@ type SpanChannelOut struct {
full error
// spanBatch is the batch being built, which immutably holds genesis timestamp and chain ID, but otherwise can be reset
spanBatch *SpanBatch
chainSpec *rollup.ChainSpec
}
func (co *SpanChannelOut) ID() ChannelID {
......@@ -47,13 +49,14 @@ func (co *SpanChannelOut) setRandomID() error {
return err
}
func NewSpanChannelOut(genesisTimestamp uint64, chainID *big.Int, targetOutputSize uint64, compressionAlgo CompressionAlgo) (*SpanChannelOut, error) {
func NewSpanChannelOut(genesisTimestamp uint64, chainID *big.Int, targetOutputSize uint64, compressionAlgo CompressionAlgo, chainSpec *rollup.ChainSpec) (*SpanChannelOut, error) {
c := &SpanChannelOut{
id: ChannelID{},
frame: 0,
spanBatch: NewSpanBatch(genesisTimestamp, chainID),
rlp: [2]*bytes.Buffer{{}, {}},
target: targetOutputSize,
chainSpec: chainSpec,
}
var err error
if err = c.setRandomID(); err != nil {
......@@ -142,10 +145,14 @@ func (co *SpanChannelOut) AddSingularBatch(batch *SingularBatch, seqNum uint64)
return fmt.Errorf("failed to encode RawSpanBatch into bytes: %w", err)
}
// check the RLP length against the max
if co.activeRLP().Len() > rollup.SafeMaxRLPBytesPerChannel {
// Fjord increases the max RLP bytes per channel. Activation of this change in the derivation pipeline
// is dependent on the timestamp of the L1 block that this channel got included in. So using the timestamp
// of the current batch guarantees that this channel will be included in an L1 block with a timestamp well after
// the Fjord activation.
maxRLPBytesPerChannel := co.chainSpec.MaxRLPBytesPerChannel(batch.Timestamp)
if co.activeRLP().Len() > int(maxRLPBytesPerChannel) {
return fmt.Errorf("could not take %d bytes as replacement of channel of %d bytes, max is %d. err: %w",
co.activeRLP().Len(), co.inactiveRLP().Len(), rollup.SafeMaxRLPBytesPerChannel, ErrTooManyRLPBytes)
co.activeRLP().Len(), co.inactiveRLP().Len(), maxRLPBytesPerChannel, ErrTooManyRLPBytes)
}
// if the compressed data *plus* the new rlp data is under the target size, return early
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment