From d5dd1a3771bd887ac80e5c8638a819f0e3aa9747 Mon Sep 17 00:00:00 2001 From: Roberto Bayardo Date: Fri, 29 Dec 2023 15:38:40 -0800 Subject: [PATCH] support Ecotone l1 block attributes --- go.mod | 4 +- go.sum | 8 +- op-batcher/batcher/channel.go | 4 +- op-batcher/batcher/channel_builder.go | 6 +- op-batcher/batcher/channel_builder_test.go | 18 +- op-batcher/batcher/channel_manager.go | 4 +- op-batcher/batcher/driver.go | 2 +- op-e2e/actions/garbage_channel_out.go | 10 +- op-e2e/actions/l2_batcher.go | 2 +- op-e2e/actions/sync_test.go | 4 +- op-e2e/actions/system_config_test.go | 8 +- op-e2e/e2eutils/geth/wait.go | 5 +- op-e2e/op_geth.go | 10 +- op-e2e/op_geth_test.go | 7 +- op-e2e/system_fpp_test.go | 2 +- op-e2e/system_test.go | 47 ++--- op-e2e/system_tob_test.go | 4 +- op-node/rollup/derive/attributes.go | 2 +- .../rollup/derive/attributes_queue_test.go | 3 +- op-node/rollup/derive/attributes_test.go | 12 +- op-node/rollup/derive/batch_queue_test.go | 4 +- op-node/rollup/derive/batches.go | 2 +- op-node/rollup/derive/channel_out.go | 16 +- op-node/rollup/derive/channel_out_test.go | 10 +- op-node/rollup/derive/engine_consolidate.go | 44 +++-- op-node/rollup/derive/engine_controller.go | 20 +- op-node/rollup/derive/engine_queue.go | 10 +- op-node/rollup/derive/engine_queue_test.go | 4 +- op-node/rollup/derive/fuzz_parsers_test.go | 43 ++++- op-node/rollup/derive/l1_block_info.go | 176 +++++++++++++++--- op-node/rollup/derive/l1_block_info_test.go | 33 ++-- .../rollup/derive/l1_block_info_tob_test.go | 20 +- op-node/rollup/derive/l2block_util.go | 5 +- op-node/rollup/derive/payload_util.go | 29 +-- op-node/rollup/derive/pipeline.go | 22 +-- op-node/rollup/derive/span_channel_out.go | 6 +- op-node/rollup/derive/test/random.go | 10 +- op-node/rollup/driver/sequencer.go | 22 +-- op-node/rollup/driver/sequencer_test.go | 6 +- op-node/rollup/superchain.go | 2 +- op-program/client/l2/engine.go | 6 +- op-program/client/l2/engine_test.go | 10 +- .../l2/engineapi/test/l2_engine_api_tests.go | 9 +- op-service/sources/l2_client.go | 12 +- 44 files changed, 449 insertions(+), 234 deletions(-) diff --git a/go.mod b/go.mod index a721b9c5d1d3..6cc903d1d02f 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 - github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231211205419-ff2e152c624f + github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240103191009-655947053753 github.com/ethereum/go-ethereum v1.13.5 github.com/fsnotify/fsnotify v1.7.0 github.com/go-chi/chi/v5 v5.0.11 @@ -219,7 +219,7 @@ require ( rsc.io/tmplfunc v0.0.3 // indirect ) -replace github.com/ethereum/go-ethereum v1.13.5 => github.com/ethereum-optimism/op-geth v1.101304.2-0.20231130012434-cd5316814d08 +replace github.com/ethereum/go-ethereum v1.13.5 => github.com/ethereum-optimism/op-geth v1.101305.1-rc.1.0.20240109215805-a79bde2c0f4f //replace github.com/ethereum-optimism/superchain-registry/superchain => ../superchain-registry/superchain //replace github.com/ethereum/go-ethereum v1.13.5 => ../go-ethereum diff --git a/go.sum b/go.sum index 54c309b174e1..8c9a280afd34 100644 --- a/go.sum +++ b/go.sum @@ -165,10 +165,10 @@ github.com/elastic/gosigar v0.14.2 h1:Dg80n8cr90OZ7x+bAax/QjoW/XqTI11RmA79ZwIm9/ github.com/elastic/gosigar v0.14.2/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc= github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs= -github.com/ethereum-optimism/op-geth v1.101304.2-0.20231130012434-cd5316814d08 h1:IrkNfwELCMOsckxA6vorlYmlsWNjXCDvPGtl6fWOD0o= -github.com/ethereum-optimism/op-geth v1.101304.2-0.20231130012434-cd5316814d08/go.mod h1:KyXcYdAJTSm8tvOmd+KPeOygiA+FEE5VX3vs2WwjwQ4= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231211205419-ff2e152c624f h1:ISd3MAco0U0XT5ADDQ8pzVntQpL9yEUQzpsIqfLJY2M= -github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20231211205419-ff2e152c624f/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk= +github.com/ethereum-optimism/op-geth v1.101305.1-rc.1.0.20240109215805-a79bde2c0f4f h1:W8oHHUpk3d1h5MLEC9vPQ2oiC9m2NdGHcCbbra9VqHc= +github.com/ethereum-optimism/op-geth v1.101305.1-rc.1.0.20240109215805-a79bde2c0f4f/go.mod h1:HGpRaQiUONEEfsL/hq9/jg8YnR9TCHCPqjmaPoFBhto= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240103191009-655947053753 h1:DL667cfM6peU8H9Ut/uu9h9Bd4gQCcJrjq+yYsfYwjk= +github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240103191009-655947053753/go.mod h1:/70H/KqrtKcvWvNGVj6S3rAcLC+kUPr3t2aDmYIS+Xk= github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= diff --git a/op-batcher/batcher/channel.go b/op-batcher/batcher/channel.go index baf9ea2b24b9..eb037af990c2 100644 --- a/op-batcher/batcher/channel.go +++ b/op-batcher/batcher/channel.go @@ -174,8 +174,8 @@ func (s *channel) RegisterL1Block(l1BlockNum uint64) { s.channelBuilder.RegisterL1Block(l1BlockNum) } -func (s *channel) AddBlock(block *types.Block) (derive.L1BlockInfo, error) { - return s.channelBuilder.AddBlock(block) +func (s *channel) AddBlock(rollupCfg *rollup.Config, block *types.Block) (*derive.L1BlockInfo, error) { + return s.channelBuilder.AddBlock(rollupCfg, block) } func (s *channel) InputBytes() int { diff --git a/op-batcher/batcher/channel_builder.go b/op-batcher/batcher/channel_builder.go index 48ac86371371..92a604c5b7e5 100644 --- a/op-batcher/batcher/channel_builder.go +++ b/op-batcher/batcher/channel_builder.go @@ -201,12 +201,12 @@ func (c *channelBuilder) Reset() error { // first transaction for subsequent use by the caller. // // Call OutputFrames() afterwards to create frames. -func (c *channelBuilder) AddBlock(block *types.Block) (derive.L1BlockInfo, error) { +func (c *channelBuilder) AddBlock(rollupCfg *rollup.Config, block *types.Block) (*derive.L1BlockInfo, error) { if c.IsFull() { - return derive.L1BlockInfo{}, c.FullErr() + return nil, c.FullErr() } - batch, l1info, err := derive.BlockToSingularBatch(block) + batch, l1info, err := derive.BlockToSingularBatch(rollupCfg, block) if err != nil { return l1info, fmt.Errorf("converting block to batch: %w", err) } diff --git a/op-batcher/batcher/channel_builder_test.go b/op-batcher/batcher/channel_builder_test.go index 5ee5c5367134..48e15a470ff4 100644 --- a/op-batcher/batcher/channel_builder_test.go +++ b/op-batcher/batcher/channel_builder_test.go @@ -119,7 +119,7 @@ func FuzzChannelConfig_CheckTimeout(f *testing.F) { // channelBuilder.AddBlock method. func addMiniBlock(cb *channelBuilder) error { a := newMiniL2Block(0) - _, err := cb.AddBlock(a) + _, err := cb.AddBlock(&defaultTestRollupConfig, a) return err } @@ -144,7 +144,7 @@ func newMiniL2BlockWithNumberParent(numTx int, number *big.Int, parent common.Ha Difficulty: common.Big0, Number: big.NewInt(100), }, nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, err := derive.L1InfoDeposit(0, eth.BlockToInfo(l1Block), eth.SystemConfig{}, false) + l1InfoTx, err := derive.L1InfoDeposit(&defaultTestRollupConfig, eth.SystemConfig{}, 0, eth.BlockToInfo(l1Block), 0) if err != nil { panic(err) } @@ -167,7 +167,7 @@ func addTooManyBlocks(cb *channelBuilder) error { rng := rand.New(rand.NewSource(1234)) for i := 0; i < 10_000; i++ { block := dtest.RandomL2BlockWithChainId(rng, 1000, defaultTestRollupConfig.L2ChainID) - _, err := cb.AddBlock(block) + _, err := cb.AddBlock(&defaultTestRollupConfig, block) if err != nil { return err } @@ -597,7 +597,7 @@ func ChannelBuilder_OutputFramesMaxFrameIndex(t *testing.T, batchType uint) { require.Equal(t, 0, cb.PendingFrames()) for { a := dtest.RandomL2BlockWithChainId(rng, 1, defaultTestRollupConfig.L2ChainID) - _, err = cb.AddBlock(a) + _, err = cb.AddBlock(&defaultTestRollupConfig, a) if cb.IsFull() { fullErr := cb.FullErr() require.ErrorIs(t, fullErr, derive.CompressorFullErr) @@ -778,7 +778,7 @@ func ChannelBuilder_PendingFrames_TotalFrames(t *testing.T, batchType uint) { // fill up for { block := dtest.RandomL2BlockWithChainId(rng, 4, defaultTestRollupConfig.L2ChainID) - _, err := cb.AddBlock(block) + _, err := cb.AddBlock(&defaultTestRollupConfig, block) if cb.IsFull() { break } @@ -823,7 +823,7 @@ func ChannelBuilder_InputBytes(t *testing.T, batchType uint) { if batchType == derive.SingularBatchType { l += blockBatchRlpSize(t, block) } else { - singularBatch, l1Info, err := derive.BlockToSingularBatch(block) + singularBatch, l1Info, err := derive.BlockToSingularBatch(&defaultTestRollupConfig, block) require.NoError(err) spanBatchBuilder.AppendSingularBatch(singularBatch, l1Info.SequenceNumber) rawSpanBatch, err := spanBatchBuilder.GetRawSpanBatch() @@ -833,7 +833,7 @@ func ChannelBuilder_InputBytes(t *testing.T, batchType uint) { require.NoError(batch.EncodeRLP(&buf)) l = buf.Len() } - _, err := cb.AddBlock(block) + _, err := cb.AddBlock(&defaultTestRollupConfig, block) require.NoError(err) require.Equal(cb.InputBytes(), l) } @@ -855,7 +855,7 @@ func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) { for { block := dtest.RandomL2BlockWithChainId(rng, rng.Intn(32), defaultTestRollupConfig.L2ChainID) - _, err := cb.AddBlock(block) + _, err := cb.AddBlock(&defaultTestRollupConfig, block) if errors.Is(err, derive.CompressorFullErr) { break } @@ -877,7 +877,7 @@ func ChannelBuilder_OutputBytes(t *testing.T, batchType uint) { func blockBatchRlpSize(t *testing.T, b *types.Block) int { t.Helper() - singularBatch, _, err := derive.BlockToSingularBatch(b) + singularBatch, _, err := derive.BlockToSingularBatch(&defaultTestRollupConfig, b) batch := derive.NewBatchData(singularBatch) require.NoError(t, err) var buf bytes.Buffer diff --git a/op-batcher/batcher/channel_manager.go b/op-batcher/batcher/channel_manager.go index 231b17627b0b..585966506363 100644 --- a/op-batcher/batcher/channel_manager.go +++ b/op-batcher/batcher/channel_manager.go @@ -235,7 +235,7 @@ func (s *channelManager) processBlocks() error { latestL2ref eth.L2BlockRef ) for i, block := range s.blocks { - l1info, err := s.currentChannel.AddBlock(block) + l1info, err := s.currentChannel.AddBlock(s.rcfg, block) if errors.As(err, &_chFullErr) { // current block didn't get added because channel is already full break @@ -327,7 +327,7 @@ func (s *channelManager) AddL2Block(block *types.Block) error { return nil } -func l2BlockRefFromBlockAndL1Info(block *types.Block, l1info derive.L1BlockInfo) eth.L2BlockRef { +func l2BlockRefFromBlockAndL1Info(block *types.Block, l1info *derive.L1BlockInfo) eth.L2BlockRef { return eth.L2BlockRef{ Hash: block.Hash(), Number: block.NumberU64(), diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 671c2743cc15..919854c51999 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -171,7 +171,7 @@ func (l *BatchSubmitter) loadBlocksIntoState(ctx context.Context) error { latestBlock = block } - l2ref, err := derive.L2BlockToBlockRef(latestBlock, &l.RollupConfig.Genesis) + l2ref, err := derive.L2BlockToBlockRef(l.RollupConfig, latestBlock) if err != nil { l.Log.Warn("Invalid L2 block loaded into state", "err", err) return err diff --git a/op-e2e/actions/garbage_channel_out.go b/op-e2e/actions/garbage_channel_out.go index 34056abdf714..b2888d28b470 100644 --- a/op-e2e/actions/garbage_channel_out.go +++ b/op-e2e/actions/garbage_channel_out.go @@ -54,7 +54,7 @@ type Writer interface { type ChannelOutIface interface { ID() derive.ChannelID Reset() error - AddBlock(block *types.Block) (uint64, error) + AddBlock(rollupCfg *rollup.Config, block *types.Block) (uint64, error) ReadyBytes() int Flush() error Close() error @@ -138,11 +138,11 @@ func (co *GarbageChannelOut) Reset() error { // error that it returns is ErrTooManyRLPBytes. If this error // is returned, the channel should be closed and a new one // should be made. -func (co *GarbageChannelOut) AddBlock(block *types.Block) (uint64, error) { +func (co *GarbageChannelOut) AddBlock(rollupCfg *rollup.Config, block *types.Block) (uint64, error) { if co.closed { return 0, errors.New("already closed") } - batch, err := blockToBatch(block) + batch, err := blockToBatch(rollupCfg, block) if err != nil { return 0, err } @@ -234,7 +234,7 @@ func (co *GarbageChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint1 } // blockToBatch transforms a block into a batch object that can easily be RLP encoded. -func blockToBatch(block *types.Block) (*derive.BatchData, error) { +func blockToBatch(rollupCfg *rollup.Config, block *types.Block) (*derive.BatchData, error) { opaqueTxs := make([]hexutil.Bytes, 0, len(block.Transactions())) for i, tx := range block.Transactions() { if tx.Type() == types.DepositTxType { @@ -250,7 +250,7 @@ func blockToBatch(block *types.Block) (*derive.BatchData, error) { if l1InfoTx.Type() != types.DepositTxType { return nil, derive.ErrNotDepositTx } - l1Info, err := derive.L1InfoDepositTxData(l1InfoTx.Data()) + l1Info, err := derive.L1BlockInfoFromBytes(rollupCfg, block.Time(), l1InfoTx.Data()) if err != nil { return nil, fmt.Errorf("could not parse the L1 Info deposit: %w", err) } diff --git a/op-e2e/actions/l2_batcher.go b/op-e2e/actions/l2_batcher.go index f76684d65828..66e3b9ea2182 100644 --- a/op-e2e/actions/l2_batcher.go +++ b/op-e2e/actions/l2_batcher.go @@ -175,7 +175,7 @@ func (s *L2Batcher) Buffer(t Testing) error { require.NoError(t, err, "failed to create channel") s.l2ChannelOut = ch } - if _, err := s.l2ChannelOut.AddBlock(block); err != nil { // should always succeed + if _, err := s.l2ChannelOut.AddBlock(s.rollupCfg, block); err != nil { // should always succeed return err } ref, err := s.engCl.L2BlockRefByHash(t.Ctx(), block.Hash()) diff --git a/op-e2e/actions/sync_test.go b/op-e2e/actions/sync_test.go index d23ccc6babb5..09927c3442f7 100644 --- a/op-e2e/actions/sync_test.go +++ b/op-e2e/actions/sync_test.go @@ -246,7 +246,7 @@ func TestInvalidPayloadInSpanBatch(gt *testing.T) { block = block.WithBody([]*types.Transaction{block.Transactions()[0], invalidTx}, []*types.Header{}) } // Add A1 ~ A12 into the channel - _, err = channelOut.AddBlock(block) + _, err = channelOut.AddBlock(sd.RollupCfg, block) require.NoError(t, err) } @@ -304,7 +304,7 @@ func TestInvalidPayloadInSpanBatch(gt *testing.T) { block = block.WithBody([]*types.Transaction{block.Transactions()[0], tx}, []*types.Header{}) } // Add B1, A2 ~ A12 into the channel - _, err = channelOut.AddBlock(block) + _, err = channelOut.AddBlock(sd.RollupCfg, block) require.NoError(t, err) } // Submit span batch(B1, A2, ... A12) diff --git a/op-e2e/actions/system_config_test.go b/op-e2e/actions/system_config_test.go index 77a1eae05dbf..d46e45373140 100644 --- a/op-e2e/actions/system_config_test.go +++ b/op-e2e/actions/system_config_test.go @@ -137,7 +137,7 @@ func BatcherKeyRotation(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { for i := 0; i <= 12; i++ { payload, err := engCl.PayloadByNumber(t.Ctx(), sequencer.L2Safe().Number+uint64(i)) require.NoError(t, err) - ref, err := derive.PayloadToBlockRef(payload, &sd.RollupCfg.Genesis) + ref, err := derive.PayloadToBlockRef(sd.RollupCfg, payload) require.NoError(t, err) if i < 6 { require.Equal(t, ref.L1Origin.Number, cfgChangeL1BlockNum-2) @@ -148,7 +148,7 @@ func BatcherKeyRotation(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { } else { require.Equal(t, ref.L1Origin.Number, cfgChangeL1BlockNum) require.Equal(t, ref.SequenceNumber, uint64(0), "first L2 block with this origin") - sysCfg, err := derive.PayloadToSystemConfig(payload, sd.RollupCfg) + sysCfg, err := derive.PayloadToSystemConfig(sd.RollupCfg, payload) require.NoError(t, err) require.Equal(t, dp.Addresses.Bob, sysCfg.BatcherAddr, "bob should be batcher now") } @@ -307,7 +307,7 @@ func GPOParamsChange(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { engCl := seqEngine.EngineClient(t, sd.RollupCfg) payload, err := engCl.PayloadByLabel(t.Ctx(), eth.Unsafe) require.NoError(t, err) - sysCfg, err := derive.PayloadToSystemConfig(payload, sd.RollupCfg) + sysCfg, err := derive.PayloadToSystemConfig(sd.RollupCfg, payload) require.NoError(t, err) require.Equal(t, sd.RollupCfg.Genesis.SystemConfig, sysCfg, "still have genesis system config before we adopt the L1 block with GPO change") @@ -320,7 +320,7 @@ func GPOParamsChange(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { payload, err = engCl.PayloadByLabel(t.Ctx(), eth.Unsafe) require.NoError(t, err) - sysCfg, err = derive.PayloadToSystemConfig(payload, sd.RollupCfg) + sysCfg, err = derive.PayloadToSystemConfig(sd.RollupCfg, payload) require.NoError(t, err) require.Equal(t, eth.Bytes32(common.BigToHash(big.NewInt(1000))), sysCfg.Overhead, "overhead changed") require.Equal(t, eth.Bytes32(common.BigToHash(big.NewInt(2_300_000))), sysCfg.Scalar, "scalar changed") diff --git a/op-e2e/e2eutils/geth/wait.go b/op-e2e/e2eutils/geth/wait.go index 28f2398626c9..76398178ac0f 100644 --- a/op-e2e/e2eutils/geth/wait.go +++ b/op-e2e/e2eutils/geth/wait.go @@ -7,6 +7,7 @@ import ( "math/big" "time" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" @@ -20,7 +21,7 @@ var ( errTimeout = errors.New("timeout") ) -func WaitForL1OriginOnL2(l1BlockNum uint64, client *ethclient.Client, timeout time.Duration) (*types.Block, error) { +func WaitForL1OriginOnL2(rollupCfg *rollup.Config, l1BlockNum uint64, client *ethclient.Client, timeout time.Duration) (*types.Block, error) { timeoutCh := time.After(timeout) ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() @@ -39,7 +40,7 @@ func WaitForL1OriginOnL2(l1BlockNum uint64, client *ethclient.Client, timeout ti if err != nil { return nil, err } - l1Info, err := derive.L1InfoDepositTxData(block.Transactions()[0].Data()) + l1Info, err := derive.L1BlockInfoFromBytes(rollupCfg, block.Time(), block.Transactions()[0].Data()) if err != nil { return nil, err } diff --git a/op-e2e/op_geth.go b/op-e2e/op_geth.go index bd2ddfb0ff8f..4e73f7d2499d 100644 --- a/op-e2e/op_geth.go +++ b/op-e2e/op_geth.go @@ -92,14 +92,17 @@ func NewOpGeth(t *testing.T, ctx context.Context, cfg *SystemConfig) (*OpGeth, e auth := rpc.WithHTTPAuth(gn.NewJWTAuth(cfg.JWTSecret)) l2Node, err := client.NewRPC(ctx, logger, node.WSAuthEndpoint(), client.WithGethRPCOptions(auth)) - require.Nil(t, err) + require.NoError(t, err) // Finally create the engine client + rollupCfg, err := cfg.DeployConfig.RollupConfig(l1Block, l2GenesisBlock.Hash(), l2GenesisBlock.NumberU64()) + require.NoError(t, err) + rollupCfg.Genesis = rollupGenesis l2Engine, err := sources.NewEngineClient( l2Node, logger, nil, - sources.EngineClientDefaultConfig(&rollup.Config{Genesis: rollupGenesis}), + sources.EngineClientDefaultConfig(rollupCfg), ) require.Nil(t, err) @@ -198,8 +201,7 @@ func (d *OpGeth) StartBlockBuilding(ctx context.Context, attrs *eth.PayloadAttri // CreatePayloadAttributes creates a valid PayloadAttributes containing a L1Info deposit transaction followed by the supplied transactions. func (d *OpGeth) CreatePayloadAttributes(txs ...*types.Transaction) (*eth.PayloadAttributes, error) { timestamp := d.L2Head.Timestamp + 2 - regolith := d.L2ChainConfig.IsRegolith(uint64(timestamp)) - l1Info, err := derive.L1InfoDepositBytes(d.sequenceNum, d.L1Head, d.SystemConfig, regolith) + l1Info, err := derive.L1InfoDepositBytes(d.l2Engine.RollupConfig(), d.SystemConfig, d.sequenceNum, d.L1Head, uint64(timestamp)) if err != nil { return nil, err } diff --git a/op-e2e/op_geth_test.go b/op-e2e/op_geth_test.go index d7dd5c9f0dee..9e7006d877d2 100644 --- a/op-e2e/op_geth_test.go +++ b/op-e2e/op_geth_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum/go-ethereum" @@ -393,7 +394,8 @@ func TestPreregolith(t *testing.T) { require.NoError(t, err) defer opGeth.Close() - systemTx, err := derive.L1InfoDeposit(1, opGeth.L1Head, opGeth.SystemConfig, false) + rollupCfg := rollup.Config{} + systemTx, err := derive.L1InfoDeposit(&rollupCfg, opGeth.SystemConfig, 1, opGeth.L1Head, 0) systemTx.IsSystemTransaction = true require.NoError(t, err) @@ -589,7 +591,8 @@ func TestRegolith(t *testing.T) { test.activateRegolith(ctx, opGeth) - systemTx, err := derive.L1InfoDeposit(1, opGeth.L1Head, opGeth.SystemConfig, false) + rollupCfg := rollup.Config{} + systemTx, err := derive.L1InfoDeposit(&rollupCfg, opGeth.SystemConfig, 1, opGeth.L1Head, 0) systemTx.IsSystemTransaction = true require.NoError(t, err) diff --git a/op-e2e/system_fpp_test.go b/op-e2e/system_fpp_test.go index 0c9a6bbc0222..20bee732fa96 100644 --- a/op-e2e/system_fpp_test.go +++ b/op-e2e/system_fpp_test.go @@ -124,7 +124,7 @@ func testVerifyL2OutputRootEmptyBlock(t *testing.T, detached bool, spanBatchActi t.Log("Wait for sequencer to catch up with last submitted batch") l1HeadNum, err := l1Client.BlockNumber(ctx) require.NoError(t, err) - _, err = geth.WaitForL1OriginOnL2(l1HeadNum, l2Seq, 30*time.Second) + _, err = geth.WaitForL1OriginOnL2(sys.RollupConfig, l1HeadNum, l2Seq, 30*time.Second) require.NoError(t, err) // Get the current safe head now that the batcher is stopped diff --git a/op-e2e/system_test.go b/op-e2e/system_test.go index bbfb7c8d6ef1..6e99157b7aa5 100644 --- a/op-e2e/system_test.go +++ b/op-e2e/system_test.go @@ -202,7 +202,7 @@ func TestSystemE2EDencunAtGenesisWithBlobs(t *testing.T) { require.Nil(t, err, "Waiting for blob tx on L1") // end sending blob-containing txns on l1 l2Client := sys.Clients["sequencer"] - finalizedBlock, err := gethutils.WaitForL1OriginOnL2(blockContainsBlob.BlockNumber.Uint64(), l2Client, 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) + finalizedBlock, err := gethutils.WaitForL1OriginOnL2(sys.RollupConfig, blockContainsBlob.BlockNumber.Uint64(), l2Client, 30*time.Duration(cfg.DeployConfig.L1BlockTime)*time.Second) require.Nil(t, err, "Waiting for L1 origin of blob tx on L2") finalizationTimeout := 30 * time.Duration(cfg.DeployConfig.L1BlockTime) * time.Second _, err = gethutils.WaitForBlockToBeSafe(finalizedBlock.Header().Number, l2Client, finalizationTimeout) @@ -326,11 +326,11 @@ func TestConfirmationDepth(t *testing.T) { l2VerHead, err := l2Verif.BlockByNumber(ctx, nil) require.NoError(t, err) - seqInfo, err := derive.L1InfoDepositTxData(l2SeqHead.Transactions()[0].Data()) + seqInfo, err := derive.L1BlockInfoFromBytes(sys.RollupConfig, l2SeqHead.Time(), l2SeqHead.Transactions()[0].Data()) require.NoError(t, err) require.LessOrEqual(t, seqInfo.Number+seqConfDepth, l1Head.NumberU64(), "the seq L2 head block should have an origin older than the L1 head block by at least the sequencer conf depth") - verInfo, err := derive.L1InfoDepositTxData(l2VerHead.Transactions()[0].Data()) + verInfo, err := derive.L1BlockInfoFromBytes(sys.RollupConfig, l2VerHead.Time(), l2VerHead.Transactions()[0].Data()) require.NoError(t, err) require.LessOrEqual(t, verInfo.Number+verConfDepth, l1Head.NumberU64(), "the ver L2 head block should have an origin older than the L1 head block by at least the verifier conf depth") } @@ -469,9 +469,9 @@ func TestMissingBatchE2E(t *testing.T) { } } -func L1InfoFromState(ctx context.Context, contract *bindings.L1Block, l2Number *big.Int) (derive.L1BlockInfo, error) { +func L1InfoFromState(ctx context.Context, contract *bindings.L1Block, l2Number *big.Int) (*derive.L1BlockInfo, error) { var err error - var out derive.L1BlockInfo + var out = &derive.L1BlockInfo{} opts := bind.CallOpts{ BlockNumber: l2Number, Context: ctx, @@ -479,45 +479,45 @@ func L1InfoFromState(ctx context.Context, contract *bindings.L1Block, l2Number * out.Number, err = contract.Number(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get number: %w", err) + return nil, fmt.Errorf("failed to get number: %w", err) } out.Time, err = contract.Timestamp(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get timestamp: %w", err) + return nil, fmt.Errorf("failed to get timestamp: %w", err) } - out.BaseFee, err = contract.Basefee(&opts) + out.Basefee, err = contract.Basefee(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get timestamp: %w", err) + return nil, fmt.Errorf("failed to get timestamp: %w", err) } blockHashBytes, err := contract.Hash(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get block hash: %w", err) + return nil, fmt.Errorf("failed to get block hash: %w", err) } out.BlockHash = common.BytesToHash(blockHashBytes[:]) out.SequenceNumber, err = contract.SequenceNumber(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get sequence number: %w", err) + return nil, fmt.Errorf("failed to get sequence number: %w", err) } overhead, err := contract.L1FeeOverhead(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get l1 fee overhead: %w", err) + return nil, fmt.Errorf("failed to get l1 fee overhead: %w", err) } out.L1FeeOverhead = eth.Bytes32(common.BigToHash(overhead)) scalar, err := contract.L1FeeScalar(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get l1 fee scalar: %w", err) + return nil, fmt.Errorf("failed to get l1 fee scalar: %w", err) } out.L1FeeScalar = eth.Bytes32(common.BigToHash(scalar)) batcherHash, err := contract.BatcherHash(&opts) if err != nil { - return derive.L1BlockInfo{}, fmt.Errorf("failed to get batch sender: %w", err) + return nil, fmt.Errorf("failed to get batch sender: %w", err) } out.BatcherAddr = common.BytesToAddress(batcherHash[:]) @@ -871,11 +871,12 @@ func TestL1InfoContract(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() - fillInfoLists := func(start *types.Block, contract *bindings.L1Block, client *ethclient.Client) ([]derive.L1BlockInfo, []derive.L1BlockInfo) { - var txList, stateList []derive.L1BlockInfo + fillInfoLists := func(start *types.Block, contract *bindings.L1Block, client *ethclient.Client) ([]*derive.L1BlockInfo, []*derive.L1BlockInfo) { + var txList, stateList []*derive.L1BlockInfo for b := start; ; { - var infoFromTx derive.L1BlockInfo - require.NoError(t, infoFromTx.UnmarshalBinary(b.Transactions()[0].Data())) + var infoFromTx *derive.L1BlockInfo + infoFromTx, err := derive.L1BlockInfoFromBytes(sys.RollupConfig, b.Time(), b.Transactions()[0].Data()) + require.NoError(t, err) txList = append(txList, infoFromTx) infoFromState, err := L1InfoFromState(ctx, contract, b.Number()) @@ -894,16 +895,16 @@ func TestL1InfoContract(t *testing.T) { l1InfosFromSequencerTransactions, l1InfosFromSequencerState := fillInfoLists(endSeqBlock, seqL1Info, l2Seq) l1InfosFromVerifierTransactions, l1InfosFromVerifierState := fillInfoLists(endVerifBlock, verifL1Info, l2Verif) - l1blocks := make(map[common.Hash]derive.L1BlockInfo) + l1blocks := make(map[common.Hash]*derive.L1BlockInfo) maxL1Hash := l1InfosFromSequencerTransactions[0].BlockHash for h := maxL1Hash; ; { b, err := l1Client.BlockByHash(ctx, h) require.Nil(t, err) - l1blocks[h] = derive.L1BlockInfo{ + l1blocks[h] = &derive.L1BlockInfo{ Number: b.NumberU64(), Time: b.Time(), - BaseFee: b.BaseFee(), + Basefee: b.BaseFee(), BlockHash: h, SequenceNumber: 0, // ignored, will be overwritten BatcherAddr: sys.RollupConfig.Genesis.SystemConfig.BatcherAddr, @@ -917,7 +918,7 @@ func TestL1InfoContract(t *testing.T) { } } - checkInfoList := func(name string, list []derive.L1BlockInfo) { + checkInfoList := func(name string, list []*derive.L1BlockInfo) { for _, info := range list { if expected, ok := l1blocks[info.BlockHash]; ok { expected.SequenceNumber = info.SequenceNumber // the seq nr is not part of the L1 info we know in advance, so we ignore it. @@ -1198,7 +1199,7 @@ func TestFees(t *testing.T) { bytes, err := tx.MarshalBinary() require.Nil(t, err) - l1Fee := l1CostFn(receipt.BlockNumber.Uint64(), header.Time, tx.RollupDataGas(), tx.IsSystemTx()) + l1Fee := l1CostFn(tx.RollupCostData(), header.Time) require.Equalf(t, l1Fee, l1FeeRecipientDiff, "L1 fee mismatch: start balance %v, end balance %v", l1FeeRecipientStartBalance, l1FeeRecipientEndBalance) gpoL1Fee, err := gpoContract.GetL1Fee(&bind.CallOpts{}, bytes) diff --git a/op-e2e/system_tob_test.go b/op-e2e/system_tob_test.go index 303c0ff8da11..c79810a9e1d9 100644 --- a/op-e2e/system_tob_test.go +++ b/op-e2e/system_tob_test.go @@ -75,7 +75,7 @@ func TestGasPriceOracleFeeUpdates(t *testing.T) { require.Nil(t, err, "waiting for sysconfig set gas config update tx") require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful, "transaction failed") - _, err = geth.WaitForL1OriginOnL2(receipt.BlockNumber.Uint64(), l2Seq, txTimeoutDuration) + _, err = geth.WaitForL1OriginOnL2(sys.RollupConfig, receipt.BlockNumber.Uint64(), l2Seq, txTimeoutDuration) require.NoError(t, err, "waiting for L2 block to include the sysconfig update") gpoOverhead, err := gpoContract.Overhead(&bind.CallOpts{}) @@ -102,7 +102,7 @@ func TestGasPriceOracleFeeUpdates(t *testing.T) { require.Nil(t, err, "waiting for sysconfig set gas config update tx") require.Equal(t, receipt.Status, types.ReceiptStatusSuccessful, "transaction failed") - _, err = geth.WaitForL1OriginOnL2(receipt.BlockNumber.Uint64(), l2Seq, txTimeoutDuration) + _, err = geth.WaitForL1OriginOnL2(sys.RollupConfig, receipt.BlockNumber.Uint64(), l2Seq, txTimeoutDuration) require.NoError(t, err, "waiting for L2 block to include the sysconfig update") gpoOverhead, err = gpoContract.Overhead(&bind.CallOpts{}) diff --git a/op-node/rollup/derive/attributes.go b/op-node/rollup/derive/attributes.go index b65f680952f3..88a1793de9f5 100644 --- a/op-node/rollup/derive/attributes.go +++ b/op-node/rollup/derive/attributes.go @@ -100,7 +100,7 @@ func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Contex l2Parent, nextL2Time, eth.ToBlockID(l1Info), l1Info.Time())) } - l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig, ba.cfg.IsRegolith(nextL2Time)) + l1InfoTx, err := L1InfoDepositBytes(ba.cfg, sysConfig, seqNumber, l1Info, nextL2Time) if err != nil { return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err)) } diff --git a/op-node/rollup/derive/attributes_queue_test.go b/op-node/rollup/derive/attributes_queue_test.go index c5b2cde5eca6..702ff1296942 100644 --- a/op-node/rollup/derive/attributes_queue_test.go +++ b/op-node/rollup/derive/attributes_queue_test.go @@ -66,7 +66,8 @@ func TestAttributesQueue(t *testing.T) { l2Fetcher := &testutils.MockL2Client{} l2Fetcher.ExpectSystemConfigByL2Hash(safeHead.Hash, parentL1Cfg, nil) - l1InfoTx, err := L1InfoDepositBytes(safeHead.SequenceNumber+1, l1Info, expectedL1Cfg, false) + rollupCfg := rollup.Config{} + l1InfoTx, err := L1InfoDepositBytes(&rollupCfg, expectedL1Cfg, safeHead.SequenceNumber+1, l1Info, 0) require.NoError(t, err) attrs := eth.PayloadAttributes{ Timestamp: eth.Uint64Quantity(safeHead.Time + cfg.BlockTime), diff --git a/op-node/rollup/derive/attributes_test.go b/op-node/rollup/derive/attributes_test.go index bdfbd97cd6c6..b6c3e80737e0 100644 --- a/op-node/rollup/derive/attributes_test.go +++ b/op-node/rollup/derive/attributes_test.go @@ -113,7 +113,7 @@ func TestPreparePayloadAttributes(t *testing.T) { l1Info.InfoParentHash = l2Parent.L1Origin.Hash l1Info.InfoNum = l2Parent.L1Origin.Number + 1 epoch := l1Info.ID() - l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, false) + l1InfoTx, err := L1InfoDepositBytes(cfg, testSysCfg, 0, l1Info, 0) require.NoError(t, err) l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil) attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher) @@ -150,7 +150,7 @@ func TestPreparePayloadAttributes(t *testing.T) { require.NoError(t, err) epoch := l1Info.ID() - l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, false) + l1InfoTx, err := L1InfoDepositBytes(cfg, testSysCfg, 0, l1Info, 0) require.NoError(t, err) l2Txs := append(append(make([]eth.Data, 0), l1InfoTx), usedDepositTxs...) @@ -180,7 +180,7 @@ func TestPreparePayloadAttributes(t *testing.T) { l1Info.InfoNum = l2Parent.L1Origin.Number epoch := l1Info.ID() - l1InfoTx, err := L1InfoDepositBytes(l2Parent.SequenceNumber+1, l1Info, testSysCfg, false) + l1InfoTx, err := L1InfoDepositBytes(cfg, testSysCfg, l2Parent.SequenceNumber+1, l1Info, 0) require.NoError(t, err) l1Fetcher.ExpectInfoByHash(epoch.Hash, l1Info, nil) @@ -232,7 +232,11 @@ func TestPreparePayloadAttributes(t *testing.T) { l1Info.InfoTime = tc.l1Time epoch := l1Info.ID() - l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, tc.regolith) + time := tc.regolithTime + if !tc.regolith { + time-- + } + l1InfoTx, err := L1InfoDepositBytes(cfg, testSysCfg, 0, l1Info, time) require.NoError(t, err) l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil) attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher) diff --git a/op-node/rollup/derive/batch_queue_test.go b/op-node/rollup/derive/batch_queue_test.go index 68bd37aa3863..d7da2b8170fb 100644 --- a/op-node/rollup/derive/batch_queue_test.go +++ b/op-node/rollup/derive/batch_queue_test.go @@ -84,9 +84,9 @@ func getDeltaTime(batchType int) *uint64 { func l1InfoDepositTx(t *testing.T, l1BlockNum uint64) hexutil.Bytes { l1Info := L1BlockInfo{ Number: l1BlockNum, - BaseFee: big.NewInt(0), + Basefee: big.NewInt(0), } - infoData, err := l1Info.MarshalBinary() + infoData, err := l1Info.marshalBinaryBedrock() require.NoError(t, err) depositTx := &types.DepositTx{ Data: infoData, diff --git a/op-node/rollup/derive/batches.go b/op-node/rollup/derive/batches.go index 1ea52f28b929..920836c30dea 100644 --- a/op-node/rollup/derive/batches.go +++ b/op-node/rollup/derive/batches.go @@ -363,7 +363,7 @@ func checkSpanBatch(ctx context.Context, cfg *rollup.Config, log log.Logger, l1B return BatchDrop } } - safeBlockRef, err := PayloadToBlockRef(safeBlockPayload, &cfg.Genesis) + safeBlockRef, err := PayloadToBlockRef(cfg, safeBlockPayload) if err != nil { log.Error("failed to extract L2BlockRef from execution payload", "hash", safeBlockPayload.BlockHash, "err", err) return BatchDrop diff --git a/op-node/rollup/derive/channel_out.go b/op-node/rollup/derive/channel_out.go index 02d0881602c1..ab94986fb8f3 100644 --- a/op-node/rollup/derive/channel_out.go +++ b/op-node/rollup/derive/channel_out.go @@ -52,7 +52,7 @@ type Compressor interface { type ChannelOut interface { ID() ChannelID Reset() error - AddBlock(*types.Block) (uint64, error) + AddBlock(*rollup.Config, *types.Block) (uint64, error) AddSingularBatch(*SingularBatch, uint64) (uint64, error) InputBytes() int ReadyBytes() int @@ -118,12 +118,12 @@ func (co *SingularChannelOut) Reset() error { // and an error if there is a problem adding the block. The only sentinel error // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel // should be closed and a new one should be made. -func (co *SingularChannelOut) AddBlock(block *types.Block) (uint64, error) { +func (co *SingularChannelOut) AddBlock(rollupCfg *rollup.Config, block *types.Block) (uint64, error) { if co.closed { return 0, ErrChannelOutAlreadyClosed } - batch, l1Info, err := BlockToSingularBatch(block) + batch, l1Info, err := BlockToSingularBatch(rollupCfg, block) if err != nil { return 0, err } @@ -223,7 +223,7 @@ func (co *SingularChannelOut) OutputFrame(w *bytes.Buffer, maxSize uint64) (uint } // BlockToSingularBatch transforms a block into a batch object that can easily be RLP encoded. -func BlockToSingularBatch(block *types.Block) (*SingularBatch, L1BlockInfo, error) { +func BlockToSingularBatch(rollupCfg *rollup.Config, block *types.Block) (*SingularBatch, *L1BlockInfo, error) { opaqueTxs := make([]hexutil.Bytes, 0, len(block.Transactions())) for i, tx := range block.Transactions() { if tx.Type() == types.DepositTxType { @@ -231,18 +231,18 @@ func BlockToSingularBatch(block *types.Block) (*SingularBatch, L1BlockInfo, erro } otx, err := tx.MarshalBinary() if err != nil { - return nil, L1BlockInfo{}, fmt.Errorf("could not encode tx %v in block %v: %w", i, tx.Hash(), err) + return nil, nil, fmt.Errorf("could not encode tx %v in block %v: %w", i, tx.Hash(), err) } opaqueTxs = append(opaqueTxs, otx) } if len(block.Transactions()) == 0 { - return nil, L1BlockInfo{}, fmt.Errorf("block %v has no transactions", block.Hash()) + return nil, nil, fmt.Errorf("block %v has no transactions", block.Hash()) } l1InfoTx := block.Transactions()[0] if l1InfoTx.Type() != types.DepositTxType { - return nil, L1BlockInfo{}, ErrNotDepositTx + return nil, nil, ErrNotDepositTx } - l1Info, err := L1InfoDepositTxData(l1InfoTx.Data()) + l1Info, err := L1BlockInfoFromBytes(rollupCfg, block.Time(), l1InfoTx.Data()) if err != nil { return nil, l1Info, fmt.Errorf("could not parse the L1 Info deposit: %w", err) } diff --git a/op-node/rollup/derive/channel_out_test.go b/op-node/rollup/derive/channel_out_test.go index 0eef4a241c1b..a46a7a333397 100644 --- a/op-node/rollup/derive/channel_out_test.go +++ b/op-node/rollup/derive/channel_out_test.go @@ -9,6 +9,12 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-node/rollup" +) + +var ( + rollupCfg rollup.Config ) // basic implementation of the Compressor interface that does no compression @@ -40,7 +46,7 @@ func TestChannelOutAddBlock(t *testing.T) { }, nil, ) - _, err := cout.AddBlock(block) + _, err := cout.AddBlock(&rollupCfg, block) require.Error(t, err) require.Equal(t, ErrNotDepositTx, err) }) @@ -152,6 +158,6 @@ func TestForceCloseTxData(t *testing.T) { func TestBlockToBatchValidity(t *testing.T) { block := new(types.Block) - _, _, err := BlockToSingularBatch(block) + _, _, err := BlockToSingularBatch(&rollupCfg, block) require.ErrorContains(t, err, "has no transactions") } diff --git a/op-node/rollup/derive/engine_consolidate.go b/op-node/rollup/derive/engine_consolidate.go index ecac3684e194..fd6677f7617f 100644 --- a/op-node/rollup/derive/engine_consolidate.go +++ b/op-node/rollup/derive/engine_consolidate.go @@ -9,12 +9,13 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" ) // AttributesMatchBlock checks if the L2 attributes pre-inputs match the output // nil if it is a match. If err is not nil, the error contains the reason for the mismatch -func AttributesMatchBlock(attrs *eth.PayloadAttributes, parentHash common.Hash, block *eth.ExecutionPayload, l log.Logger) error { +func AttributesMatchBlock(rollupCfg *rollup.Config, attrs *eth.PayloadAttributes, parentHash common.Hash, block *eth.ExecutionPayload, l log.Logger) error { if parentHash != block.ParentHash { return fmt.Errorf("parent hash field does not match. expected: %v. got: %v", parentHash, block.ParentHash) } @@ -30,7 +31,7 @@ func AttributesMatchBlock(attrs *eth.PayloadAttributes, parentHash common.Hash, for i, otx := range attrs.Transactions { if expect := block.Transactions[i]; !bytes.Equal(otx, expect) { if i == 0 { - logL1InfoTxns(l, uint64(block.BlockNumber), uint64(block.Timestamp), otx, block.Transactions[i]) + logL1InfoTxns(rollupCfg, l, uint64(block.BlockNumber), uint64(block.Timestamp), otx, block.Transactions[i]) } return fmt.Errorf("transaction %d does not match. expected: %v. got: %v", i, expect, otx) } @@ -77,7 +78,7 @@ func checkWithdrawalsMatch(attrWithdrawals *types.Withdrawals, blockWithdrawals // logL1InfoTxns reports the values from the L1 info tx when they differ to aid // debugging. This check is the one that has been most frequently triggered. -func logL1InfoTxns(l log.Logger, l2Number, l2Timestamp uint64, safeTx, unsafeTx hexutil.Bytes) { +func logL1InfoTxns(rollupCfg *rollup.Config, l log.Logger, l2Number, l2Timestamp uint64, safeTx, unsafeTx hexutil.Bytes) { // First decode into *types.Transaction to get the tx data. var safeTxValue, unsafeTxValue types.Transaction errSafe := (&safeTxValue).UnmarshalBinary(safeTx) @@ -88,21 +89,34 @@ func logL1InfoTxns(l log.Logger, l2Number, l2Timestamp uint64, safeTx, unsafeTx } // Then decode the ABI encoded parameters - var safeInfo, unsafeInfo L1BlockInfo - errSafe = (&safeInfo).UnmarshalBinary(safeTxValue.Data()) - errUnsafe = (&unsafeInfo).UnmarshalBinary(unsafeTxValue.Data()) + safeInfo, errSafe := L1BlockInfoFromBytes(rollupCfg, l2Timestamp, safeTxValue.Data()) + unsafeInfo, errUnsafe := L1BlockInfoFromBytes(rollupCfg, l2Timestamp, unsafeTxValue.Data()) if errSafe != nil || errUnsafe != nil { l.Error("failed to umarshal l1 info", "errSafe", errSafe, "errUnsafe", errUnsafe) return } - l.Error("L1 Info transaction differs", "number", l2Number, "time", l2Timestamp, - "safe_l1_number", safeInfo.Number, "safe_l1_hash", safeInfo.BlockHash, - "safe_l1_time", safeInfo.Time, "safe_seq_num", safeInfo.SequenceNumber, - "safe_l1_basefee", safeInfo.BaseFee, "safe_batcher_add", safeInfo.BlockHash, - "safe_gpo_scalar", safeInfo.L1FeeScalar, "safe_gpo_overhead", safeInfo.L1FeeOverhead, - "unsafe_l1_number", unsafeInfo.Number, "unsafe_l1_hash", unsafeInfo.BlockHash, - "unsafe_l1_time", unsafeInfo.Time, "unsafe_seq_num", unsafeInfo.SequenceNumber, - "unsafe_l1_basefee", unsafeInfo.BaseFee, "unsafe_batcher_add", unsafeInfo.BlockHash, - "unsafe_gpo_scalar", unsafeInfo.L1FeeScalar, "unsafe_gpo_overhead", unsafeInfo.L1FeeOverhead) + if bytes.HasPrefix(safeTxValue.Data(), types.EcotoneL1AttributesSelector) { + l.Error("L1 Info transaction differs", "number", l2Number, "time", l2Timestamp, + "safe_l1_number", safeInfo.Number, "safe_l1_hash", safeInfo.BlockHash, + "safe_l1_time", safeInfo.Time, "safe_seq_num", safeInfo.SequenceNumber, + "safe_l1_basefee", safeInfo.Basefee, "safe_l1_blob_basefee", safeInfo.BlobBasefee, + "safe_l1_basefee_scalar", safeInfo.BasefeeScalar, "safe_l1_blob_basefee_scalar", safeInfo.BlobBasefeeScalar, + "safe_batcher_add", safeInfo.BlockHash, + "unsafe_l1_number", unsafeInfo.Number, "unsafe_l1_hash", unsafeInfo.BlockHash, + "unsafe_l1_time", unsafeInfo.Time, "unsafe_seq_num", unsafeInfo.SequenceNumber, + "unsafe_l1_basefee", unsafeInfo.Basefee, "unsafe_l1_blob_basefee", unsafeInfo.BlobBasefee, + "unsafe_l1_basefee_scalar", unsafeInfo.BasefeeScalar, "unsafe_l1_blob_basefee_scalar", unsafeInfo.BlobBasefeeScalar, + "unsafe_batcher_add", unsafeInfo.BlockHash) + } else { + l.Error("L1 Info transaction differs", "number", l2Number, "time", l2Timestamp, + "safe_l1_number", safeInfo.Number, "safe_l1_hash", safeInfo.BlockHash, + "safe_l1_time", safeInfo.Time, "safe_seq_num", safeInfo.SequenceNumber, + "safe_l1_basefee", safeInfo.Basefee, "safe_batcher_add", safeInfo.BlockHash, + "safe_gpo_scalar", safeInfo.L1FeeScalar, "safe_gpo_overhead", safeInfo.L1FeeOverhead, + "unsafe_l1_number", unsafeInfo.Number, "unsafe_l1_hash", unsafeInfo.BlockHash, + "unsafe_l1_time", unsafeInfo.Time, "unsafe_seq_num", unsafeInfo.SequenceNumber, + "unsafe_l1_basefee", unsafeInfo.Basefee, "unsafe_batcher_add", unsafeInfo.BlockHash, + "unsafe_gpo_scalar", unsafeInfo.L1FeeScalar, "unsafe_gpo_overhead", unsafeInfo.L1FeeOverhead) + } } diff --git a/op-node/rollup/derive/engine_controller.go b/op-node/rollup/derive/engine_controller.go index f87a9f664e8b..15406d28bdd2 100644 --- a/op-node/rollup/derive/engine_controller.go +++ b/op-node/rollup/derive/engine_controller.go @@ -20,10 +20,10 @@ type ExecEngine interface { } type EngineController struct { - engine ExecEngine // Underlying execution engine RPC - log log.Logger - metrics Metrics - genesis *rollup.Genesis + engine ExecEngine // Underlying execution engine RPC + log log.Logger + metrics Metrics + rollupCfg *rollup.Config // Block Head State syncTarget eth.L2BlockRef @@ -39,12 +39,12 @@ type EngineController struct { safeAttrs *AttributesWithParent } -func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, genesis rollup.Genesis) *EngineController { +func NewEngineController(engine ExecEngine, log log.Logger, metrics Metrics, rollupCfg *rollup.Config) *EngineController { return &EngineController{ - engine: engine, - log: log, - metrics: metrics, - genesis: &genesis, + engine: engine, + log: log, + metrics: metrics, + rollupCfg: rollupCfg, } } @@ -159,7 +159,7 @@ func (e *EngineController) ConfirmPayload(ctx context.Context) (out *eth.Executi if err != nil { return nil, errTyp, fmt.Errorf("failed to complete building on top of L2 chain %s, id: %s, error (%d): %w", e.buildingOnto, e.buildingID, errTyp, err) } - ref, err := PayloadToBlockRef(payload, e.genesis) + ref, err := PayloadToBlockRef(e.rollupCfg, payload) if err != nil { return nil, BlockInsertPayloadErr, NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err)) } diff --git a/op-node/rollup/derive/engine_queue.go b/op-node/rollup/derive/engine_queue.go index ca0eeac22648..5f45c8a9d846 100644 --- a/op-node/rollup/derive/engine_queue.go +++ b/op-node/rollup/derive/engine_queue.go @@ -167,7 +167,7 @@ func NewEngineQueue(log log.Logger, cfg *rollup.Config, engine Engine, metrics M return &EngineQueue{ log: log, cfg: cfg, - ec: NewEngineController(engine, log, metrics, cfg.Genesis), + ec: NewEngineController(engine, log, metrics, cfg), engine: engine, metrics: metrics, finalityData: make([]FinalityData, 0, finalityLookback), @@ -501,7 +501,7 @@ func (eq *EngineQueue) tryNextUnsafePayload(ctx context.Context) error { return io.EOF // time to go to next stage if we cannot process the first unsafe payload } - ref, err := PayloadToBlockRef(first, &eq.cfg.Genesis) + ref, err := PayloadToBlockRef(eq.cfg, first) if err != nil { eq.log.Error("failed to decode L2 block ref from payload", "err", err) eq.unsafePayloads.Pop() @@ -604,12 +604,12 @@ func (eq *EngineQueue) consolidateNextSafeAttributes(ctx context.Context) error } return NewTemporaryError(fmt.Errorf("failed to get existing unsafe payload to compare against derived attributes from L1: %w", err)) } - if err := AttributesMatchBlock(eq.safeAttributes.attributes, eq.ec.PendingSafeL2Head().Hash, payload, eq.log); err != nil { + if err := AttributesMatchBlock(eq.cfg, eq.safeAttributes.attributes, eq.ec.PendingSafeL2Head().Hash, payload, eq.log); err != nil { eq.log.Warn("L2 reorg: existing unsafe block does not match derived attributes from L1", "err", err, "unsafe", eq.ec.UnsafeL2Head(), "pending_safe", eq.ec.PendingSafeL2Head(), "safe", eq.ec.SafeL2Head()) // geth cannot wind back a chain without reorging to a new, previously non-canonical, block return eq.forceNextSafeAttributes(ctx) } - ref, err := PayloadToBlockRef(payload, &eq.cfg.Genesis) + ref, err := PayloadToBlockRef(eq.cfg, payload) if err != nil { return NewResetError(fmt.Errorf("failed to decode L2 block ref from payload: %w", err)) } @@ -768,7 +768,7 @@ func (eq *EngineQueue) Reset(ctx context.Context, _ eth.L1BlockRef, _ eth.System // UnsafeL2SyncTarget retrieves the first queued-up L2 unsafe payload, or a zeroed reference if there is none. func (eq *EngineQueue) UnsafeL2SyncTarget() eth.L2BlockRef { if first := eq.unsafePayloads.Peek(); first != nil { - ref, err := PayloadToBlockRef(first, &eq.cfg.Genesis) + ref, err := PayloadToBlockRef(eq.cfg, first) if err != nil { return eth.L2BlockRef{} } diff --git a/op-node/rollup/derive/engine_queue_test.go b/op-node/rollup/derive/engine_queue_test.go index 2da9a174952c..af071bb2ac05 100644 --- a/op-node/rollup/derive/engine_queue_test.go +++ b/op-node/rollup/derive/engine_queue_test.go @@ -947,7 +947,7 @@ func TestBlockBuildingRace(t *testing.T) { require.NotNil(t, eq.safeAttributes, "still have attributes") // Now allow the building to complete - a1InfoTx, err := L1InfoDepositBytes(refA1.SequenceNumber, &testutils.MockBlockInfo{ + a1InfoTx, err := L1InfoDepositBytes(cfg, cfg.Genesis.SystemConfig, refA1.SequenceNumber, &testutils.MockBlockInfo{ InfoHash: refA.Hash, InfoParentHash: refA.ParentHash, InfoCoinbase: common.Address{}, @@ -958,7 +958,7 @@ func TestBlockBuildingRace(t *testing.T) { InfoBaseFee: big.NewInt(7), InfoReceiptRoot: common.Hash{}, InfoGasUsed: 0, - }, cfg.Genesis.SystemConfig, false) + }, 0) require.NoError(t, err) payloadA1 := ð.ExecutionPayload{ diff --git a/op-node/rollup/derive/fuzz_parsers_test.go b/op-node/rollup/derive/fuzz_parsers_test.go index 6e0e9ebe51b6..728997fb005c 100644 --- a/op-node/rollup/derive/fuzz_parsers_test.go +++ b/op-node/rollup/derive/fuzz_parsers_test.go @@ -49,16 +49,45 @@ func FuzzL1InfoRoundTrip(f *testing.F) { in := L1BlockInfo{ Number: number, Time: time, - BaseFee: BytesToBigInt(baseFee), + Basefee: BytesToBigInt(baseFee), BlockHash: common.BytesToHash(hash), SequenceNumber: seqNumber, } - enc, err := in.MarshalBinary() + enc, err := in.marshalBinaryBedrock() if err != nil { t.Fatalf("Failed to marshal binary: %v", err) } var out L1BlockInfo - err = out.UnmarshalBinary(enc) + err = out.unmarshalBinaryBedrock(enc) + if err != nil { + t.Fatalf("Failed to unmarshal binary: %v", err) + } + if !cmp.Equal(in, out, cmp.Comparer(testutils.BigEqual)) { + t.Fatalf("The data did not round trip correctly. in: %v. out: %v", in, out) + } + + }) +} + +// FuzzL1InfoEcotoneRoundTrip checks that our Ecotone encoder round trips properly +func FuzzL1InfoEcotoneRoundTrip(f *testing.F) { + f.Fuzz(func(t *testing.T, number, time uint64, baseFee, blobBasefee, hash []byte, seqNumber uint64, basefeeScalar, blobBasefeeScalar uint32) { + in := L1BlockInfo{ + Number: number, + Time: time, + Basefee: BytesToBigInt(baseFee), + BlockHash: common.BytesToHash(hash), + SequenceNumber: seqNumber, + BlobBasefee: BytesToBigInt(blobBasefee), + BasefeeScalar: basefeeScalar, + BlobBasefeeScalar: blobBasefeeScalar, + } + enc, err := in.marshalBinaryEcotone() + if err != nil { + t.Fatalf("Failed to marshal binary: %v", err) + } + var out L1BlockInfo + err = out.unmarshalBinaryEcotone(enc) if err != nil { t.Fatalf("Failed to unmarshal binary: %v", err) } @@ -71,12 +100,14 @@ func FuzzL1InfoRoundTrip(f *testing.F) { // FuzzL1InfoAgainstContract checks the custom marshalling functions against the contract // bindings to ensure that our functions are up to date and match the bindings. +// +// TODO: Add Ecotone upgrade version once op-bindings have been updated. func FuzzL1InfoAgainstContract(f *testing.F) { f.Fuzz(func(t *testing.T, number, time uint64, baseFee, hash []byte, seqNumber uint64, batcherHash []byte, l1FeeOverhead []byte, l1FeeScalar []byte) { expected := L1BlockInfo{ Number: number, Time: time, - BaseFee: BytesToBigInt(baseFee), + Basefee: BytesToBigInt(baseFee), BlockHash: common.BytesToHash(hash), SequenceNumber: seqNumber, BatcherAddr: common.BytesToAddress(batcherHash), @@ -107,7 +138,7 @@ func FuzzL1InfoAgainstContract(f *testing.F) { // Check that our encoder produces the same value and that we // can decode the contract values exactly - enc, err := expected.MarshalBinary() + enc, err := expected.marshalBinaryBedrock() if err != nil { t.Fatalf("Failed to marshal binary: %v", err) } @@ -118,7 +149,7 @@ func FuzzL1InfoAgainstContract(f *testing.F) { } var actual L1BlockInfo - err = actual.UnmarshalBinary(tx.Data()) + err = actual.unmarshalBinaryBedrock(tx.Data()) if err != nil { t.Fatalf("Failed to unmarshal binary: %v", err) } diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index 83d8e0fe9bd3..c51bd28e9324 100644 --- a/op-node/rollup/derive/l1_block_info.go +++ b/op-node/rollup/derive/l1_block_info.go @@ -2,6 +2,7 @@ package derive import ( "bytes" + "encoding/binary" "errors" "fmt" "math/big" @@ -11,20 +12,24 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum-optimism/optimism/op-bindings/predeploys" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/solabi" ) const ( - L1InfoFuncSignature = "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)" - L1InfoArguments = 8 - L1InfoLen = 4 + 32*L1InfoArguments + L1InfoFuncSignature = "setL1BlockValues(uint64,uint64,uint256,bytes32,uint64,bytes32,uint256,uint256)" + L1InfoFuncEcotoneSignature = "setL1BlockValuesEcotone()" + L1InfoArguments = 8 + L1InfoLen = 4 + 32*L1InfoArguments + L1InfoEcotoneLen = 4 + 32*5 // after Ecotone upgrade, args are packed into 5 32-byte slots ) var ( - L1InfoFuncBytes4 = crypto.Keccak256([]byte(L1InfoFuncSignature))[:4] - L1InfoDepositerAddress = common.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001") - L1BlockAddress = predeploys.L1BlockAddr + L1InfoFuncBytes4 = crypto.Keccak256([]byte(L1InfoFuncSignature))[:4] + L1InfoFuncEcotoneBytes4 = crypto.Keccak256([]byte(L1InfoFuncEcotoneSignature))[:4] + L1InfoDepositerAddress = common.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001") + L1BlockAddress = predeploys.L1BlockAddr ) const ( @@ -35,33 +40,38 @@ const ( type L1BlockInfo struct { Number uint64 Time uint64 - BaseFee *big.Int + Basefee *big.Int BlockHash common.Hash // Not strictly a piece of L1 information. Represents the number of L2 blocks since the start of the epoch, // i.e. when the actual L1 info was first introduced. SequenceNumber uint64 // BatcherHash version 0 is just the address with 0 padding to the left. - BatcherAddr common.Address - L1FeeOverhead eth.Bytes32 - L1FeeScalar eth.Bytes32 + BatcherAddr common.Address + + L1FeeOverhead eth.Bytes32 // ignored after Ecotone upgrade + L1FeeScalar eth.Bytes32 // ignored after Ecotone upgrade + + BlobBasefee *big.Int // added by Ecotone upgrade + BasefeeScalar uint32 // added by Ecotone upgrade + BlobBasefeeScalar uint32 // added by Ecotone upgrade } -// Binary Format +// Bedrock Binary Format // +---------+--------------------------+ // | Bytes | Field | // +---------+--------------------------+ // | 4 | Function signature | // | 32 | Number | // | 32 | Time | -// | 32 | BaseFee | +// | 32 | Basefee | // | 32 | BlockHash | // | 32 | SequenceNumber | -// | 32 | BatcherAddr | +// | 32 | BatcherHash | // | 32 | L1FeeOverhead | // | 32 | L1FeeScalar | // +---------+--------------------------+ -func (info *L1BlockInfo) MarshalBinary() ([]byte, error) { +func (info *L1BlockInfo) marshalBinaryBedrock() ([]byte, error) { w := bytes.NewBuffer(make([]byte, 0, L1InfoLen)) if err := solabi.WriteSignature(w, L1InfoFuncBytes4); err != nil { return nil, err @@ -72,7 +82,7 @@ func (info *L1BlockInfo) MarshalBinary() ([]byte, error) { if err := solabi.WriteUint64(w, info.Time); err != nil { return nil, err } - if err := solabi.WriteUint256(w, info.BaseFee); err != nil { + if err := solabi.WriteUint256(w, info.Basefee); err != nil { return nil, err } if err := solabi.WriteHash(w, info.BlockHash); err != nil { @@ -93,7 +103,7 @@ func (info *L1BlockInfo) MarshalBinary() ([]byte, error) { return w.Bytes(), nil } -func (info *L1BlockInfo) UnmarshalBinary(data []byte) error { +func (info *L1BlockInfo) unmarshalBinaryBedrock(data []byte) error { if len(data) != L1InfoLen { return fmt.Errorf("data is unexpected length: %d", len(data)) } @@ -109,7 +119,7 @@ func (info *L1BlockInfo) UnmarshalBinary(data []byte) error { if info.Time, err = solabi.ReadUint64(reader); err != nil { return err } - if info.BaseFee, err = solabi.ReadUint256(reader); err != nil { + if info.Basefee, err = solabi.ReadUint256(reader); err != nil { return err } if info.BlockHash, err = solabi.ReadHash(reader); err != nil { @@ -133,29 +143,137 @@ func (info *L1BlockInfo) UnmarshalBinary(data []byte) error { return nil } -// L1InfoDepositTxData is the inverse of L1InfoDeposit, to see where the L2 chain is derived from -func L1InfoDepositTxData(data []byte) (L1BlockInfo, error) { +// Ecotone Binary Format +// +---------+--------------------------+ +// | Bytes | Field | +// +---------+--------------------------+ +// | 4 | Function signature | +// | 4 | BasefeeScalar | +// | 4 | BlobBasefeeScalar | +// | 8 | SequenceNumber | +// | 8 | Timestamp | +// | 8 | L1BlockNumber | +// | 32 | Basefee | +// | 32 | BlobBasefee | +// | 32 | BlockHash | +// | 32 | BatcherHash | +// +---------+--------------------------+ + +func (info *L1BlockInfo) marshalBinaryEcotone() ([]byte, error) { + w := bytes.NewBuffer(make([]byte, 0, L1InfoEcotoneLen)) + if err := solabi.WriteSignature(w, L1InfoFuncEcotoneBytes4); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.BasefeeScalar); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.BlobBasefeeScalar); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.SequenceNumber); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.Time); err != nil { + return nil, err + } + if err := binary.Write(w, binary.BigEndian, info.Number); err != nil { + return nil, err + } + if err := solabi.WriteUint256(w, info.Basefee); err != nil { + return nil, err + } + if err := solabi.WriteUint256(w, info.BlobBasefee); err != nil { + return nil, err + } + if err := solabi.WriteHash(w, info.BlockHash); err != nil { + return nil, err + } + if err := solabi.WriteAddress(w, info.BatcherAddr); err != nil { + return nil, err + } + return w.Bytes(), nil +} + +func (info *L1BlockInfo) unmarshalBinaryEcotone(data []byte) error { + if len(data) != L1InfoEcotoneLen { + return fmt.Errorf("data is unexpected length: %d", len(data)) + } + r := bytes.NewReader(data) + + var err error + if _, err := solabi.ReadAndValidateSignature(r, L1InfoFuncEcotoneBytes4); err != nil { + return err + } + if err := binary.Read(r, binary.BigEndian, &info.BasefeeScalar); err != nil { + return fmt.Errorf("invalid ecotone l1 block info format") + } + if err := binary.Read(r, binary.BigEndian, &info.BlobBasefeeScalar); err != nil { + return fmt.Errorf("invalid ecotone l1 block info format") + } + if err := binary.Read(r, binary.BigEndian, &info.SequenceNumber); err != nil { + return fmt.Errorf("invalid ecotone l1 block info format") + } + if err := binary.Read(r, binary.BigEndian, &info.Time); err != nil { + return fmt.Errorf("invalid ecotone l1 block info format") + } + if err := binary.Read(r, binary.BigEndian, &info.Number); err != nil { + return fmt.Errorf("invalid ecotone l1 block info format") + } + if info.Basefee, err = solabi.ReadUint256(r); err != nil { + return err + } + if info.BlobBasefee, err = solabi.ReadUint256(r); err != nil { + return err + } + if info.BlockHash, err = solabi.ReadHash(r); err != nil { + return err + } + if info.BatcherAddr, err = solabi.ReadAddress(r); err != nil { + return err + } + if !solabi.EmptyReader(r) { + return errors.New("too many bytes") + } + return nil +} + +// L1BlockInfoFromBytes is the inverse of L1InfoDeposit, to see where the L2 chain is derived from +func L1BlockInfoFromBytes(rollupCfg *rollup.Config, l2BlockTime uint64, data []byte) (*L1BlockInfo, error) { var info L1BlockInfo - err := info.UnmarshalBinary(data) - return info, err + // TODO: Edge case: the very first block of the Ecotone upgrade is required to have Bedrock style + // attributes, not Ecotone attributes, since the L1Block contract doesn't get upgraded to + // support Ecotone until after the first block is processed. + if rollupCfg.IsEcotone(l2BlockTime) { + return &info, info.unmarshalBinaryEcotone(data) + } + return &info, info.unmarshalBinaryBedrock(data) } // L1InfoDeposit creates a L1 Info deposit transaction based on the L1 block, // and the L2 block-height difference with the start of the epoch. -func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfig, regolith bool) (*types.DepositTx, error) { - infoDat := L1BlockInfo{ +func L1InfoDeposit(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber uint64, block eth.BlockInfo, l2BlockTime uint64) (*types.DepositTx, error) { + l1BlockInfo := L1BlockInfo{ Number: block.NumberU64(), Time: block.Time(), - BaseFee: block.BaseFee(), + Basefee: block.BaseFee(), BlockHash: block.Hash(), SequenceNumber: seqNumber, BatcherAddr: sysCfg.BatcherAddr, L1FeeOverhead: sysCfg.Overhead, L1FeeScalar: sysCfg.Scalar, } - data, err := infoDat.MarshalBinary() + var data []byte + var err error + if rollupCfg.IsEcotone(l2BlockTime) && + (l2BlockTime < rollupCfg.BlockTime || rollupCfg.IsEcotone(l2BlockTime-rollupCfg.BlockTime)) { + // we check the last two block times in order to exclude the first block of the Ecotone + // upgrade, which *must* use bedrock style l1 attributes. + data, err = l1BlockInfo.marshalBinaryEcotone() + } else { + data, err = l1BlockInfo.marshalBinaryBedrock() + } if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to marshal l1 block info: %w", err) } source := L1InfoDepositSource{ @@ -175,7 +293,7 @@ func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfi Data: data, } // With the regolith fork we disable the IsSystemTx functionality, and allocate real gas - if regolith { + if rollupCfg.IsRegolith(l2BlockTime) { out.IsSystemTransaction = false out.Gas = RegolithSystemTxGas } @@ -183,8 +301,8 @@ func L1InfoDeposit(seqNumber uint64, block eth.BlockInfo, sysCfg eth.SystemConfi } // L1InfoDepositBytes returns a serialized L1-info attributes transaction. -func L1InfoDepositBytes(seqNumber uint64, l1Info eth.BlockInfo, sysCfg eth.SystemConfig, regolith bool) ([]byte, error) { - dep, err := L1InfoDeposit(seqNumber, l1Info, sysCfg, regolith) +func L1InfoDepositBytes(rollupCfg *rollup.Config, sysCfg eth.SystemConfig, seqNumber uint64, l1Info eth.BlockInfo, l2BlockTime uint64) ([]byte, error) { + dep, err := L1InfoDeposit(rollupCfg, sysCfg, seqNumber, l1Info, l2BlockTime) if err != nil { return nil, fmt.Errorf("failed to create L1 info tx: %w", err) } diff --git a/op-node/rollup/derive/l1_block_info_test.go b/op-node/rollup/derive/l1_block_info_test.go index ba3b68d0ef5f..fc2cb86e6149 100644 --- a/op-node/rollup/derive/l1_block_info_test.go +++ b/op-node/rollup/derive/l1_block_info_test.go @@ -11,11 +11,15 @@ import ( "github.com/ethereum/go-ethereum/common" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testutils" ) -var _ eth.BlockInfo = (*testutils.MockBlockInfo)(nil) +var ( + MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeef00000000") + _ eth.BlockInfo = (*testutils.MockBlockInfo)(nil) +) type infoTest struct { name string @@ -33,8 +37,6 @@ func randomL1Cfg(rng *rand.Rand, l1Info eth.BlockInfo) eth.SystemConfig { } } -var MockDepositContractAddr = common.HexToAddress("0xdeadbeefdeadbeefdeadbeefdeadbeef00000000") - func TestParseL1InfoDepositTxData(t *testing.T) { randomSeqNr := func(rng *rand.Rand) uint64 { return rng.Uint64() @@ -60,20 +62,21 @@ func TestParseL1InfoDepositTxData(t *testing.T) { return 0 }}, } + var rollupCfg rollup.Config for i, testCase := range cases { t.Run(testCase.name, func(t *testing.T) { rng := rand.New(rand.NewSource(int64(1234 + i))) info := testCase.mkInfo(rng) l1Cfg := testCase.mkL1Cfg(rng, info) seqNr := testCase.seqNr(rng) - depTx, err := L1InfoDeposit(seqNr, info, l1Cfg, false) + depTx, err := L1InfoDeposit(&rollupCfg, l1Cfg, seqNr, info, 0) require.NoError(t, err) - res, err := L1InfoDepositTxData(depTx.Data) + res, err := L1BlockInfoFromBytes(&rollupCfg, info.Time(), depTx.Data) require.NoError(t, err, "expected valid deposit info") assert.Equal(t, res.Number, info.NumberU64()) assert.Equal(t, res.Time, info.Time()) - assert.True(t, res.BaseFee.Sign() >= 0) - assert.Equal(t, res.BaseFee.Bytes(), info.BaseFee().Bytes()) + assert.True(t, res.Basefee.Sign() >= 0) + assert.Equal(t, res.Basefee.Bytes(), info.BaseFee().Bytes()) assert.Equal(t, res.BlockHash, info.Hash()) assert.Equal(t, res.SequenceNumber, seqNr) assert.Equal(t, res.BatcherAddr, l1Cfg.BatcherAddr) @@ -82,31 +85,35 @@ func TestParseL1InfoDepositTxData(t *testing.T) { }) } t.Run("no data", func(t *testing.T) { - _, err := L1InfoDepositTxData(nil) + _, err := L1BlockInfoFromBytes(&rollupCfg, 0, nil) assert.Error(t, err) }) t.Run("not enough data", func(t *testing.T) { - _, err := L1InfoDepositTxData([]byte{1, 2, 3, 4}) + _, err := L1BlockInfoFromBytes(&rollupCfg, 0, []byte{1, 2, 3, 4}) assert.Error(t, err) }) t.Run("too much data", func(t *testing.T) { - _, err := L1InfoDepositTxData(make([]byte, 4+32+32+32+32+32+1)) + _, err := L1BlockInfoFromBytes(&rollupCfg, 0, make([]byte, 4+32+32+32+32+32+1)) assert.Error(t, err) }) t.Run("invalid selector", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) - depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info), false) + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 0) require.NoError(t, err) _, err = crand.Read(depTx.Data[0:4]) require.NoError(t, err) - _, err = L1InfoDepositTxData(depTx.Data) + _, err = L1BlockInfoFromBytes(&rollupCfg, info.Time(), depTx.Data) require.ErrorContains(t, err, "function signature") }) t.Run("regolith", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) - depTx, err := L1InfoDeposit(randomSeqNr(rng), info, randomL1Cfg(rng, info), true) + zero := uint64(0) + rollupCfg := rollup.Config{ + RegolithTime: &zero, + } + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 0) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) diff --git a/op-node/rollup/derive/l1_block_info_tob_test.go b/op-node/rollup/derive/l1_block_info_tob_test.go index b3e0fadb0253..69b5799dcc37 100644 --- a/op-node/rollup/derive/l1_block_info_tob_test.go +++ b/op-node/rollup/derive/l1_block_info_tob_test.go @@ -3,6 +3,7 @@ package derive import ( "testing" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/testutils/fuzzerutils" @@ -25,20 +26,21 @@ func FuzzParseL1InfoDepositTxDataValid(f *testing.F) { typeProvider.Fuzz(&seqNr) var sysCfg eth.SystemConfig typeProvider.Fuzz(&sysCfg) + var rollupCfg rollup.Config // Create our deposit tx from our info - depTx, err := L1InfoDeposit(seqNr, &l1Info, sysCfg, false) + depTx, err := L1InfoDeposit(&rollupCfg, sysCfg, seqNr, &l1Info, 0) require.NoError(t, err, "error creating deposit tx from L1 info") // Get our info from out deposit tx - res, err := L1InfoDepositTxData(depTx.Data) + res, err := L1BlockInfoFromBytes(&rollupCfg, l1Info.InfoTime, depTx.Data) require.NoError(t, err, "expected valid deposit info") // Verify all parameters match in our round trip deriving operations require.Equal(t, res.Number, l1Info.NumberU64()) require.Equal(t, res.Time, l1Info.Time()) - require.True(t, res.BaseFee.Sign() >= 0) - require.Equal(t, res.BaseFee.Bytes(), l1Info.BaseFee().Bytes()) + require.True(t, res.Basefee.Sign() >= 0) + require.Equal(t, res.Basefee.Bytes(), l1Info.BaseFee().Bytes()) require.Equal(t, res.BlockHash, l1Info.Hash()) require.Equal(t, res.SequenceNumber, seqNr) require.Equal(t, res.BatcherAddr, sysCfg.BatcherAddr) @@ -50,9 +52,10 @@ func FuzzParseL1InfoDepositTxDataValid(f *testing.F) { // Reverse of the above test. Accepts a random byte string and attempts to extract L1Info from it, // then attempts to convert that info back into the tx data and compare it with the original input. func FuzzDecodeDepositTxDataToL1Info(f *testing.F) { + var rollupCfg rollup.Config f.Fuzz(func(t *testing.T, fuzzedData []byte) { // Get our info from out deposit tx - res, err := L1InfoDepositTxData(fuzzedData) + res, err := L1BlockInfoFromBytes(&rollupCfg, 0, fuzzedData) if err != nil { return } @@ -61,7 +64,7 @@ func FuzzDecodeDepositTxDataToL1Info(f *testing.F) { InfoHash: res.BlockHash, InfoNum: res.Number, InfoTime: res.Time, - InfoBaseFee: res.BaseFee, + InfoBaseFee: res.Basefee, } sysCfg := eth.SystemConfig{ @@ -71,7 +74,7 @@ func FuzzDecodeDepositTxDataToL1Info(f *testing.F) { GasLimit: uint64(0), } - depTx, err := L1InfoDeposit(res.SequenceNumber, &l1Info, sysCfg, false) + depTx, err := L1InfoDeposit(&rollupCfg, sysCfg, res.SequenceNumber, &l1Info, 0) require.NoError(t, err, "error creating deposit tx from L1 info") require.Equal(t, depTx.Data, fuzzedData) }) @@ -81,10 +84,11 @@ func FuzzDecodeDepositTxDataToL1Info(f *testing.F) { // random L1 deposit tx info and derives a tx from it, then derives the info back from the tx, to ensure round-trip // derivation is upheld. This generates "invalid" data and ensures it always throws an error where expected. func FuzzParseL1InfoDepositTxDataBadLength(f *testing.F) { + var rollupCfg rollup.Config const expectedDepositTxDataLength = 4 + 32 + 32 + 32 + 32 + 32 f.Fuzz(func(t *testing.T, fuzzedData []byte) { // Derive a transaction from random fuzzed data - _, err := L1InfoDepositTxData(fuzzedData) + _, err := L1BlockInfoFromBytes(&rollupCfg, 0, fuzzedData) // If the data is null, or too short or too long, we expect an error if fuzzedData == nil || len(fuzzedData) != expectedDepositTxDataLength { diff --git a/op-node/rollup/derive/l2block_util.go b/op-node/rollup/derive/l2block_util.go index b141567989af..5946247c7dc6 100644 --- a/op-node/rollup/derive/l2block_util.go +++ b/op-node/rollup/derive/l2block_util.go @@ -25,11 +25,12 @@ type L2BlockRefSource interface { // L2BlockToBlockRef extracts the essential L2BlockRef information from an L2 // block ref source, falling back to genesis information if necessary. -func L2BlockToBlockRef(block L2BlockRefSource, genesis *rollup.Genesis) (eth.L2BlockRef, error) { +func L2BlockToBlockRef(rollupCfg *rollup.Config, block L2BlockRefSource) (eth.L2BlockRef, error) { hash, number := block.Hash(), block.NumberU64() var l1Origin eth.BlockID var sequenceNumber uint64 + genesis := &rollupCfg.Genesis if number == genesis.L2.Number { if hash != genesis.L2.Hash { return eth.L2BlockRef{}, fmt.Errorf("expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", genesis.L2.Number, hash, genesis.L2.Hash) @@ -45,7 +46,7 @@ func L2BlockToBlockRef(block L2BlockRefSource, genesis *rollup.Genesis) (eth.L2B if tx.Type() != types.DepositTxType { return eth.L2BlockRef{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type()) } - info, err := L1InfoDepositTxData(tx.Data()) + info, err := L1BlockInfoFromBytes(rollupCfg, block.Time(), tx.Data()) if err != nil { return eth.L2BlockRef{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) } diff --git a/op-node/rollup/derive/payload_util.go b/op-node/rollup/derive/payload_util.go index 11a183992be2..c1dfe0432018 100644 --- a/op-node/rollup/derive/payload_util.go +++ b/op-node/rollup/derive/payload_util.go @@ -11,7 +11,8 @@ import ( // PayloadToBlockRef extracts the essential L2BlockRef information from an execution payload, // falling back to genesis information if necessary. -func PayloadToBlockRef(payload *eth.ExecutionPayload, genesis *rollup.Genesis) (eth.L2BlockRef, error) { +func PayloadToBlockRef(rollupCfg *rollup.Config, payload *eth.ExecutionPayload) (eth.L2BlockRef, error) { + genesis := &rollupCfg.Genesis var l1Origin eth.BlockID var sequenceNumber uint64 if uint64(payload.BlockNumber) == genesis.L2.Number { @@ -31,7 +32,7 @@ func PayloadToBlockRef(payload *eth.ExecutionPayload, genesis *rollup.Genesis) ( if tx.Type() != types.DepositTxType { return eth.L2BlockRef{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type()) } - info, err := L1InfoDepositTxData(tx.Data()) + info, err := L1BlockInfoFromBytes(rollupCfg, uint64(payload.Timestamp), tx.Data()) if err != nil { return eth.L2BlockRef{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) } @@ -49,12 +50,14 @@ func PayloadToBlockRef(payload *eth.ExecutionPayload, genesis *rollup.Genesis) ( }, nil } -func PayloadToSystemConfig(payload *eth.ExecutionPayload, cfg *rollup.Config) (eth.SystemConfig, error) { - if uint64(payload.BlockNumber) == cfg.Genesis.L2.Number { - if payload.BlockHash != cfg.Genesis.L2.Hash { - return eth.SystemConfig{}, fmt.Errorf("expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", cfg.Genesis.L2.Number, payload.BlockHash, cfg.Genesis.L2.Hash) +func PayloadToSystemConfig(rollupCfg *rollup.Config, payload *eth.ExecutionPayload) (eth.SystemConfig, error) { + if uint64(payload.BlockNumber) == rollupCfg.Genesis.L2.Number { + if payload.BlockHash != rollupCfg.Genesis.L2.Hash { + return eth.SystemConfig{}, fmt.Errorf( + "expected L2 genesis hash to match L2 block at genesis block number %d: %s <> %s", + rollupCfg.Genesis.L2.Number, payload.BlockHash, rollupCfg.Genesis.L2.Hash) } - return cfg.Genesis.SystemConfig, nil + return rollupCfg.Genesis.SystemConfig, nil } else { if len(payload.Transactions) == 0 { return eth.SystemConfig{}, fmt.Errorf("l2 block is missing L1 info deposit tx, block hash: %s", payload.BlockHash) @@ -66,15 +69,17 @@ func PayloadToSystemConfig(payload *eth.ExecutionPayload, cfg *rollup.Config) (e if tx.Type() != types.DepositTxType { return eth.SystemConfig{}, fmt.Errorf("first payload tx has unexpected tx type: %d", tx.Type()) } - info, err := L1InfoDepositTxData(tx.Data()) + info, err := L1BlockInfoFromBytes(rollupCfg, uint64(payload.Timestamp), tx.Data()) if err != nil { return eth.SystemConfig{}, fmt.Errorf("failed to parse L1 info deposit tx from L2 block: %w", err) } return eth.SystemConfig{ - BatcherAddr: info.BatcherAddr, - Overhead: info.L1FeeOverhead, - Scalar: info.L1FeeScalar, - GasLimit: uint64(payload.GasLimit), + BatcherAddr: info.BatcherAddr, + Overhead: info.L1FeeOverhead, + Scalar: info.L1FeeScalar, + GasLimit: uint64(payload.GasLimit), + BasefeeScalar: info.BasefeeScalar, + BlobBasefeeScalar: info.BlobBasefeeScalar, }, err } } diff --git a/op-node/rollup/derive/pipeline.go b/op-node/rollup/derive/pipeline.go index 8108c269e540..6e69f16df00d 100644 --- a/op-node/rollup/derive/pipeline.go +++ b/op-node/rollup/derive/pipeline.go @@ -66,7 +66,7 @@ type EngineQueueStage interface { // DerivationPipeline is updated with new L1 data, and the Step() function can be iterated on to keep the L2 Engine in sync. type DerivationPipeline struct { log log.Logger - cfg *rollup.Config + rollupCfg *rollup.Config l1Fetcher L1Fetcher // Index of the stage that is currently being reset. @@ -82,21 +82,21 @@ type DerivationPipeline struct { } // NewDerivationPipeline creates a derivation pipeline, which should be reset before use. -func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetcher, l1Blobs L1BlobsFetcher, engine Engine, metrics Metrics, syncCfg *sync.Config) *DerivationPipeline { +func NewDerivationPipeline(log log.Logger, rollupCfg *rollup.Config, l1Fetcher L1Fetcher, l1Blobs L1BlobsFetcher, engine Engine, metrics Metrics, syncCfg *sync.Config) *DerivationPipeline { // Pull stages - l1Traversal := NewL1Traversal(log, cfg, l1Fetcher) - dataSrc := NewDataSourceFactory(log, cfg, l1Fetcher, l1Blobs) // auxiliary stage for L1Retrieval + l1Traversal := NewL1Traversal(log, rollupCfg, l1Fetcher) + dataSrc := NewDataSourceFactory(log, rollupCfg, l1Fetcher, l1Blobs) // auxiliary stage for L1Retrieval l1Src := NewL1Retrieval(log, dataSrc, l1Traversal) frameQueue := NewFrameQueue(log, l1Src) - bank := NewChannelBank(log, cfg, frameQueue, l1Fetcher, metrics) - chInReader := NewChannelInReader(cfg, log, bank, metrics) - batchQueue := NewBatchQueue(log, cfg, chInReader, engine) - attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, engine) - attributesQueue := NewAttributesQueue(log, cfg, attrBuilder, batchQueue) + bank := NewChannelBank(log, rollupCfg, frameQueue, l1Fetcher, metrics) + chInReader := NewChannelInReader(rollupCfg, log, bank, metrics) + batchQueue := NewBatchQueue(log, rollupCfg, chInReader, engine) + attrBuilder := NewFetchingAttributesBuilder(rollupCfg, l1Fetcher, engine) + attributesQueue := NewAttributesQueue(log, rollupCfg, attrBuilder, batchQueue) // Step stages - eng := NewEngineQueue(log, cfg, engine, metrics, attributesQueue, l1Fetcher, syncCfg) + eng := NewEngineQueue(log, rollupCfg, engine, metrics, attributesQueue, l1Fetcher, syncCfg) // Reset from engine queue then up from L1 Traversal. The stages do not talk to each other during // the reset, but after the engine queue, this is the order in which the stages could talk to each other. @@ -105,7 +105,7 @@ func NewDerivationPipeline(log log.Logger, cfg *rollup.Config, l1Fetcher L1Fetch return &DerivationPipeline{ log: log, - cfg: cfg, + rollupCfg: rollupCfg, l1Fetcher: l1Fetcher, resetting: 0, stages: stages, diff --git a/op-node/rollup/derive/span_channel_out.go b/op-node/rollup/derive/span_channel_out.go index 7d224a90ab57..362272a44e21 100644 --- a/op-node/rollup/derive/span_channel_out.go +++ b/op-node/rollup/derive/span_channel_out.go @@ -8,6 +8,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rlp" + + "github.com/ethereum-optimism/optimism/op-node/rollup" ) type SpanChannelOut struct { @@ -63,12 +65,12 @@ func (co *SpanChannelOut) Reset() error { // and an error if there is a problem adding the block. The only sentinel error // that it returns is ErrTooManyRLPBytes. If this error is returned, the channel // should be closed and a new one should be made. -func (co *SpanChannelOut) AddBlock(block *types.Block) (uint64, error) { +func (co *SpanChannelOut) AddBlock(rollupCfg *rollup.Config, block *types.Block) (uint64, error) { if co.closed { return 0, ErrChannelOutAlreadyClosed } - batch, l1Info, err := BlockToSingularBatch(block) + batch, l1Info, err := BlockToSingularBatch(rollupCfg, block) if err != nil { return 0, err } diff --git a/op-node/rollup/derive/test/random.go b/op-node/rollup/derive/test/random.go index 5724fd023f31..c0f512b49588 100644 --- a/op-node/rollup/derive/test/random.go +++ b/op-node/rollup/derive/test/random.go @@ -4,6 +4,7 @@ import ( "math/big" "math/rand" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testutils" @@ -11,12 +12,17 @@ import ( "github.com/ethereum/go-ethereum/trie" ) -// RandomL2Block returns a random block whose first transaction is a random +// RandomL2Block returns a random block whose first transaction is a random pre-Ecotone upgrade // L1 Info Deposit transaction. func RandomL2Block(rng *rand.Rand, txCount int) (*types.Block, []*types.Receipt) { l1Block := types.NewBlock(testutils.RandomHeader(rng), nil, nil, nil, trie.NewStackTrie(nil)) - l1InfoTx, err := derive.L1InfoDeposit(0, eth.BlockToInfo(l1Block), eth.SystemConfig{}, testutils.RandomBool(rng)) + rollupCfg := rollup.Config{} + if testutils.RandomBool(rng) { + t := uint64(0) + rollupCfg.RegolithTime = &t + } + l1InfoTx, err := derive.L1InfoDeposit(&rollupCfg, eth.SystemConfig{}, 0, eth.BlockToInfo(l1Block), 0) if err != nil { panic("L1InfoDeposit: " + err.Error()) } diff --git a/op-node/rollup/driver/sequencer.go b/op-node/rollup/driver/sequencer.go index 29d2637e2757..4cdc91bf51b0 100644 --- a/op-node/rollup/driver/sequencer.go +++ b/op-node/rollup/driver/sequencer.go @@ -31,8 +31,8 @@ type SequencerMetrics interface { // Sequencer implements the sequencing interface of the driver: it starts and completes block building jobs. type Sequencer struct { - log log.Logger - config *rollup.Config + log log.Logger + rollupCfg *rollup.Config engine derive.ResettableEngineControl @@ -47,10 +47,10 @@ type Sequencer struct { nextAction time.Time } -func NewSequencer(log log.Logger, cfg *rollup.Config, engine derive.ResettableEngineControl, attributesBuilder derive.AttributesBuilder, l1OriginSelector L1OriginSelectorIface, metrics SequencerMetrics) *Sequencer { +func NewSequencer(log log.Logger, rollupCfg *rollup.Config, engine derive.ResettableEngineControl, attributesBuilder derive.AttributesBuilder, l1OriginSelector L1OriginSelectorIface, metrics SequencerMetrics) *Sequencer { return &Sequencer{ log: log, - config: cfg, + rollupCfg: rollupCfg, engine: engine, timeNow: time.Now, attrBuilder: attributesBuilder, @@ -89,7 +89,7 @@ func (d *Sequencer) StartBuildingBlock(ctx context.Context) error { // empty blocks (other than the L1 info deposit and any user deposits). We handle this by // setting NoTxPool to true, which will cause the Sequencer to not include any transactions // from the transaction pool. - attrs.NoTxPool = uint64(attrs.Timestamp) > l1Origin.Time+d.config.MaxSequencerDrift + attrs.NoTxPool = uint64(attrs.Timestamp) > l1Origin.Time+d.rollupCfg.MaxSequencerDrift d.log.Debug("prepared attributes for new block", "num", l2Head.Number+1, "time", uint64(attrs.Timestamp), @@ -129,7 +129,7 @@ func (d *Sequencer) PlanNextSequencerAction() time.Duration { if onto, _, safe := d.engine.BuildingPayload(); safe { d.log.Warn("delaying sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time) // approximates the worst-case time it takes to build a block, to reattempt sequencing after. - return time.Second * time.Duration(d.config.BlockTime) + return time.Second * time.Duration(d.rollupCfg.BlockTime) } head := d.engine.UnsafeL2Head() @@ -143,8 +143,8 @@ func (d *Sequencer) PlanNextSequencerAction() time.Duration { return delay } - blockTime := time.Duration(d.config.BlockTime) * time.Second - payloadTime := time.Unix(int64(head.Time+d.config.BlockTime), 0) + blockTime := time.Duration(d.rollupCfg.BlockTime) * time.Second + payloadTime := time.Unix(int64(head.Time+d.rollupCfg.BlockTime), 0) remainingTime := payloadTime.Sub(now) // If we started building a block already, and if that work is still consistent, @@ -202,7 +202,7 @@ func (d *Sequencer) RunNextSequencerAction(ctx context.Context) (*eth.ExecutionP if safe { d.log.Warn("avoiding sequencing to not interrupt safe-head changes", "onto", onto, "onto_time", onto.Time) // approximates the worst-case time it takes to build a block, to reattempt sequencing after. - d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.config.BlockTime)) + d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.rollupCfg.BlockTime)) return nil, nil } payload, err := d.CompleteBuildingBlock(ctx) @@ -212,7 +212,7 @@ func (d *Sequencer) RunNextSequencerAction(ctx context.Context) (*eth.ExecutionP } else if errors.Is(err, derive.ErrReset) { d.log.Error("sequencer failed to seal new block, requiring derivation reset", "err", err) d.metrics.RecordSequencerReset() - d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.config.BlockTime)) // hold off from sequencing for a full block + d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.rollupCfg.BlockTime)) // hold off from sequencing for a full block d.CancelBuildingBlock(ctx) d.engine.Reset() } else if errors.Is(err, derive.ErrTemporary) { @@ -238,7 +238,7 @@ func (d *Sequencer) RunNextSequencerAction(ctx context.Context) (*eth.ExecutionP } else if errors.Is(err, derive.ErrReset) { d.log.Error("sequencer failed to seal new block, requiring derivation reset", "err", err) d.metrics.RecordSequencerReset() - d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.config.BlockTime)) // hold off from sequencing for a full block + d.nextAction = d.timeNow().Add(time.Second * time.Duration(d.rollupCfg.BlockTime)) // hold off from sequencing for a full block d.engine.Reset() } else if errors.Is(err, derive.ErrTemporary) { d.log.Error("sequencer temporarily failed to start building new block", "err", err) diff --git a/op-node/rollup/driver/sequencer_test.go b/op-node/rollup/driver/sequencer_test.go index 3495526a7674..e8026619f53c 100644 --- a/op-node/rollup/driver/sequencer_test.go +++ b/op-node/rollup/driver/sequencer_test.go @@ -81,7 +81,7 @@ func (m *FakeEngineControl) ConfirmPayload(ctx context.Context) (out *eth.Execut m.totalBuildingTime += buildTime m.totalBuiltBlocks += 1 payload := m.makePayload(m.buildingOnto, m.buildingAttrs) - ref, err := derive.PayloadToBlockRef(payload, &m.cfg.Genesis) + ref, err := derive.PayloadToBlockRef(m.cfg, payload) if err != nil { panic(err) } @@ -252,7 +252,7 @@ func TestSequencerChaosMonkey(t *testing.T) { InfoBaseFee: big.NewInt(1234), InfoReceiptRoot: common.Hash{}, } - infoDep, err := derive.L1InfoDepositBytes(seqNr, l1Info, cfg.Genesis.SystemConfig, false) + infoDep, err := derive.L1InfoDepositBytes(cfg, cfg.Genesis.SystemConfig, seqNr, l1Info, 0) require.NoError(t, err) testGasLimit := eth.Uint64Quantity(10_000_000) @@ -354,7 +354,7 @@ func TestSequencerChaosMonkey(t *testing.T) { require.Equal(t, engControl.UnsafeL2Head().ID(), payload.ID(), "head must stay in sync with emitted payloads") var tx types.Transaction require.NoError(t, tx.UnmarshalBinary(payload.Transactions[0])) - info, err := derive.L1InfoDepositTxData(tx.Data()) + info, err := derive.L1BlockInfoFromBytes(cfg, 0, tx.Data()) require.NoError(t, err) require.GreaterOrEqual(t, uint64(payload.Timestamp), info.Time, "ensure L2 time >= L1 time") } diff --git a/op-node/rollup/superchain.go b/op-node/rollup/superchain.go index dba51b991d4d..703eff774e32 100644 --- a/op-node/rollup/superchain.go +++ b/op-node/rollup/superchain.go @@ -100,7 +100,7 @@ func LoadOPStackRollupConfig(chainID uint64) (*Config, error) { RegolithTime: ®olithTime, CanyonTime: superChain.Config.CanyonTime, DeltaTime: superChain.Config.DeltaTime, - EcotoneTime: superChain.Config.EclipseTime, + EcotoneTime: superChain.Config.EcotoneTime, FjordTime: superChain.Config.FjordTime, BatchInboxAddress: common.Address(chConfig.BatchInboxAddr), DepositContractAddress: depositContractAddress, diff --git a/op-program/client/l2/engine.go b/op-program/client/l2/engine.go index 2bc808e4a78a..30ea72a321cd 100644 --- a/op-program/client/l2/engine.go +++ b/op-program/client/l2/engine.go @@ -101,7 +101,7 @@ func (o *OracleEngine) L2BlockRefByLabel(ctx context.Context, label eth.BlockLab if block == nil { return eth.L2BlockRef{}, ErrNotFound } - return derive.L2BlockToBlockRef(block, &o.rollupCfg.Genesis) + return derive.L2BlockToBlockRef(o.rollupCfg, block) } func (o *OracleEngine) L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) (eth.L2BlockRef, error) { @@ -109,7 +109,7 @@ func (o *OracleEngine) L2BlockRefByHash(ctx context.Context, l2Hash common.Hash) if block == nil { return eth.L2BlockRef{}, ErrNotFound } - return derive.L2BlockToBlockRef(block, &o.rollupCfg.Genesis) + return derive.L2BlockToBlockRef(o.rollupCfg, block) } func (o *OracleEngine) L2BlockRefByNumber(ctx context.Context, n uint64) (eth.L2BlockRef, error) { @@ -125,5 +125,5 @@ func (o *OracleEngine) SystemConfigByL2Hash(ctx context.Context, hash common.Has if err != nil { return eth.SystemConfig{}, err } - return derive.PayloadToSystemConfig(payload, o.rollupCfg) + return derive.PayloadToSystemConfig(o.rollupCfg, payload) } diff --git a/op-program/client/l2/engine_test.go b/op-program/client/l2/engine_test.go index cfee68e6dac7..a4391cac7803 100644 --- a/op-program/client/l2/engine_test.go +++ b/op-program/client/l2/engine_test.go @@ -87,7 +87,7 @@ func TestL2BlockRefByLabel(t *testing.T) { } for _, test := range tests { t.Run(string(test.name), func(t *testing.T) { - expected, err := derive.L2BlockToBlockRef(test.block, &engine.rollupCfg.Genesis) + expected, err := derive.L2BlockToBlockRef(engine.rollupCfg, test.block) require.NoError(t, err) blockRef, err := engine.L2BlockRefByLabel(ctx, test.name) require.NoError(t, err) @@ -105,7 +105,7 @@ func TestL2BlockRefByHash(t *testing.T) { engine, stub := createOracleEngine(t) t.Run("KnownBlock", func(t *testing.T) { - expected, err := derive.L2BlockToBlockRef(stub.safe, &engine.rollupCfg.Genesis) + expected, err := derive.L2BlockToBlockRef(engine.rollupCfg, stub.safe) require.NoError(t, err) ref, err := engine.L2BlockRefByHash(ctx, stub.safe.Hash()) require.NoError(t, err) @@ -126,7 +126,7 @@ func TestSystemConfigByL2Hash(t *testing.T) { t.Run("KnownBlock", func(t *testing.T) { payload, err := eth.BlockAsPayload(stub.safe, engine.rollupCfg.CanyonTime) require.NoError(t, err) - expected, err := derive.PayloadToSystemConfig(payload, engine.rollupCfg) + expected, err := derive.PayloadToSystemConfig(engine.rollupCfg, payload) require.NoError(t, err) cfg, err := engine.SystemConfigByL2Hash(ctx, stub.safe.Hash()) require.NoError(t, err) @@ -167,10 +167,10 @@ func createOracleEngine(t *testing.T) (*OracleEngine, *stubEngineBackend) { } func createL2Block(t *testing.T, number int) *types.Block { - tx, err := derive.L1InfoDeposit(uint64(1), eth.HeaderBlockInfo(&types.Header{ + tx, err := derive.L1InfoDeposit(chaincfg.Goerli, eth.SystemConfig{}, uint64(1), eth.HeaderBlockInfo(&types.Header{ Number: big.NewInt(32), BaseFee: big.NewInt(7), - }), eth.SystemConfig{}, true) + }), 0) require.NoError(t, err) header := &types.Header{ Number: big.NewInt(int64(number)), diff --git a/op-program/client/l2/engineapi/test/l2_engine_api_tests.go b/op-program/client/l2/engineapi/test/l2_engine_api_tests.go index 2bdc784886e4..232f002e8fe9 100644 --- a/op-program/client/l2/engineapi/test/l2_engine_api_tests.go +++ b/op-program/client/l2/engineapi/test/l2_engine_api_tests.go @@ -4,6 +4,7 @@ import ( "context" "testing" + "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-program/client/l2/engineapi" "github.com/ethereum-optimism/optimism/op-service/eth" @@ -26,11 +27,15 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi. api.assert.Equal(block.BlockHash, api.headHash(), "should create and import new block") }) + zero := uint64(0) + rollupCfg := &rollup.Config{ + RegolithTime: &zero, // activate Regolith upgrade + } t.Run("IncludeRequiredTransactions", func(t *testing.T) { api := newTestHelper(t, createBackend) genesis := api.backend.CurrentHeader() - txData, err := derive.L1InfoDeposit(1, eth.HeaderBlockInfo(genesis), eth.SystemConfig{}, true) + txData, err := derive.L1InfoDeposit(rollupCfg, eth.SystemConfig{}, 1, eth.HeaderBlockInfo(genesis), 0) api.assert.NoError(err) tx := types.NewTx(txData) block := api.addBlock(tx) @@ -48,7 +53,7 @@ func RunEngineAPITests(t *testing.T, createBackend func(t *testing.T) engineapi. api := newTestHelper(t, createBackend) genesis := api.backend.CurrentHeader() - txData, err := derive.L1InfoDeposit(1, eth.HeaderBlockInfo(genesis), eth.SystemConfig{}, true) + txData, err := derive.L1InfoDeposit(rollupCfg, eth.SystemConfig{}, 1, eth.HeaderBlockInfo(genesis), 0) api.assert.NoError(err) txData.Gas = uint64(gasLimit + 1) tx := types.NewTx(txData) diff --git a/op-service/sources/l2_client.go b/op-service/sources/l2_client.go index 5d108d3ca338..b2f87e1f89ac 100644 --- a/op-service/sources/l2_client.go +++ b/op-service/sources/l2_client.go @@ -92,6 +92,10 @@ func NewL2Client(client client.RPC, log log.Logger, metrics caching.Metrics, con }, nil } +func (s *L2Client) RollupConfig() *rollup.Config { + return s.rollupCfg +} + // L2BlockRefByLabel returns the [eth.L2BlockRef] for the given block label. func (s *L2Client) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) (eth.L2BlockRef, error) { payload, err := s.PayloadByLabel(ctx, label) @@ -104,7 +108,7 @@ func (s *L2Client) L2BlockRefByLabel(ctx context.Context, label eth.BlockLabel) // w%: wrap to preserve ethereum.NotFound case return eth.L2BlockRef{}, fmt.Errorf("failed to determine L2BlockRef of %s, could not get payload: %w", label, err) } - ref, err := derive.PayloadToBlockRef(payload, &s.rollupCfg.Genesis) + ref, err := derive.PayloadToBlockRef(s.rollupCfg, payload) if err != nil { return eth.L2BlockRef{}, err } @@ -119,7 +123,7 @@ func (s *L2Client) L2BlockRefByNumber(ctx context.Context, num uint64) (eth.L2Bl // w%: wrap to preserve ethereum.NotFound case return eth.L2BlockRef{}, fmt.Errorf("failed to determine L2BlockRef of height %v, could not get payload: %w", num, err) } - ref, err := derive.PayloadToBlockRef(payload, &s.rollupCfg.Genesis) + ref, err := derive.PayloadToBlockRef(s.rollupCfg, payload) if err != nil { return eth.L2BlockRef{}, err } @@ -139,7 +143,7 @@ func (s *L2Client) L2BlockRefByHash(ctx context.Context, hash common.Hash) (eth. // w%: wrap to preserve ethereum.NotFound case return eth.L2BlockRef{}, fmt.Errorf("failed to determine block-hash of hash %v, could not get payload: %w", hash, err) } - ref, err := derive.PayloadToBlockRef(payload, &s.rollupCfg.Genesis) + ref, err := derive.PayloadToBlockRef(s.rollupCfg, payload) if err != nil { return eth.L2BlockRef{}, err } @@ -159,7 +163,7 @@ func (s *L2Client) SystemConfigByL2Hash(ctx context.Context, hash common.Hash) ( // w%: wrap to preserve ethereum.NotFound case return eth.SystemConfig{}, fmt.Errorf("failed to determine block-hash of hash %v, could not get payload: %w", hash, err) } - cfg, err := derive.PayloadToSystemConfig(payload, s.rollupCfg) + cfg, err := derive.PayloadToSystemConfig(s.rollupCfg, payload) if err != nil { return eth.SystemConfig{}, err }