From e85ece4ea50705b205ac2008be1cd571a46d19fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:03:29 +0100 Subject: [PATCH 01/39] Split GetConsensusParsedInitMsg into two variants --- cmd/nitro/init/init.go | 100 ++++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 45 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 63852d9a267..e33fb5a582e 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -635,7 +635,12 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co return executionDB, nil, nil, err } - parsedInitMessage, err := GetConsensusParsedInitMsg(ctx, config.Node.ParentChainReader.Enable, chainId, l1Client, &rollupAddrs, chainConfig) + var parsedInitMessage *arbostypes.ParsedInitMessage + if config.Node.ParentChainReader.Enable { + parsedInitMessage, err = GetParsedInitMsgFromConsensus(ctx, chainId, l1Client, &rollupAddrs, chainConfig) + } else { + parsedInitMessage, err = GetParsedInitMsgFromConfig(chainConfig, genesisArbOSInit) + } if err != nil { return executionDB, nil, nil, err } @@ -1033,55 +1038,60 @@ func OpenExistingExecutionDB(stack *node.Node, config *config.NodeConfig, chainI return nil, nil, nil, nil, nil } -func GetConsensusParsedInitMsg(ctx context.Context, parentChainReaderEnabled bool, chainId *big.Int, l1Client *ethclient.Client, rollupAddrs *chaininfo.RollupAddresses, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { +func GetParsedInitMsgFromConsensus(ctx context.Context, chainId *big.Int, l1Client *ethclient.Client, rollupAddrs *chaininfo.RollupAddresses, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { var parsedInitMessage *arbostypes.ParsedInitMessage - if parentChainReaderEnabled { - delayedBridge, err := arbnode.NewDelayedBridge(l1Client, rollupAddrs.Bridge, rollupAddrs.DeployedAt) - if err != nil { - return nil, fmt.Errorf("failed creating delayed bridge while attempting to get serialized chain config from init message: %w", err) - } - deployedAt := new(big.Int).SetUint64(rollupAddrs.DeployedAt) - delayedMessages, err := delayedBridge.LookupMessagesInRange(ctx, deployedAt, deployedAt, nil) - if err != nil { - return nil, fmt.Errorf("failed getting delayed messages while attempting to get serialized chain config from init message: %w", err) - } - var initMessage *arbostypes.L1IncomingMessage - for _, msg := range delayedMessages { - if msg.Message.Header.Kind == arbostypes.L1MessageType_Initialize { - initMessage = msg.Message - break - } - } - if initMessage == nil { - return nil, fmt.Errorf("failed to get init message while attempting to get serialized chain config") - } - parsedInitMessage, err = initMessage.ParseInitMessage() - if err != nil { - return nil, err - } - if parsedInitMessage.ChainId.Cmp(chainId) != 0 { - return nil, fmt.Errorf("expected L2 chain ID %v but read L2 chain ID %v from init message in L1 inbox", chainId, parsedInitMessage.ChainId) - } - if parsedInitMessage.ChainConfig != nil { - if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { - return nil, fmt.Errorf("incompatible chain config read from init message in L1 inbox: %w", err) - } - } - log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig)) - } else { - serializedChainConfig, err := json.Marshal(chainConfig) - if err != nil { - return nil, err + delayedBridge, err := arbnode.NewDelayedBridge(l1Client, rollupAddrs.Bridge, rollupAddrs.DeployedAt) + if err != nil { + return nil, fmt.Errorf("failed creating delayed bridge while attempting to get serialized chain config from init message: %w", err) + } + deployedAt := new(big.Int).SetUint64(rollupAddrs.DeployedAt) + delayedMessages, err := delayedBridge.LookupMessagesInRange(ctx, deployedAt, deployedAt, nil) + if err != nil { + return nil, fmt.Errorf("failed getting delayed messages while attempting to get serialized chain config from init message: %w", err) + } + var initMessage *arbostypes.L1IncomingMessage + for _, msg := range delayedMessages { + if msg.Message.Header.Kind == arbostypes.L1MessageType_Initialize { + initMessage = msg.Message + break } - parsedInitMessage = &arbostypes.ParsedInitMessage{ - ChainId: chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: chainConfig, - SerializedChainConfig: serializedChainConfig, + } + if initMessage == nil { + return nil, fmt.Errorf("failed to get init message while attempting to get serialized chain config") + } + parsedInitMessage, err = initMessage.ParseInitMessage() + if err != nil { + return nil, err + } + if parsedInitMessage.ChainId.Cmp(chainId) != 0 { + return nil, fmt.Errorf("expected L2 chain ID %v but read L2 chain ID %v from init message in L1 inbox", chainId, parsedInitMessage.ChainId) + } + if parsedInitMessage.ChainConfig != nil { + if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { + return nil, fmt.Errorf("incompatible chain config read from init message in L1 inbox: %w", err) } - log.Warn("Created fake init message as L1Reader is disabled and serialized chain config from init message is not available", "json", string(serializedChainConfig)) } + log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig)) + return parsedInitMessage, nil +} +func GetParsedInitMsgFromConfig(chainConfig *params.ChainConfig, genesisArbOSInit *params.ArbOSInit) (*arbostypes.ParsedInitMessage, error) { + serializedChainConfig, err := json.Marshal(chainConfig) + if err != nil { + return nil, err + } + + fee := genesisArbOSInit.InitialL1BaseFee + if fee == nil { + fee = arbostypes.DefaultInitialL1BaseFee + } + + parsedInitMessage := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: fee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedChainConfig, + } return parsedInitMessage, nil } From 850f48d2b646be84a5a585bac188f94ea176f65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:16:32 +0100 Subject: [PATCH 02/39] into 3 variants --- cmd/nitro/init/init.go | 57 ++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index e33fb5a582e..086384e500e 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -628,9 +628,9 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co return nil, nil, nil, err } - var genesisArbOSInit *params.ArbOSInit + var genesis *core.Genesis - initDataReader, chainConfig, genesisArbOSInit, err = GetInit(config, executionDB) + initDataReader, chainConfig, genesis, err = GetInit(config, executionDB) if err != nil { return executionDB, nil, nil, err } @@ -638,14 +638,16 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co var parsedInitMessage *arbostypes.ParsedInitMessage if config.Node.ParentChainReader.Enable { parsedInitMessage, err = GetParsedInitMsgFromConsensus(ctx, chainId, l1Client, &rollupAddrs, chainConfig) + } else if genesis != nil { + parsedInitMessage, err = GetParsedInitMsgFromGenesis(genesis) } else { - parsedInitMessage, err = GetParsedInitMsgFromConfig(chainConfig, genesisArbOSInit) + parsedInitMessage, err = GetParsedInitMsgFromChainConfig(chainConfig) } if err != nil { return executionDB, nil, nil, err } - l2BlockChain, err = getNewBlockchain(parsedInitMessage, config, initDataReader, chainConfig, genesisArbOSInit, executionDB, cacheConfig, tracer) + l2BlockChain, err = getNewBlockchain(parsedInitMessage, config, initDataReader, chainConfig, genesis.ArbOSInit, executionDB, cacheConfig, tracer) if err != nil { return executionDB, nil, nil, err } @@ -690,12 +692,12 @@ func pruneExecutionDB(ctx context.Context, executionDB ethdb.Database, stack *no return pruning.PruneExecutionDB(ctx, executionDB, stack, &config.Init, cacheConfig, persistentConfig, l1Client, rollupAddrs, config.Node.ValidatorRequired(), false) } -func GetInit(config *config.NodeConfig, executionDB ethdb.Database) (statetransfer.InitDataReader, *params.ChainConfig, *params.ArbOSInit, error) { +func GetInit(config *config.NodeConfig, executionDB ethdb.Database) (statetransfer.InitDataReader, *params.ChainConfig, *core.Genesis, error) { var ( - initDataReader statetransfer.InitDataReader - chainConfig *params.ChainConfig - genesisArbOSInit *params.ArbOSInit - err error + initDataReader statetransfer.InitDataReader + chainConfig *params.ChainConfig + genesis *core.Genesis + err error ) if config.Init.ImportFile != "" { @@ -747,12 +749,11 @@ func GetInit(config *config.NodeConfig, executionDB ethdb.Database) (statetransf if err != nil { return nil, nil, nil, err } - var gen core.Genesis - if err := json.Unmarshal(genesisJson, &gen); err != nil { + if err := json.Unmarshal(genesisJson, &genesis); err != nil { return nil, nil, nil, err } var accounts []statetransfer.AccountInitializationInfo - for address, account := range gen.Alloc { + for address, account := range genesis.Alloc { accounts = append(accounts, statetransfer.AccountInitializationInfo{ Addr: address, EthBalance: account.Balance, @@ -766,11 +767,10 @@ func GetInit(config *config.NodeConfig, executionDB ethdb.Database) (statetransf initDataReader = statetransfer.NewMemoryInitDataReader(&statetransfer.ArbosInitializationInfo{ Accounts: accounts, }) - chainConfig, err = gen.GetConfig() + chainConfig, err = genesis.GetConfig() if err != nil { return nil, nil, nil, err } - genesisArbOSInit = gen.ArbOSInit } else { if initDataReader == nil { chainConfig = gethexec.TryReadStoredChainConfig(executionDB) @@ -793,7 +793,7 @@ func GetInit(config *config.NodeConfig, executionDB ethdb.Database) (statetransf chainConfig.ArbitrumChainParams.MaxCodeSize = config.Init.DevMaxCodeSize } - return initDataReader, chainConfig, genesisArbOSInit, nil + return initDataReader, chainConfig, genesis, nil } func GetGenesisFileNameFromDirectory(genesisFileDirectory string, chainId uint64) (string, error) { @@ -1075,20 +1075,35 @@ func GetParsedInitMsgFromConsensus(ctx context.Context, chainId *big.Int, l1Clie return parsedInitMessage, nil } -func GetParsedInitMsgFromConfig(chainConfig *params.ChainConfig, genesisArbOSInit *params.ArbOSInit) (*arbostypes.ParsedInitMessage, error) { - serializedChainConfig, err := json.Marshal(chainConfig) +func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitMessage, error) { + chainConfig, err := genesis.GetConfig() if err != nil { return nil, err } - fee := genesisArbOSInit.InitialL1BaseFee - if fee == nil { - fee = arbostypes.DefaultInitialL1BaseFee + initialL1BaseFee := genesis.ArbOSInit.InitialL1BaseFee + if initialL1BaseFee == nil { + initialL1BaseFee = arbostypes.DefaultInitialL1BaseFee + } + + parsedInitMessage := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: initialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: []byte(genesis.SerializedChainConfig), + } + return parsedInitMessage, nil +} + +func GetParsedInitMsgFromChainConfig(chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { + serializedChainConfig, err := json.Marshal(chainConfig) + if err != nil { + return nil, err } parsedInitMessage := &arbostypes.ParsedInitMessage{ ChainId: chainConfig.ChainID, - InitialL1BaseFee: fee, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, ChainConfig: chainConfig, SerializedChainConfig: serializedChainConfig, } From 911e97caddf7b78324167f5efaa6c39008bb027a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:18:29 +0100 Subject: [PATCH 03/39] Adapt system tests that were just reading msg from consensus --- system_tests/bold_challenge_protocol_test.go | 2 +- system_tests/bold_customda_challenge_test.go | 2 +- system_tests/genesis_assertion_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/system_tests/bold_challenge_protocol_test.go b/system_tests/bold_challenge_protocol_test.go index e9f2fb5e379..2edb34af3c7 100644 --- a/system_tests/bold_challenge_protocol_test.go +++ b/system_tests/bold_challenge_protocol_test.go @@ -594,7 +594,7 @@ func create2ndNodeWithConfigForBoldProtocol( txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) Require(t, err) execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) diff --git a/system_tests/bold_customda_challenge_test.go b/system_tests/bold_customda_challenge_test.go index 7d7e10d5ee5..71d011e0bd7 100644 --- a/system_tests/bold_customda_challenge_test.go +++ b/system_tests/bold_customda_challenge_test.go @@ -203,7 +203,7 @@ func createNodeBWithSharedContracts( txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) Require(t, err) execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) diff --git a/system_tests/genesis_assertion_test.go b/system_tests/genesis_assertion_test.go index 75ee65e5472..92e478da028 100644 --- a/system_tests/genesis_assertion_test.go +++ b/system_tests/genesis_assertion_test.go @@ -289,7 +289,7 @@ func createL2NodeWithRollupAddresses( Require(t, execConfig.Validate()) stackConfig := testhelpers.CreateStackConfigForTest("") stackConfig.DBEngine = rawdb.DBPebble - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, l2infoIn.Signer.ChainID(), l1client, addresses, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, l2infoIn.Signer.ChainID(), l1client, addresses, chainConfig) Require(t, err) var l2executionDB ethdb.Database From 3a8478318f47bda3731e9c614d37017219319168 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:27:11 +0100 Subject: [PATCH 04/39] Adapt tests --- cmd/nitro/init/init.go | 6 +++--- system_tests/common_test.go | 2 +- system_tests/nitro_init_test.go | 28 ++++++++++++++++++++-------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 086384e500e..0f4ccd1cccd 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1081,9 +1081,9 @@ func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitM return nil, err } - initialL1BaseFee := genesis.ArbOSInit.InitialL1BaseFee - if initialL1BaseFee == nil { - initialL1BaseFee = arbostypes.DefaultInitialL1BaseFee + initialL1BaseFee := arbostypes.DefaultInitialL1BaseFee + if genesis.ArbOSInit != nil && genesis.ArbOSInit.InitialL1BaseFee == nil { + initialL1BaseFee = genesis.ArbOSInit.InitialL1BaseFee } parsedInitMessage := &arbostypes.ParsedInitMessage{ diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 47288ecd676..dc8adfaefdd 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -1986,7 +1986,7 @@ func deployOnParentChain( parentChainInfo.SetContract("SequencerInbox", addresses.SequencerInbox) parentChainInfo.SetContract("Inbox", addresses.Inbox) parentChainInfo.SetContract("UpgradeExecutor", addresses.UpgradeExecutor) - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, chainConfig.ChainID, parentChainClient, addresses, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, chainConfig.ChainID, parentChainClient, addresses, chainConfig) Require(t, err) return addresses, initMessage diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index 10af937b5a6..5bf10a20a67 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -8,18 +8,17 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" - "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/nitro/config" "github.com/offchainlabs/nitro/cmd/nitro/init" "github.com/offchainlabs/nitro/execution/gethexec" ) -func TestGetConsensusParsedInitMsgNoParentChain(t *testing.T) { +func TestGetParsedInitMsgWithoutConsensus(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -27,9 +26,6 @@ func TestGetConsensusParsedInitMsgNoParentChain(t *testing.T) { cleanup := builder.Build(t) defer cleanup() - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, false, builder.chainConfig.ChainID, nil, &chaininfo.RollupAddresses{}, builder.chainConfig) - Require(t, err) - serializedChainConfig, err := json.Marshal(builder.chainConfig) Require(t, err) @@ -41,12 +37,28 @@ func TestGetConsensusParsedInitMsgNoParentChain(t *testing.T) { SerializedChainConfig: serializedChainConfig, } + // 1. From Genesis + genesis := &core.Genesis{ + SerializedChainConfig: string(serializedChainConfig), + } + + initMessage, err := nitroinit.GetParsedInitMsgFromGenesis(genesis) + Require(t, err) + + if success := reflect.DeepEqual(initMessage, expectedInitMessage); !success { + t.Fatalf("diff found in initMessage %v and builder.initMessage: %v", initMessage, builder.initMessage) + } + + // 2. Directly from chain config + initMessage, err = nitroinit.GetParsedInitMsgFromChainConfig(builder.chainConfig) + Require(t, err) + if success := reflect.DeepEqual(initMessage, expectedInitMessage); !success { t.Fatalf("diff found in initMessage %v and builder.initMessage: %v", initMessage, builder.initMessage) } } -func TestGetConsensusParsedInitMsgWithParentChain(t *testing.T) { +func TestGetParsedInitMsgFromConsensus(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -55,7 +67,7 @@ func TestGetConsensusParsedInitMsgWithParentChain(t *testing.T) { cleanup := builder.Build(t) defer cleanup() - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, builder.chainConfig.ChainID, builder.L1.Client, builder.addresses, builder.chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, builder.chainConfig.ChainID, builder.L1.Client, builder.addresses, builder.chainConfig) Require(t, err) if success := reflect.DeepEqual(initMessage, builder.initMessage); !success { From c31155ded3fb7f6be4e67dab8ec69840360b5568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:28:59 +0100 Subject: [PATCH 05/39] Small fix --- cmd/nitro/init/init_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index c7abe935fec..1f3fa04a49b 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1080,8 +1080,8 @@ func getInitHelper(t *testing.T, ownerAdress string, chainID uint64, emptyState // We already call getInit once inside openInitializeExecutionDB but calling a // second time is okay since we're just loading configs - initDataReader, chainConfig, arbOsInit, err := GetInit(&nodeConfig, executionDB) - return initDataReader, chainConfig, arbOsInit, executionDB, cleanup, err + initDataReader, chainConfig, genesis, err := GetInit(&nodeConfig, executionDB) + return initDataReader, chainConfig, genesis.ArbOSInit, executionDB, cleanup, err } From ccbb0957b92bd7b324a918f6fdb7f25ade3e9289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:29:04 +0100 Subject: [PATCH 06/39] Changelog --- changelog/pmikolajczyk-nit-4242.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/pmikolajczyk-nit-4242.md diff --git a/changelog/pmikolajczyk-nit-4242.md b/changelog/pmikolajczyk-nit-4242.md new file mode 100644 index 00000000000..6568ce30fca --- /dev/null +++ b/changelog/pmikolajczyk-nit-4242.md @@ -0,0 +1,2 @@ +### Added + - Nitro can restore parsed init message from genesis, without access to the consensus side \ No newline at end of file From a09bba951903ce1af516bdb505ddcaf6e3ba99f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 11:29:14 +0100 Subject: [PATCH 07/39] fmt --- system_tests/nitro_init_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index 5bf10a20a67..bfaf8c3861f 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -12,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" + "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/cmd/nitro/config" "github.com/offchainlabs/nitro/cmd/nitro/init" From df0a54ede2417f9454021eb510f9af43c3119ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 12:46:16 +0100 Subject: [PATCH 08/39] Fix getinithelper --- cmd/nitro/init/init_test.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 1f3fa04a49b..50f76dbce08 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1081,7 +1081,13 @@ func getInitHelper(t *testing.T, ownerAdress string, chainID uint64, emptyState // We already call getInit once inside openInitializeExecutionDB but calling a // second time is okay since we're just loading configs initDataReader, chainConfig, genesis, err := GetInit(&nodeConfig, executionDB) - return initDataReader, chainConfig, genesis.ArbOSInit, executionDB, cleanup, err + + var arbOsInit *params.ArbOSInit + if genesis != nil { + arbOsInit = genesis.ArbOSInit + } + + return initDataReader, chainConfig, arbOsInit, executionDB, cleanup, err } From 61f1e76d52ea453a86e0f7dc59379d765e5b2bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 12:51:19 +0100 Subject: [PATCH 09/39] Fix nilpointers --- cmd/nitro/init/init.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 0f4ccd1cccd..f21f11d8c48 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -647,7 +647,12 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co return executionDB, nil, nil, err } - l2BlockChain, err = getNewBlockchain(parsedInitMessage, config, initDataReader, chainConfig, genesis.ArbOSInit, executionDB, cacheConfig, tracer) + var arbosInit *params.ArbOSInit + if genesis != nil { + arbosInit = genesis.ArbOSInit + } + + l2BlockChain, err = getNewBlockchain(parsedInitMessage, config, initDataReader, chainConfig, arbosInit, executionDB, cacheConfig, tracer) if err != nil { return executionDB, nil, nil, err } From 96f46feb1612b13b9a474c6cb2e3a29fd155c176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Tue, 17 Feb 2026 15:23:05 +0100 Subject: [PATCH 10/39] typo --- cmd/nitro/init/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index f21f11d8c48..c2d6b102cb6 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1087,7 +1087,7 @@ func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitM } initialL1BaseFee := arbostypes.DefaultInitialL1BaseFee - if genesis.ArbOSInit != nil && genesis.ArbOSInit.InitialL1BaseFee == nil { + if genesis.ArbOSInit != nil && genesis.ArbOSInit.InitialL1BaseFee != nil { initialL1BaseFee = genesis.ArbOSInit.InitialL1BaseFee } From 47d24149414467c02f650b42d48f9dda820d262c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 25 Feb 2026 13:47:52 +0100 Subject: [PATCH 11/39] Add GenesisOverride substruct --- cmd/conf/init.go | 100 ++++++++++++----- cmd/nitro/init/init.go | 48 ++++++++ cmd/nitro/init/init_test.go | 218 ++++++++++++++++++++++++++++++++++++ 3 files changed, 337 insertions(+), 29 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 50b1a083a95..cbdf4fed1f6 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -4,6 +4,7 @@ package conf import ( "fmt" + "math/big" "slices" "strings" "time" @@ -15,36 +16,72 @@ import ( "github.com/offchainlabs/nitro/util" ) +type GenesisOverride struct { + SerializedChainConfig string `koanf:"serialized-chain-config"` + InitialL1BaseFee string `koanf:"initial-l1-base-fee"` +} + +var GenesisOverrideDefault = GenesisOverride{ + SerializedChainConfig: "", + InitialL1BaseFee: "", +} + +func GenesisOverrideAddOptions(prefix string, f *pflag.FlagSet) { + f.String(prefix+".serialized-chain-config", GenesisOverrideDefault.SerializedChainConfig, "JSON-serialized chain config, can be used to provide chain config when genesis.json is not available") + f.String(prefix+".initial-l1-base-fee", GenesisOverrideDefault.InitialL1BaseFee, "initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, use default)") +} + +// ParseInitialL1BaseFee parses the InitialL1BaseFee string into a *big.Int. +// Returns nil if the string is empty (not set). +func (c *GenesisOverride) ParseInitialL1BaseFee() (*big.Int, error) { + if c.InitialL1BaseFee == "" { + return nil, nil + } + fee, ok := new(big.Int).SetString(c.InitialL1BaseFee, 10) + if !ok { + return nil, fmt.Errorf("failed to parse initial-l1-base-fee %q as integer", c.InitialL1BaseFee) + } + if fee.Sign() < 0 { + return nil, fmt.Errorf("initial-l1-base-fee must be non-negative, got %s", c.InitialL1BaseFee) + } + return fee, nil +} + +func (c *GenesisOverride) IsSet() bool { + return c.SerializedChainConfig != "" // initial L1 base fee has a fallback +} + type InitConfig struct { - Force bool `koanf:"force"` - Url string `koanf:"url"` - Latest string `koanf:"latest"` - LatestBase string `koanf:"latest-base"` - ValidateChecksum bool `koanf:"validate-checksum"` - DownloadPath string `koanf:"download-path"` - DownloadPoll time.Duration `koanf:"download-poll"` - DevInit bool `koanf:"dev-init"` - DevInitAddress string `koanf:"dev-init-address"` - DevMaxCodeSize uint64 `koanf:"dev-max-code-size"` - DevInitBlockNum uint64 `koanf:"dev-init-blocknum"` - Empty bool `koanf:"empty"` - ImportWasm bool `koanf:"import-wasm"` - AccountsPerSync uint `koanf:"accounts-per-sync"` - ImportFile string `koanf:"import-file"` - GenesisJsonFile string `koanf:"genesis-json-file"` - GenesisJsonFileDirectory string `koanf:"genesis-json-file-directory"` - ThenQuit bool `koanf:"then-quit"` - Prune string `koanf:"prune"` - PruneParallelStorageTraversal bool `koanf:"prune-parallel-storage-traversal"` - PruneBloomSize uint64 `koanf:"prune-bloom-size"` - PruneThreads int `koanf:"prune-threads"` - PruneTrieCleanCache int `koanf:"prune-trie-clean-cache"` - RecreateMissingStateFrom uint64 `koanf:"recreate-missing-state-from"` - RebuildLocalWasm string `koanf:"rebuild-local-wasm"` - ReorgToBatch int64 `koanf:"reorg-to-batch"` - ReorgToMessageBatch int64 `koanf:"reorg-to-message-batch"` - ReorgToBlockBatch int64 `koanf:"reorg-to-block-batch"` - ValidateGenesisAssertion bool `koanf:"validate-genesis-assertion"` + Force bool `koanf:"force"` + Url string `koanf:"url"` + Latest string `koanf:"latest"` + LatestBase string `koanf:"latest-base"` + ValidateChecksum bool `koanf:"validate-checksum"` + DownloadPath string `koanf:"download-path"` + DownloadPoll time.Duration `koanf:"download-poll"` + DevInit bool `koanf:"dev-init"` + DevInitAddress string `koanf:"dev-init-address"` + DevMaxCodeSize uint64 `koanf:"dev-max-code-size"` + DevInitBlockNum uint64 `koanf:"dev-init-blocknum"` + Empty bool `koanf:"empty"` + ImportWasm bool `koanf:"import-wasm"` + AccountsPerSync uint `koanf:"accounts-per-sync"` + ImportFile string `koanf:"import-file"` + GenesisJsonFile string `koanf:"genesis-json-file"` + GenesisJsonFileDirectory string `koanf:"genesis-json-file-directory"` + GenesisOverride GenesisOverride `koanf:"genesis-override"` + ThenQuit bool `koanf:"then-quit"` + Prune string `koanf:"prune"` + PruneParallelStorageTraversal bool `koanf:"prune-parallel-storage-traversal"` + PruneBloomSize uint64 `koanf:"prune-bloom-size"` + PruneThreads int `koanf:"prune-threads"` + PruneTrieCleanCache int `koanf:"prune-trie-clean-cache"` + RecreateMissingStateFrom uint64 `koanf:"recreate-missing-state-from"` + RebuildLocalWasm string `koanf:"rebuild-local-wasm"` + ReorgToBatch int64 `koanf:"reorg-to-batch"` + ReorgToMessageBatch int64 `koanf:"reorg-to-message-batch"` + ReorgToBlockBatch int64 `koanf:"reorg-to-block-batch"` + ValidateGenesisAssertion bool `koanf:"validate-genesis-assertion"` } var InitConfigDefault = InitConfig{ @@ -64,6 +101,7 @@ var InitConfigDefault = InitConfig{ ImportFile: "", GenesisJsonFile: "", GenesisJsonFileDirectory: "", + GenesisOverride: GenesisOverrideDefault, AccountsPerSync: 100000, ThenQuit: false, Prune: "", @@ -97,6 +135,7 @@ func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".import-file", InitConfigDefault.ImportFile, "path for json data to import") f.String(prefix+".genesis-json-file", InitConfigDefault.GenesisJsonFile, "path for genesis json file") f.String(prefix+".genesis-json-file-directory", InitConfigDefault.GenesisJsonFileDirectory, "directory path for genesis json files - will search for a file named by the chain ID") + GenesisOverrideAddOptions(prefix+".genesis-override", f) f.Uint(prefix+".accounts-per-sync", InitConfigDefault.AccountsPerSync, "during init - sync database every X accounts. Lower value for low-memory systems. 0 disables.") f.String(prefix+".prune", InitConfigDefault.Prune, "pruning for a given use: \"full\" for full nodes serving RPC requests, or \"validator\" for validators") f.Bool(prefix+".prune-parallel-storage-traversal", InitConfigDefault.PruneParallelStorageTraversal, "if true: use parallel pruning per account") @@ -119,6 +158,9 @@ func (c *InitConfig) Validate() error { if c.Empty && c.GenesisJsonFile != "" { return fmt.Errorf("init config cannot be both empty and have a genesis json file specified") } + if _, err := c.GenesisOverride.ParseInitialL1BaseFee(); err != nil { + return fmt.Errorf("invalid init.genesis-override: %w", err) + } if c.Force && c.RecreateMissingStateFrom > 0 { log.Warn("force init enabled, recreate-missing-state-from will have no effect") } diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index c2d6b102cb6..fa8581b8c05 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -4,6 +4,7 @@ package nitroinit import ( + "bytes" "context" "crypto/sha256" "encoding/hex" @@ -640,6 +641,17 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co parsedInitMessage, err = GetParsedInitMsgFromConsensus(ctx, chainId, l1Client, &rollupAddrs, chainConfig) } else if genesis != nil { parsedInitMessage, err = GetParsedInitMsgFromGenesis(genesis) + if err == nil && config.Init.GenesisOverride.IsSet() { + overrideMsg, overrideErr := GetParsedInitMsgFromGenesisOverride(&config.Init.GenesisOverride) + if overrideErr != nil { + return executionDB, nil, nil, fmt.Errorf("error parsing genesis override: %w", overrideErr) + } + if err := validateParsedInitMessagesMatch(parsedInitMessage, overrideMsg); err != nil { + return executionDB, nil, nil, fmt.Errorf("genesis and genesis override mismatch: %w", err) + } + } + } else if config.Init.GenesisOverride.IsSet() { + parsedInitMessage, err = GetParsedInitMsgFromGenesisOverride(&config.Init.GenesisOverride) } else { parsedInitMessage, err = GetParsedInitMsgFromChainConfig(chainConfig) } @@ -1100,6 +1112,29 @@ func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitM return parsedInitMessage, nil } +func GetParsedInitMsgFromGenesisOverride(genesisOverride *conf.GenesisOverride) (*arbostypes.ParsedInitMessage, error) { + var chainConfig params.ChainConfig + if err := json.Unmarshal([]byte(genesisOverride.SerializedChainConfig), &chainConfig); err != nil { + return nil, fmt.Errorf("failed to deserialize chain config from genesis override: %w", err) + } + + initialL1BaseFee := arbostypes.DefaultInitialL1BaseFee + fee, err := genesisOverride.ParseInitialL1BaseFee() + if err != nil { + return nil, err + } + if fee != nil { + initialL1BaseFee = fee + } + + return &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: initialL1BaseFee, + ChainConfig: &chainConfig, + SerializedChainConfig: []byte(genesisOverride.SerializedChainConfig), + }, nil +} + func GetParsedInitMsgFromChainConfig(chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { serializedChainConfig, err := json.Marshal(chainConfig) if err != nil { @@ -1115,6 +1150,19 @@ func GetParsedInitMsgFromChainConfig(chainConfig *params.ChainConfig) (*arbostyp return parsedInitMessage, nil } +func validateParsedInitMessagesMatch(a, b *arbostypes.ParsedInitMessage) error { + if a.ChainId.Cmp(b.ChainId) != 0 { + return fmt.Errorf("chain ID mismatch: %v vs %v", a.ChainId, b.ChainId) + } + if a.InitialL1BaseFee.Cmp(b.InitialL1BaseFee) != 0 { + return fmt.Errorf("initial L1 base fee mismatch: %v vs %v", a.InitialL1BaseFee, b.InitialL1BaseFee) + } + if !bytes.Equal(a.SerializedChainConfig, b.SerializedChainConfig) { + return fmt.Errorf("serialized chain config mismatch: %s vs %s", string(a.SerializedChainConfig), string(b.SerializedChainConfig)) + } + return nil +} + func getGenesisAssertionCreationInfo(ctx context.Context, rollupAddress common.Address, l1Client *ethclient.Client, genesisHash common.Hash, sendRoot common.Hash) (*protocol.AssertionCreatedInfo, [32]byte, bool, error) { var assertionHash [32]byte diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 50f76dbce08..a1b4ed110e3 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/nitro/config" @@ -1364,3 +1365,220 @@ func gracefulShutdown(t *testing.T, ctx context.Context, server *http.Server) { t.Logf("HTTP server shutdown error: %v", err) } } + +func testChainConfigJSON(t *testing.T) string { + t.Helper() + chainConfig := chaininfo.ArbitrumDevTestChainConfig() + serialized, err := json.Marshal(chainConfig) + require.NoError(t, err) + return string(serialized) +} + +func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { + chainConfig := chaininfo.ArbitrumDevTestChainConfig() + serializedConfig := testChainConfigJSON(t) + + t.Run("with default initial L1 base fee", func(t *testing.T) { + override := &conf.GenesisOverride{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: "", + } + msg, err := GetParsedInitMsgFromGenesisOverride(override) + require.NoError(t, err) + require.Equal(t, chainConfig.ChainID, msg.ChainId) + require.Equal(t, arbostypes.DefaultInitialL1BaseFee, msg.InitialL1BaseFee) + require.Equal(t, serializedConfig, string(msg.SerializedChainConfig)) + require.NotNil(t, msg.ChainConfig) + }) + + t.Run("with custom initial L1 base fee", func(t *testing.T) { + override := &conf.GenesisOverride{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: "100000000000", // 100 GWei + } + msg, err := GetParsedInitMsgFromGenesisOverride(override) + require.NoError(t, err) + require.Equal(t, big.NewInt(100_000_000_000), msg.InitialL1BaseFee) + }) + + t.Run("with large initial L1 base fee", func(t *testing.T) { + largeFee := "999999999999999999999999999999" + override := &conf.GenesisOverride{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: largeFee, + } + msg, err := GetParsedInitMsgFromGenesisOverride(override) + require.NoError(t, err) + expected, _ := new(big.Int).SetString(largeFee, 10) + require.Equal(t, expected, msg.InitialL1BaseFee) + }) + + t.Run("with invalid chain config JSON", func(t *testing.T) { + override := &conf.GenesisOverride{ + SerializedChainConfig: "not-valid-json", + } + _, err := GetParsedInitMsgFromGenesisOverride(override) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to deserialize chain config from genesis override") + }) + + t.Run("with invalid initial L1 base fee", func(t *testing.T) { + override := &conf.GenesisOverride{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: "not-a-number", + } + _, err := GetParsedInitMsgFromGenesisOverride(override) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to parse initial-l1-base-fee") + }) +} + +func TestGetParsedInitMsgFromGenesis(t *testing.T) { + serializedConfig := testChainConfigJSON(t) + + t.Run("with ArbOSInit initial L1 base fee", func(t *testing.T) { + customFee := big.NewInt(100_000_000_000) + genesis := &core.Genesis{ + SerializedChainConfig: serializedConfig, + ArbOSInit: ¶ms.ArbOSInit{ + InitialL1BaseFee: customFee, + }, + } + msg, err := GetParsedInitMsgFromGenesis(genesis) + require.NoError(t, err) + require.Equal(t, customFee, msg.InitialL1BaseFee) + }) + + t.Run("without ArbOSInit uses default", func(t *testing.T) { + genesis := &core.Genesis{ + SerializedChainConfig: serializedConfig, + } + msg, err := GetParsedInitMsgFromGenesis(genesis) + require.NoError(t, err) + require.Equal(t, arbostypes.DefaultInitialL1BaseFee, msg.InitialL1BaseFee) + }) +} + +func TestValidateParsedInitMessagesMatch(t *testing.T) { + chainConfig := chaininfo.ArbitrumDevTestChainConfig() + serializedConfig, err := json.Marshal(chainConfig) + require.NoError(t, err) + + baseMsg := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + + t.Run("matching messages", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + require.NoError(t, validateParsedInitMessagesMatch(baseMsg, other)) + }) + + t.Run("chain ID mismatch", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: big.NewInt(999999), + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + err := validateParsedInitMessagesMatch(baseMsg, other) + require.Error(t, err) + require.Contains(t, err.Error(), "chain ID mismatch") + }) + + t.Run("initial L1 base fee mismatch", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: big.NewInt(123), + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + err := validateParsedInitMessagesMatch(baseMsg, other) + require.Error(t, err) + require.Contains(t, err.Error(), "initial L1 base fee mismatch") + }) + + t.Run("serialized chain config mismatch", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: []byte(`{"chainId":999}`), + } + err := validateParsedInitMessagesMatch(baseMsg, other) + require.Error(t, err) + require.Contains(t, err.Error(), "serialized chain config mismatch") + }) +} + +func TestGenesisOverride(t *testing.T) { + t.Run("IsSet returns false for defaults", func(t *testing.T) { + c := conf.GenesisOverrideDefault + require.False(t, c.IsSet()) + }) + + t.Run("IsSet returns true when serialized chain config is set", func(t *testing.T) { + c := conf.GenesisOverride{ + SerializedChainConfig: `{"chainId": 1}`, + } + require.True(t, c.IsSet()) + }) + + t.Run("IsSet returns false when only initial L1 base fee is set", func(t *testing.T) { + c := conf.GenesisOverride{ + InitialL1BaseFee: "0", + } + require.False(t, c.IsSet()) + }) + + t.Run("ParseInitialL1BaseFee returns nil when not set", func(t *testing.T) { + c := conf.GenesisOverride{} + fee, err := c.ParseInitialL1BaseFee() + require.NoError(t, err) + require.Nil(t, fee) + }) + + t.Run("ParseInitialL1BaseFee returns value when set", func(t *testing.T) { + c := conf.GenesisOverride{InitialL1BaseFee: "50000000000"} + fee, err := c.ParseInitialL1BaseFee() + require.NoError(t, err) + require.Equal(t, big.NewInt(50_000_000_000), fee) + }) + + t.Run("ParseInitialL1BaseFee returns zero when explicitly set to zero", func(t *testing.T) { + c := conf.GenesisOverride{InitialL1BaseFee: "0"} + fee, err := c.ParseInitialL1BaseFee() + require.NoError(t, err) + require.Equal(t, big.NewInt(0), fee) + }) + + t.Run("ParseInitialL1BaseFee supports large values", func(t *testing.T) { + largeFee := "999999999999999999999999999999" + c := conf.GenesisOverride{InitialL1BaseFee: largeFee} + fee, err := c.ParseInitialL1BaseFee() + require.NoError(t, err) + expected, _ := new(big.Int).SetString(largeFee, 10) + require.Equal(t, expected, fee) + }) + + t.Run("ParseInitialL1BaseFee errors on invalid string", func(t *testing.T) { + c := conf.GenesisOverride{InitialL1BaseFee: "not-a-number"} + _, err := c.ParseInitialL1BaseFee() + require.Error(t, err) + require.Contains(t, err.Error(), "failed to parse initial-l1-base-fee") + }) + + t.Run("ParseInitialL1BaseFee errors on negative value", func(t *testing.T) { + c := conf.GenesisOverride{InitialL1BaseFee: "-1"} + _, err := c.ParseInitialL1BaseFee() + require.Error(t, err) + require.Contains(t, err.Error(), "must be non-negative") + }) +} From b0d9fbd3d9c90b23fd7f57472a1d852109c6cef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 25 Feb 2026 13:51:59 +0100 Subject: [PATCH 12/39] s/TestGetParsedInitMsgFromConsensus/TestGetParsedInitMsgFromParentChain --- cmd/nitro/init/init.go | 4 ++-- system_tests/bold_challenge_protocol_test.go | 2 +- system_tests/bold_customda_challenge_test.go | 2 +- system_tests/common_test.go | 2 +- system_tests/genesis_assertion_test.go | 2 +- system_tests/nitro_init_test.go | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index fa8581b8c05..7f59189f365 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -638,7 +638,7 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co var parsedInitMessage *arbostypes.ParsedInitMessage if config.Node.ParentChainReader.Enable { - parsedInitMessage, err = GetParsedInitMsgFromConsensus(ctx, chainId, l1Client, &rollupAddrs, chainConfig) + parsedInitMessage, err = GetParsedInitMsgFromParentChain(ctx, chainId, l1Client, &rollupAddrs, chainConfig) } else if genesis != nil { parsedInitMessage, err = GetParsedInitMsgFromGenesis(genesis) if err == nil && config.Init.GenesisOverride.IsSet() { @@ -1055,7 +1055,7 @@ func OpenExistingExecutionDB(stack *node.Node, config *config.NodeConfig, chainI return nil, nil, nil, nil, nil } -func GetParsedInitMsgFromConsensus(ctx context.Context, chainId *big.Int, l1Client *ethclient.Client, rollupAddrs *chaininfo.RollupAddresses, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { +func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Client *ethclient.Client, rollupAddrs *chaininfo.RollupAddresses, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { var parsedInitMessage *arbostypes.ParsedInitMessage delayedBridge, err := arbnode.NewDelayedBridge(l1Client, rollupAddrs.Bridge, rollupAddrs.DeployedAt) if err != nil { diff --git a/system_tests/bold_challenge_protocol_test.go b/system_tests/bold_challenge_protocol_test.go index 2edb34af3c7..47a8640b440 100644 --- a/system_tests/bold_challenge_protocol_test.go +++ b/system_tests/bold_challenge_protocol_test.go @@ -594,7 +594,7 @@ func create2ndNodeWithConfigForBoldProtocol( txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) Require(t, err) execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) diff --git a/system_tests/bold_customda_challenge_test.go b/system_tests/bold_customda_challenge_test.go index 71d011e0bd7..e24c132e41f 100644 --- a/system_tests/bold_customda_challenge_test.go +++ b/system_tests/bold_customda_challenge_test.go @@ -203,7 +203,7 @@ func createNodeBWithSharedContracts( txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) Require(t, err) execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index dc8adfaefdd..1b4c664f7c4 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -1986,7 +1986,7 @@ func deployOnParentChain( parentChainInfo.SetContract("SequencerInbox", addresses.SequencerInbox) parentChainInfo.SetContract("Inbox", addresses.Inbox) parentChainInfo.SetContract("UpgradeExecutor", addresses.UpgradeExecutor) - initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, chainConfig.ChainID, parentChainClient, addresses, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, parentChainClient, addresses, chainConfig) Require(t, err) return addresses, initMessage diff --git a/system_tests/genesis_assertion_test.go b/system_tests/genesis_assertion_test.go index 92e478da028..4c0a6ac0aad 100644 --- a/system_tests/genesis_assertion_test.go +++ b/system_tests/genesis_assertion_test.go @@ -289,7 +289,7 @@ func createL2NodeWithRollupAddresses( Require(t, execConfig.Validate()) stackConfig := testhelpers.CreateStackConfigForTest("") stackConfig.DBEngine = rawdb.DBPebble - initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, l2infoIn.Signer.ChainID(), l1client, addresses, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, l2infoIn.Signer.ChainID(), l1client, addresses, chainConfig) Require(t, err) var l2executionDB ethdb.Database diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index bfaf8c3861f..ef2251e5cb1 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -59,7 +59,7 @@ func TestGetParsedInitMsgWithoutConsensus(t *testing.T) { } } -func TestGetParsedInitMsgFromConsensus(t *testing.T) { +func TestGetParsedInitMsgFromParentChain(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -68,7 +68,7 @@ func TestGetParsedInitMsgFromConsensus(t *testing.T) { cleanup := builder.Build(t) defer cleanup() - initMessage, err := nitroinit.GetParsedInitMsgFromConsensus(ctx, builder.chainConfig.ChainID, builder.L1.Client, builder.addresses, builder.chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, builder.chainConfig.ChainID, builder.L1.Client, builder.addresses, builder.chainConfig) Require(t, err) if success := reflect.DeepEqual(initMessage, builder.initMessage); !success { From 629e9f8b31441369c4a228a56acafda5c2d850da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 25 Feb 2026 15:05:51 +0100 Subject: [PATCH 13/39] Fix the fetching and comparing flow --- cmd/nitro/init/init.go | 68 +++++++++++++------- cmd/nitro/init/init_test.go | 5 ++ cmd/nitro/nitro.go | 13 +++- system_tests/bold_challenge_protocol_test.go | 2 +- system_tests/bold_customda_challenge_test.go | 2 +- system_tests/common_test.go | 2 +- system_tests/genesis_assertion_test.go | 2 +- system_tests/nitro_init_test.go | 2 +- 8 files changed, 66 insertions(+), 30 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 7f59189f365..dea586f2b4b 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -610,7 +610,7 @@ func rebuildLocalWasm(ctx context.Context, config *gethexec.Config, l2BlockChain return executionDB, l2BlockChain, nil } -func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *config.NodeConfig, chainId *big.Int, cacheConfig *core.BlockChainConfig, tracer *tracing.Hooks, persistentConfig *conf.PersistentConfig, l1Client *ethclient.Client, rollupAddrs chaininfo.RollupAddresses) (ethdb.Database, statetransfer.InitDataReader, *core.BlockChain, error) { +func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *config.NodeConfig, chainId *big.Int, cacheConfig *core.BlockChainConfig, tracer *tracing.Hooks, persistentConfig *conf.PersistentConfig, l1Client *ethclient.Client, rollupAddrs chaininfo.RollupAddresses, consensusParsedInitMessage *arbostypes.ParsedInitMessage) (ethdb.Database, statetransfer.InitDataReader, *core.BlockChain, error) { executionDB, wasmDB, l2BlockChain, chainConfig, err := OpenExistingExecutionDB(stack, config, chainId, cacheConfig, tracer, persistentConfig) if err != nil { return nil, nil, nil, err @@ -636,29 +636,22 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co return executionDB, nil, nil, err } - var parsedInitMessage *arbostypes.ParsedInitMessage - if config.Node.ParentChainReader.Enable { - parsedInitMessage, err = GetParsedInitMsgFromParentChain(ctx, chainId, l1Client, &rollupAddrs, chainConfig) - } else if genesis != nil { - parsedInitMessage, err = GetParsedInitMsgFromGenesis(genesis) - if err == nil && config.Init.GenesisOverride.IsSet() { - overrideMsg, overrideErr := GetParsedInitMsgFromGenesisOverride(&config.Init.GenesisOverride) - if overrideErr != nil { - return executionDB, nil, nil, fmt.Errorf("error parsing genesis override: %w", overrideErr) - } - if err := validateParsedInitMessagesMatch(parsedInitMessage, overrideMsg); err != nil { - return executionDB, nil, nil, fmt.Errorf("genesis and genesis override mismatch: %w", err) - } - } - } else if config.Init.GenesisOverride.IsSet() { - parsedInitMessage, err = GetParsedInitMsgFromGenesisOverride(&config.Init.GenesisOverride) - } else { - parsedInitMessage, err = GetParsedInitMsgFromChainConfig(chainConfig) - } + executionParsedInitMsg, err := GetExecutionParsedInitMsg(genesis, &config.Init.GenesisOverride, chainConfig) if err != nil { return executionDB, nil, nil, err } + parsedInitMessage := executionParsedInitMsg + if consensusParsedInitMessage != nil { + if err := validateChainConfigCompatibility(consensusParsedInitMessage, chainConfig); err != nil { + return executionDB, nil, nil, err + } + if err := validateParsedInitMessagesMatch(executionParsedInitMsg, consensusParsedInitMessage); err != nil { + log.Warn("Execution and consensus parsed init messages do not match", "err", err) + } + parsedInitMessage = consensusParsedInitMessage + } + var arbosInit *params.ArbOSInit if genesis != nil { arbosInit = genesis.ArbOSInit @@ -1055,7 +1048,7 @@ func OpenExistingExecutionDB(stack *node.Node, config *config.NodeConfig, chainI return nil, nil, nil, nil, nil } -func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Client *ethclient.Client, rollupAddrs *chaininfo.RollupAddresses, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { +func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Client *ethclient.Client, rollupAddrs *chaininfo.RollupAddresses) (*arbostypes.ParsedInitMessage, error) { var parsedInitMessage *arbostypes.ParsedInitMessage delayedBridge, err := arbnode.NewDelayedBridge(l1Client, rollupAddrs.Bridge, rollupAddrs.DeployedAt) if err != nil { @@ -1083,13 +1076,40 @@ func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Cl if parsedInitMessage.ChainId.Cmp(chainId) != 0 { return nil, fmt.Errorf("expected L2 chain ID %v but read L2 chain ID %v from init message in L1 inbox", chainId, parsedInitMessage.ChainId) } + log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig)) + return parsedInitMessage, nil +} + +func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.GenesisOverride, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { + if genesis != nil { + parsedInitMessage, err := GetParsedInitMsgFromGenesis(genesis) + if err != nil { + return nil, err + } + if genesisOverride.IsSet() { + overrideMsg, err := GetParsedInitMsgFromGenesisOverride(genesisOverride) + if err != nil { + return nil, fmt.Errorf("error parsing genesis override: %w", err) + } + if err := validateParsedInitMessagesMatch(parsedInitMessage, overrideMsg); err != nil { + return nil, fmt.Errorf("genesis and genesis override mismatch: %w", err) + } + } + return parsedInitMessage, nil + } + if genesisOverride.IsSet() { + return GetParsedInitMsgFromGenesisOverride(genesisOverride) + } + return GetParsedInitMsgFromChainConfig(chainConfig) +} + +func validateChainConfigCompatibility(parsedInitMessage *arbostypes.ParsedInitMessage, chainConfig *params.ChainConfig) error { if parsedInitMessage.ChainConfig != nil { if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { - return nil, fmt.Errorf("incompatible chain config read from init message in L1 inbox: %w", err) + return fmt.Errorf("incompatible chain config read from init message: %w", err) } } - log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig)) - return parsedInitMessage, nil + return nil } func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitMessage, error) { diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index a1b4ed110e3..02bb1566382 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -450,6 +450,7 @@ func TestOpenInitializeExecutionDBIncompatibleStateScheme(t *testing.T) { &nodeConfig.Persistent, l1Client, chaininfo.RollupAddresses{}, + nil, ) Require(t, err) blockchain.Stop() @@ -467,6 +468,7 @@ func TestOpenInitializeExecutionDBIncompatibleStateScheme(t *testing.T) { &nodeConfig.Persistent, l1Client, chaininfo.RollupAddresses{}, + nil, ) Require(t, err) blockchain.Stop() @@ -485,6 +487,7 @@ func TestOpenInitializeExecutionDBIncompatibleStateScheme(t *testing.T) { &nodeConfig.Persistent, l1Client, chaininfo.RollupAddresses{}, + nil, ) if !strings.Contains(err.Error(), "incompatible state scheme, stored: path, provided: hash") { t.Fatalf("Failed to detect incompatible state scheme") @@ -713,6 +716,7 @@ func TestOpenInitializeExecutionDbEmptyInit(t *testing.T) { &nodeConfig.Persistent, l1Client, chaininfo.RollupAddresses{}, + nil, ) Require(t, err) blockchain.Stop() @@ -1068,6 +1072,7 @@ func getInitHelper(t *testing.T, ownerAdress string, chainID uint64, emptyState &nodeConfig.Persistent, l1Client, chaininfo.RollupAddresses{}, + nil, ) Require(t, err) diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index 69311aeb929..b8e1efe68e8 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -39,6 +39,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos/arbostypes" nitroversionalerter "github.com/offchainlabs/nitro/arbnode/nitro-version-alerter" "github.com/offchainlabs/nitro/arbnode/resourcemanager" blocksreexecutor "github.com/offchainlabs/nitro/blocks_reexecutor" @@ -427,7 +428,17 @@ func mainImpl() int { return 1 } - executionDB, initDataReader, l2BlockChain, err := nitroinit.OpenInitializeExecutionDB(ctx, stack, nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), tracer, &nodeConfig.Persistent, l1Client, rollupAddrs) + chainId := new(big.Int).SetUint64(nodeConfig.Chain.ID) + var consensusParsedInitMsg *arbostypes.ParsedInitMessage + if nodeConfig.Node.ParentChainReader.Enable { + consensusParsedInitMsg, err = nitroinit.GetParsedInitMsgFromParentChain(ctx, chainId, l1Client, &rollupAddrs) + if err != nil { + log.Error("error getting parsed init message from parent chain", "err", err) + return 1 + } + } + + executionDB, initDataReader, l2BlockChain, err := nitroinit.OpenInitializeExecutionDB(ctx, stack, nodeConfig, chainId, gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), tracer, &nodeConfig.Persistent, l1Client, rollupAddrs, consensusParsedInitMsg) if l2BlockChain != nil { deferFuncs = append(deferFuncs, func() { l2BlockChain.Stop() }) } diff --git a/system_tests/bold_challenge_protocol_test.go b/system_tests/bold_challenge_protocol_test.go index 47a8640b440..5ee7c460c52 100644 --- a/system_tests/bold_challenge_protocol_test.go +++ b/system_tests/bold_challenge_protocol_test.go @@ -594,7 +594,7 @@ func create2ndNodeWithConfigForBoldProtocol( txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, l1client, first.DeployInfo) Require(t, err) execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) diff --git a/system_tests/bold_customda_challenge_test.go b/system_tests/bold_customda_challenge_test.go index e24c132e41f..1396b887abd 100644 --- a/system_tests/bold_customda_challenge_test.go +++ b/system_tests/bold_customda_challenge_test.go @@ -203,7 +203,7 @@ func createNodeBWithSharedContracts( txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, l1client, first.DeployInfo) Require(t, err) execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 1b4c664f7c4..dba33103a9e 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -1986,7 +1986,7 @@ func deployOnParentChain( parentChainInfo.SetContract("SequencerInbox", addresses.SequencerInbox) parentChainInfo.SetContract("Inbox", addresses.Inbox) parentChainInfo.SetContract("UpgradeExecutor", addresses.UpgradeExecutor) - initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, parentChainClient, addresses, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, chainConfig.ChainID, parentChainClient, addresses) Require(t, err) return addresses, initMessage diff --git a/system_tests/genesis_assertion_test.go b/system_tests/genesis_assertion_test.go index 4c0a6ac0aad..81d29e3729a 100644 --- a/system_tests/genesis_assertion_test.go +++ b/system_tests/genesis_assertion_test.go @@ -289,7 +289,7 @@ func createL2NodeWithRollupAddresses( Require(t, execConfig.Validate()) stackConfig := testhelpers.CreateStackConfigForTest("") stackConfig.DBEngine = rawdb.DBPebble - initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, l2infoIn.Signer.ChainID(), l1client, addresses, chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, l2infoIn.Signer.ChainID(), l1client, addresses) Require(t, err) var l2executionDB ethdb.Database diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index ef2251e5cb1..f821003c5c5 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -68,7 +68,7 @@ func TestGetParsedInitMsgFromParentChain(t *testing.T) { cleanup := builder.Build(t) defer cleanup() - initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, builder.chainConfig.ChainID, builder.L1.Client, builder.addresses, builder.chainConfig) + initMessage, err := nitroinit.GetParsedInitMsgFromParentChain(ctx, builder.chainConfig.ChainID, builder.L1.Client, builder.addresses) Require(t, err) if success := reflect.DeepEqual(initMessage, builder.initMessage); !success { From 994e86c3199010c38b236861990b4c7bde7eb588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 26 Feb 2026 09:40:08 +0100 Subject: [PATCH 14/39] fmt --- cmd/nitro/nitro.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index b8e1efe68e8..ad0a67c4aa8 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -39,9 +39,9 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbos/arbostypes" nitroversionalerter "github.com/offchainlabs/nitro/arbnode/nitro-version-alerter" "github.com/offchainlabs/nitro/arbnode/resourcemanager" + "github.com/offchainlabs/nitro/arbos/arbostypes" blocksreexecutor "github.com/offchainlabs/nitro/blocks_reexecutor" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" From b14aaa22bf747582da1a0129f2178bfaa31004fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 12:29:43 +0100 Subject: [PATCH 15/39] Clarify comment in IsSet --- cmd/conf/init.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index cbdf4fed1f6..b293b658170 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -48,7 +48,8 @@ func (c *GenesisOverride) ParseInitialL1BaseFee() (*big.Int, error) { } func (c *GenesisOverride) IsSet() bool { - return c.SerializedChainConfig != "" // initial L1 base fee has a fallback + // Only SerializedChainConfig is required; InitialL1BaseFee falls back to the default if not provided. + return c.SerializedChainConfig != "" } type InitConfig struct { From 5cdeeef220d36ea745290f1dbafc975b1f9fa8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 12:38:27 +0100 Subject: [PATCH 16/39] s/GenesisOverride/GenesisOverrideConfig --- cmd/conf/init.go | 78 ++++++++++++++++++------------------- cmd/nitro/init/init.go | 4 +- cmd/nitro/init/init_test.go | 30 +++++++------- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index b293b658170..bb53c690551 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -16,24 +16,24 @@ import ( "github.com/offchainlabs/nitro/util" ) -type GenesisOverride struct { +type GenesisOverrideConfig struct { SerializedChainConfig string `koanf:"serialized-chain-config"` InitialL1BaseFee string `koanf:"initial-l1-base-fee"` } -var GenesisOverrideDefault = GenesisOverride{ +var GenesisOverrideConfigDefault = GenesisOverrideConfig{ SerializedChainConfig: "", InitialL1BaseFee: "", } -func GenesisOverrideAddOptions(prefix string, f *pflag.FlagSet) { - f.String(prefix+".serialized-chain-config", GenesisOverrideDefault.SerializedChainConfig, "JSON-serialized chain config, can be used to provide chain config when genesis.json is not available") - f.String(prefix+".initial-l1-base-fee", GenesisOverrideDefault.InitialL1BaseFee, "initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, use default)") +func GenesisOverrideConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.String(prefix+".serialized-chain-config", GenesisOverrideConfigDefault.SerializedChainConfig, "JSON-serialized chain config, can be used to provide chain config when genesis.json is not available") + f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, "initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, use default)") } // ParseInitialL1BaseFee parses the InitialL1BaseFee string into a *big.Int. // Returns nil if the string is empty (not set). -func (c *GenesisOverride) ParseInitialL1BaseFee() (*big.Int, error) { +func (c *GenesisOverrideConfig) ParseInitialL1BaseFee() (*big.Int, error) { if c.InitialL1BaseFee == "" { return nil, nil } @@ -47,42 +47,42 @@ func (c *GenesisOverride) ParseInitialL1BaseFee() (*big.Int, error) { return fee, nil } -func (c *GenesisOverride) IsSet() bool { +func (c *GenesisOverrideConfig) IsSet() bool { // Only SerializedChainConfig is required; InitialL1BaseFee falls back to the default if not provided. return c.SerializedChainConfig != "" } type InitConfig struct { - Force bool `koanf:"force"` - Url string `koanf:"url"` - Latest string `koanf:"latest"` - LatestBase string `koanf:"latest-base"` - ValidateChecksum bool `koanf:"validate-checksum"` - DownloadPath string `koanf:"download-path"` - DownloadPoll time.Duration `koanf:"download-poll"` - DevInit bool `koanf:"dev-init"` - DevInitAddress string `koanf:"dev-init-address"` - DevMaxCodeSize uint64 `koanf:"dev-max-code-size"` - DevInitBlockNum uint64 `koanf:"dev-init-blocknum"` - Empty bool `koanf:"empty"` - ImportWasm bool `koanf:"import-wasm"` - AccountsPerSync uint `koanf:"accounts-per-sync"` - ImportFile string `koanf:"import-file"` - GenesisJsonFile string `koanf:"genesis-json-file"` - GenesisJsonFileDirectory string `koanf:"genesis-json-file-directory"` - GenesisOverride GenesisOverride `koanf:"genesis-override"` - ThenQuit bool `koanf:"then-quit"` - Prune string `koanf:"prune"` - PruneParallelStorageTraversal bool `koanf:"prune-parallel-storage-traversal"` - PruneBloomSize uint64 `koanf:"prune-bloom-size"` - PruneThreads int `koanf:"prune-threads"` - PruneTrieCleanCache int `koanf:"prune-trie-clean-cache"` - RecreateMissingStateFrom uint64 `koanf:"recreate-missing-state-from"` - RebuildLocalWasm string `koanf:"rebuild-local-wasm"` - ReorgToBatch int64 `koanf:"reorg-to-batch"` - ReorgToMessageBatch int64 `koanf:"reorg-to-message-batch"` - ReorgToBlockBatch int64 `koanf:"reorg-to-block-batch"` - ValidateGenesisAssertion bool `koanf:"validate-genesis-assertion"` + Force bool `koanf:"force"` + Url string `koanf:"url"` + Latest string `koanf:"latest"` + LatestBase string `koanf:"latest-base"` + ValidateChecksum bool `koanf:"validate-checksum"` + DownloadPath string `koanf:"download-path"` + DownloadPoll time.Duration `koanf:"download-poll"` + DevInit bool `koanf:"dev-init"` + DevInitAddress string `koanf:"dev-init-address"` + DevMaxCodeSize uint64 `koanf:"dev-max-code-size"` + DevInitBlockNum uint64 `koanf:"dev-init-blocknum"` + Empty bool `koanf:"empty"` + ImportWasm bool `koanf:"import-wasm"` + AccountsPerSync uint `koanf:"accounts-per-sync"` + ImportFile string `koanf:"import-file"` + GenesisJsonFile string `koanf:"genesis-json-file"` + GenesisJsonFileDirectory string `koanf:"genesis-json-file-directory"` + GenesisOverride GenesisOverrideConfig `koanf:"genesis-override"` + ThenQuit bool `koanf:"then-quit"` + Prune string `koanf:"prune"` + PruneParallelStorageTraversal bool `koanf:"prune-parallel-storage-traversal"` + PruneBloomSize uint64 `koanf:"prune-bloom-size"` + PruneThreads int `koanf:"prune-threads"` + PruneTrieCleanCache int `koanf:"prune-trie-clean-cache"` + RecreateMissingStateFrom uint64 `koanf:"recreate-missing-state-from"` + RebuildLocalWasm string `koanf:"rebuild-local-wasm"` + ReorgToBatch int64 `koanf:"reorg-to-batch"` + ReorgToMessageBatch int64 `koanf:"reorg-to-message-batch"` + ReorgToBlockBatch int64 `koanf:"reorg-to-block-batch"` + ValidateGenesisAssertion bool `koanf:"validate-genesis-assertion"` } var InitConfigDefault = InitConfig{ @@ -102,7 +102,7 @@ var InitConfigDefault = InitConfig{ ImportFile: "", GenesisJsonFile: "", GenesisJsonFileDirectory: "", - GenesisOverride: GenesisOverrideDefault, + GenesisOverride: GenesisOverrideConfigDefault, AccountsPerSync: 100000, ThenQuit: false, Prune: "", @@ -136,7 +136,7 @@ func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".import-file", InitConfigDefault.ImportFile, "path for json data to import") f.String(prefix+".genesis-json-file", InitConfigDefault.GenesisJsonFile, "path for genesis json file") f.String(prefix+".genesis-json-file-directory", InitConfigDefault.GenesisJsonFileDirectory, "directory path for genesis json files - will search for a file named by the chain ID") - GenesisOverrideAddOptions(prefix+".genesis-override", f) + GenesisOverrideConfigAddOptions(prefix+".genesis-override", f) f.Uint(prefix+".accounts-per-sync", InitConfigDefault.AccountsPerSync, "during init - sync database every X accounts. Lower value for low-memory systems. 0 disables.") f.String(prefix+".prune", InitConfigDefault.Prune, "pruning for a given use: \"full\" for full nodes serving RPC requests, or \"validator\" for validators") f.Bool(prefix+".prune-parallel-storage-traversal", InitConfigDefault.PruneParallelStorageTraversal, "if true: use parallel pruning per account") diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index d0b5c0608d4..2d72d1d6bed 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1080,7 +1080,7 @@ func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Cl return parsedInitMessage, nil } -func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.GenesisOverride, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { +func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.GenesisOverrideConfig, chainConfig *params.ChainConfig) (*arbostypes.ParsedInitMessage, error) { if genesis != nil { parsedInitMessage, err := GetParsedInitMsgFromGenesis(genesis) if err != nil { @@ -1132,7 +1132,7 @@ func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitM return parsedInitMessage, nil } -func GetParsedInitMsgFromGenesisOverride(genesisOverride *conf.GenesisOverride) (*arbostypes.ParsedInitMessage, error) { +func GetParsedInitMsgFromGenesisOverride(genesisOverride *conf.GenesisOverrideConfig) (*arbostypes.ParsedInitMessage, error) { var chainConfig params.ChainConfig if err := json.Unmarshal([]byte(genesisOverride.SerializedChainConfig), &chainConfig); err != nil { return nil, fmt.Errorf("failed to deserialize chain config from genesis override: %w", err) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 02bb1566382..c071189378d 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1384,7 +1384,7 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { serializedConfig := testChainConfigJSON(t) t.Run("with default initial L1 base fee", func(t *testing.T) { - override := &conf.GenesisOverride{ + override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig, InitialL1BaseFee: "", } @@ -1397,7 +1397,7 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { }) t.Run("with custom initial L1 base fee", func(t *testing.T) { - override := &conf.GenesisOverride{ + override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig, InitialL1BaseFee: "100000000000", // 100 GWei } @@ -1408,7 +1408,7 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { t.Run("with large initial L1 base fee", func(t *testing.T) { largeFee := "999999999999999999999999999999" - override := &conf.GenesisOverride{ + override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig, InitialL1BaseFee: largeFee, } @@ -1419,7 +1419,7 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { }) t.Run("with invalid chain config JSON", func(t *testing.T) { - override := &conf.GenesisOverride{ + override := &conf.GenesisOverrideConfig{ SerializedChainConfig: "not-valid-json", } _, err := GetParsedInitMsgFromGenesisOverride(override) @@ -1428,7 +1428,7 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { }) t.Run("with invalid initial L1 base fee", func(t *testing.T) { - override := &conf.GenesisOverride{ + override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig, InitialL1BaseFee: "not-a-number", } @@ -1523,42 +1523,42 @@ func TestValidateParsedInitMessagesMatch(t *testing.T) { }) } -func TestGenesisOverride(t *testing.T) { +func TestGenesisOverrideConfig(t *testing.T) { t.Run("IsSet returns false for defaults", func(t *testing.T) { - c := conf.GenesisOverrideDefault + c := conf.GenesisOverrideConfigDefault require.False(t, c.IsSet()) }) t.Run("IsSet returns true when serialized chain config is set", func(t *testing.T) { - c := conf.GenesisOverride{ + c := conf.GenesisOverrideConfig{ SerializedChainConfig: `{"chainId": 1}`, } require.True(t, c.IsSet()) }) t.Run("IsSet returns false when only initial L1 base fee is set", func(t *testing.T) { - c := conf.GenesisOverride{ + c := conf.GenesisOverrideConfig{ InitialL1BaseFee: "0", } require.False(t, c.IsSet()) }) t.Run("ParseInitialL1BaseFee returns nil when not set", func(t *testing.T) { - c := conf.GenesisOverride{} + c := conf.GenesisOverrideConfig{} fee, err := c.ParseInitialL1BaseFee() require.NoError(t, err) require.Nil(t, fee) }) t.Run("ParseInitialL1BaseFee returns value when set", func(t *testing.T) { - c := conf.GenesisOverride{InitialL1BaseFee: "50000000000"} + c := conf.GenesisOverrideConfig{InitialL1BaseFee: "50000000000"} fee, err := c.ParseInitialL1BaseFee() require.NoError(t, err) require.Equal(t, big.NewInt(50_000_000_000), fee) }) t.Run("ParseInitialL1BaseFee returns zero when explicitly set to zero", func(t *testing.T) { - c := conf.GenesisOverride{InitialL1BaseFee: "0"} + c := conf.GenesisOverrideConfig{InitialL1BaseFee: "0"} fee, err := c.ParseInitialL1BaseFee() require.NoError(t, err) require.Equal(t, big.NewInt(0), fee) @@ -1566,7 +1566,7 @@ func TestGenesisOverride(t *testing.T) { t.Run("ParseInitialL1BaseFee supports large values", func(t *testing.T) { largeFee := "999999999999999999999999999999" - c := conf.GenesisOverride{InitialL1BaseFee: largeFee} + c := conf.GenesisOverrideConfig{InitialL1BaseFee: largeFee} fee, err := c.ParseInitialL1BaseFee() require.NoError(t, err) expected, _ := new(big.Int).SetString(largeFee, 10) @@ -1574,14 +1574,14 @@ func TestGenesisOverride(t *testing.T) { }) t.Run("ParseInitialL1BaseFee errors on invalid string", func(t *testing.T) { - c := conf.GenesisOverride{InitialL1BaseFee: "not-a-number"} + c := conf.GenesisOverrideConfig{InitialL1BaseFee: "not-a-number"} _, err := c.ParseInitialL1BaseFee() require.Error(t, err) require.Contains(t, err.Error(), "failed to parse initial-l1-base-fee") }) t.Run("ParseInitialL1BaseFee errors on negative value", func(t *testing.T) { - c := conf.GenesisOverride{InitialL1BaseFee: "-1"} + c := conf.GenesisOverrideConfig{InitialL1BaseFee: "-1"} _, err := c.ParseInitialL1BaseFee() require.Error(t, err) require.Contains(t, err.Error(), "must be non-negative") From f5fcc3748fe6c98497c3b690c0eb79d597a2bd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 12:49:54 +0100 Subject: [PATCH 17/39] Remove validateParsedInitMessagesMatch --- cmd/nitro/init/init.go | 23 ++++----------- cmd/nitro/init/init_test.go | 59 ------------------------------------- 2 files changed, 5 insertions(+), 77 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 2d72d1d6bed..7908bb85d0f 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -4,7 +4,6 @@ package nitroinit import ( - "bytes" "context" "crypto/sha256" "encoding/hex" @@ -18,6 +17,7 @@ import ( "os" "path" "path/filepath" + "reflect" "regexp" "strings" "sync" @@ -646,8 +646,8 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co if err := validateChainConfigCompatibility(consensusParsedInitMessage, chainConfig); err != nil { return executionDB, nil, nil, err } - if err := validateParsedInitMessagesMatch(executionParsedInitMsg, consensusParsedInitMessage); err != nil { - log.Warn("Execution and consensus parsed init messages do not match", "err", err) + if !reflect.DeepEqual(executionParsedInitMsg, consensusParsedInitMessage) { + log.Warn("Execution and consensus parsed init messages do not match") } parsedInitMessage = consensusParsedInitMessage } @@ -1091,8 +1091,8 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene if err != nil { return nil, fmt.Errorf("error parsing genesis override: %w", err) } - if err := validateParsedInitMessagesMatch(parsedInitMessage, overrideMsg); err != nil { - return nil, fmt.Errorf("genesis and genesis override mismatch: %w", err) + if !reflect.DeepEqual(parsedInitMessage, overrideMsg) { + return nil, fmt.Errorf("genesis and genesis override parsed init messages do not match") } } return parsedInitMessage, nil @@ -1170,19 +1170,6 @@ func GetParsedInitMsgFromChainConfig(chainConfig *params.ChainConfig) (*arbostyp return parsedInitMessage, nil } -func validateParsedInitMessagesMatch(a, b *arbostypes.ParsedInitMessage) error { - if a.ChainId.Cmp(b.ChainId) != 0 { - return fmt.Errorf("chain ID mismatch: %v vs %v", a.ChainId, b.ChainId) - } - if a.InitialL1BaseFee.Cmp(b.InitialL1BaseFee) != 0 { - return fmt.Errorf("initial L1 base fee mismatch: %v vs %v", a.InitialL1BaseFee, b.InitialL1BaseFee) - } - if !bytes.Equal(a.SerializedChainConfig, b.SerializedChainConfig) { - return fmt.Errorf("serialized chain config mismatch: %s vs %s", string(a.SerializedChainConfig), string(b.SerializedChainConfig)) - } - return nil -} - func getGenesisAssertionCreationInfo(ctx context.Context, rollupAddress common.Address, l1Client *ethclient.Client, genesisHash common.Hash, sendRoot common.Hash) (*protocol.AssertionCreatedInfo, [32]byte, bool, error) { var assertionHash [32]byte diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index c071189378d..332df07a63e 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1464,65 +1464,6 @@ func TestGetParsedInitMsgFromGenesis(t *testing.T) { }) } -func TestValidateParsedInitMessagesMatch(t *testing.T) { - chainConfig := chaininfo.ArbitrumDevTestChainConfig() - serializedConfig, err := json.Marshal(chainConfig) - require.NoError(t, err) - - baseMsg := &arbostypes.ParsedInitMessage{ - ChainId: chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: chainConfig, - SerializedChainConfig: serializedConfig, - } - - t.Run("matching messages", func(t *testing.T) { - other := &arbostypes.ParsedInitMessage{ - ChainId: chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: chainConfig, - SerializedChainConfig: serializedConfig, - } - require.NoError(t, validateParsedInitMessagesMatch(baseMsg, other)) - }) - - t.Run("chain ID mismatch", func(t *testing.T) { - other := &arbostypes.ParsedInitMessage{ - ChainId: big.NewInt(999999), - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: chainConfig, - SerializedChainConfig: serializedConfig, - } - err := validateParsedInitMessagesMatch(baseMsg, other) - require.Error(t, err) - require.Contains(t, err.Error(), "chain ID mismatch") - }) - - t.Run("initial L1 base fee mismatch", func(t *testing.T) { - other := &arbostypes.ParsedInitMessage{ - ChainId: chainConfig.ChainID, - InitialL1BaseFee: big.NewInt(123), - ChainConfig: chainConfig, - SerializedChainConfig: serializedConfig, - } - err := validateParsedInitMessagesMatch(baseMsg, other) - require.Error(t, err) - require.Contains(t, err.Error(), "initial L1 base fee mismatch") - }) - - t.Run("serialized chain config mismatch", func(t *testing.T) { - other := &arbostypes.ParsedInitMessage{ - ChainId: chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: chainConfig, - SerializedChainConfig: []byte(`{"chainId":999}`), - } - err := validateParsedInitMessagesMatch(baseMsg, other) - require.Error(t, err) - require.Contains(t, err.Error(), "serialized chain config mismatch") - }) -} - func TestGenesisOverrideConfig(t *testing.T) { t.Run("IsSet returns false for defaults", func(t *testing.T) { c := conf.GenesisOverrideConfigDefault From 53b24e42590dfbf897c4a7cc9569dd373035bef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 12:56:26 +0100 Subject: [PATCH 18/39] Run check also for nil --- cmd/nitro/init/init.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 7908bb85d0f..3b4d5ba0793 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -636,6 +636,10 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co return executionDB, nil, nil, err } + if err := validateChainConfigCompatibility(consensusParsedInitMessage, chainConfig); err != nil { + return executionDB, nil, nil, err + } + executionParsedInitMsg, err := GetExecutionParsedInitMsg(genesis, &config.Init.GenesisOverride, chainConfig) if err != nil { return executionDB, nil, nil, err @@ -643,9 +647,6 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co parsedInitMessage := executionParsedInitMsg if consensusParsedInitMessage != nil { - if err := validateChainConfigCompatibility(consensusParsedInitMessage, chainConfig); err != nil { - return executionDB, nil, nil, err - } if !reflect.DeepEqual(executionParsedInitMsg, consensusParsedInitMessage) { log.Warn("Execution and consensus parsed init messages do not match") } @@ -1104,7 +1105,7 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene } func validateChainConfigCompatibility(parsedInitMessage *arbostypes.ParsedInitMessage, chainConfig *params.ChainConfig) error { - if parsedInitMessage.ChainConfig != nil { + if parsedInitMessage != nil && parsedInitMessage.ChainConfig != nil { if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { return fmt.Errorf("incompatible chain config read from init message: %w", err) } From 21d6cfa0da1e14757a04f8df3047c6d57b4b717c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:00:54 +0100 Subject: [PATCH 19/39] Log message instructions --- cmd/nitro/init/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 3b4d5ba0793..a26c5142e38 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -648,7 +648,7 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co parsedInitMessage := executionParsedInitMsg if consensusParsedInitMessage != nil { if !reflect.DeepEqual(executionParsedInitMsg, consensusParsedInitMessage) { - log.Warn("Execution and consensus parsed init messages do not match") + log.Warn("Execution and consensus parsed init messages do not match, verify that --init.genesis-json-file and --init.genesis-override match the on-chain init message") } parsedInitMessage = consensusParsedInitMessage } From e8abb342ea76d2d8bcb8a3c1880625eca699c5c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:06:33 +0100 Subject: [PATCH 20/39] Require ArbOSInit.InitialL1BaseFee in genesis instead of falling back to default --- cmd/nitro/init/init.go | 7 +++---- cmd/nitro/init/init_test.go | 8 ++++---- system_tests/nitro_init_test.go | 4 ++++ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index a26c5142e38..40d748b70a1 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1119,14 +1119,13 @@ func GetParsedInitMsgFromGenesis(genesis *core.Genesis) (*arbostypes.ParsedInitM return nil, err } - initialL1BaseFee := arbostypes.DefaultInitialL1BaseFee - if genesis.ArbOSInit != nil && genesis.ArbOSInit.InitialL1BaseFee != nil { - initialL1BaseFee = genesis.ArbOSInit.InitialL1BaseFee + if genesis.ArbOSInit == nil || genesis.ArbOSInit.InitialL1BaseFee == nil { + return nil, fmt.Errorf("genesis is missing arbOSInit.initialL1BaseFee") } parsedInitMessage := &arbostypes.ParsedInitMessage{ ChainId: chainConfig.ChainID, - InitialL1BaseFee: initialL1BaseFee, + InitialL1BaseFee: genesis.ArbOSInit.InitialL1BaseFee, ChainConfig: chainConfig, SerializedChainConfig: []byte(genesis.SerializedChainConfig), } diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 332df07a63e..1b4a5dd615a 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1454,13 +1454,13 @@ func TestGetParsedInitMsgFromGenesis(t *testing.T) { require.Equal(t, customFee, msg.InitialL1BaseFee) }) - t.Run("without ArbOSInit uses default", func(t *testing.T) { + t.Run("without ArbOSInit returns error", func(t *testing.T) { genesis := &core.Genesis{ SerializedChainConfig: serializedConfig, } - msg, err := GetParsedInitMsgFromGenesis(genesis) - require.NoError(t, err) - require.Equal(t, arbostypes.DefaultInitialL1BaseFee, msg.InitialL1BaseFee) + _, err := GetParsedInitMsgFromGenesis(genesis) + require.Error(t, err) + require.Contains(t, err.Error(), "genesis is missing arbOSInit.initialL1BaseFee") }) } diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index 5a87840f9e7..4434d8ff17f 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/cmd/nitro/config" @@ -43,6 +44,9 @@ func TestGetParsedInitMsgWithoutConsensus(t *testing.T) { // 1. From Genesis genesis := &core.Genesis{ SerializedChainConfig: string(serializedChainConfig), + ArbOSInit: ¶ms.ArbOSInit{ + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + }, } initMessage, err := nitroinit.GetParsedInitMsgFromGenesis(genesis) From 07c3a2ac685369c181bf2f963236bc76835a229a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:09:56 +0100 Subject: [PATCH 21/39] log lines indicating from which source Execution retrieved parsed init message --- cmd/nitro/init/init.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 40d748b70a1..74b598b3203 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1096,11 +1096,14 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene return nil, fmt.Errorf("genesis and genesis override parsed init messages do not match") } } + log.Info("Retrieved parsed init message from genesis.json") return parsedInitMessage, nil } if genesisOverride.IsSet() { + log.Info("Retrieved parsed init message from --init.genesis-override CLI flags") return GetParsedInitMsgFromGenesisOverride(genesisOverride) } + log.Info("Retrieved parsed init message from chain config") return GetParsedInitMsgFromChainConfig(chainConfig) } From daeed5c99516bf2f3e2c47a365d4427d9136b7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:11:37 +0100 Subject: [PATCH 22/39] write the initial l1 base fee in log --- cmd/nitro/init/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 74b598b3203..4201d4bd3a1 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1077,7 +1077,7 @@ func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Cl if parsedInitMessage.ChainId.Cmp(chainId) != 0 { return nil, fmt.Errorf("expected L2 chain ID %v but read L2 chain ID %v from init message in L1 inbox", chainId, parsedInitMessage.ChainId) } - log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig)) + log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig), "initialL1BaseFee", parsedInitMessage.InitialL1BaseFee) return parsedInitMessage, nil } From 941c0902d92533cdeca3382bf4cc3583b38ace3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:27:54 +0100 Subject: [PATCH 23/39] minor fixed --- cmd/conf/init.go | 2 +- cmd/nitro/init/init.go | 2 +- system_tests/nitro_init_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index bb53c690551..2094377b2bd 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -49,7 +49,7 @@ func (c *GenesisOverrideConfig) ParseInitialL1BaseFee() (*big.Int, error) { func (c *GenesisOverrideConfig) IsSet() bool { // Only SerializedChainConfig is required; InitialL1BaseFee falls back to the default if not provided. - return c.SerializedChainConfig != "" + return c != nil && c.SerializedChainConfig != "" } type InitConfig struct { diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 4201d4bd3a1..403235ebbbe 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1108,7 +1108,7 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene } func validateChainConfigCompatibility(parsedInitMessage *arbostypes.ParsedInitMessage, chainConfig *params.ChainConfig) error { - if parsedInitMessage != nil && parsedInitMessage.ChainConfig != nil { + if parsedInitMessage != nil && parsedInitMessage.ChainConfig != nil && chainConfig != nil { if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { return fmt.Errorf("incompatible chain config read from init message: %w", err) } diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index 4434d8ff17f..93c3c432821 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -53,7 +53,7 @@ func TestGetParsedInitMsgWithoutConsensus(t *testing.T) { Require(t, err) if success := reflect.DeepEqual(initMessage, expectedInitMessage); !success { - t.Fatalf("diff found in initMessage %v and builder.initMessage: %v", initMessage, builder.initMessage) + t.Fatalf("diff found in initMessage %v and expectedInitMessage: %v", initMessage, expectedInitMessage) } // 2. Directly from chain config @@ -61,7 +61,7 @@ func TestGetParsedInitMsgWithoutConsensus(t *testing.T) { Require(t, err) if success := reflect.DeepEqual(initMessage, expectedInitMessage); !success { - t.Fatalf("diff found in initMessage %v and builder.initMessage: %v", initMessage, builder.initMessage) + t.Fatalf("diff found in initMessage %v and expectedInitMessage: %v", initMessage, expectedInitMessage) } } From 8334ec3bfbf516609dc8d05fecd44182d8059526 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:31:36 +0100 Subject: [PATCH 24/39] TestGetExecutionParsedInitMsg --- cmd/nitro/init/init_test.go | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 1b4a5dd615a..54242fbc958 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1464,6 +1464,62 @@ func TestGetParsedInitMsgFromGenesis(t *testing.T) { }) } +func TestGetExecutionParsedInitMsg(t *testing.T) { + chainConfig := chaininfo.ArbitrumDevTestChainConfig() + serializedConfig := testChainConfigJSON(t) + customFee := big.NewInt(100_000_000_000) + + genesis := &core.Genesis{ + SerializedChainConfig: serializedConfig, + ArbOSInit: ¶ms.ArbOSInit{ + InitialL1BaseFee: customFee, + }, + } + + matchingOverride := &conf.GenesisOverrideConfig{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: "100000000000", + } + + t.Run("genesis only", func(t *testing.T) { + msg, err := GetExecutionParsedInitMsg(genesis, nil, chainConfig) + require.NoError(t, err) + require.Equal(t, chainConfig.ChainID, msg.ChainId) + require.Equal(t, customFee, msg.InitialL1BaseFee) + }) + + t.Run("genesis with matching override", func(t *testing.T) { + msg, err := GetExecutionParsedInitMsg(genesis, matchingOverride, chainConfig) + require.NoError(t, err) + require.Equal(t, chainConfig.ChainID, msg.ChainId) + require.Equal(t, customFee, msg.InitialL1BaseFee) + }) + + t.Run("genesis with mismatching override returns error", func(t *testing.T) { + mismatchOverride := &conf.GenesisOverrideConfig{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: "999", + } + _, err := GetExecutionParsedInitMsg(genesis, mismatchOverride, chainConfig) + require.Error(t, err) + require.Contains(t, err.Error(), "genesis and genesis override parsed init messages do not match") + }) + + t.Run("override only", func(t *testing.T) { + msg, err := GetExecutionParsedInitMsg(nil, matchingOverride, chainConfig) + require.NoError(t, err) + require.Equal(t, chainConfig.ChainID, msg.ChainId) + require.Equal(t, customFee, msg.InitialL1BaseFee) + }) + + t.Run("chain config fallback", func(t *testing.T) { + msg, err := GetExecutionParsedInitMsg(nil, nil, chainConfig) + require.NoError(t, err) + require.Equal(t, chainConfig.ChainID, msg.ChainId) + require.Equal(t, arbostypes.DefaultInitialL1BaseFee, msg.InitialL1BaseFee) + }) +} + func TestGenesisOverrideConfig(t *testing.T) { t.Run("IsSet returns false for defaults", func(t *testing.T) { c := conf.GenesisOverrideConfigDefault From cc986a7960578cc76afdf8ff4bae2d013ef5ea96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Mon, 9 Mar 2026 13:36:42 +0100 Subject: [PATCH 25/39] Add arbosinit to testgenesis --- cmd/nitro/init/init_test.go | 4 ++-- cmd/nitro/init/testdata/testGenesis.json | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 54242fbc958..80e4b9cb524 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1295,8 +1295,8 @@ func TestGetInitWithGenesis(t *testing.T) { require.Equal(t, expectedChainConfig, chainConfig) - if arbOsInit != nil { - t.Fatalf("arbOsInit expected to be nil") + if arbOsInit == nil { + t.Fatalf("arbOsInit expected to be non-nil") } if initDataReader == nil { diff --git a/cmd/nitro/init/testdata/testGenesis.json b/cmd/nitro/init/testdata/testGenesis.json index 9e229efa66c..a3cb1a3329d 100644 --- a/cmd/nitro/init/testdata/testGenesis.json +++ b/cmd/nitro/init/testdata/testGenesis.json @@ -1,4 +1,7 @@ { + "arbOSInit": { + "initialL1BaseFee": 50000000000 + }, "serializedChainConfig": "{\"chainId\": 3503995874084926,\"homesteadBlock\": 0,\"eip150Block\": 0,\"eip155Block\": 0,\"eip158Block\": 0,\"byzantiumBlock\": 0,\"constantinopleBlock\": 0,\"petersburgBlock\": 0,\"istanbulBlock\": 0,\"muirGlacierBlock\": 0,\"berlinBlock\": 0,\"londonBlock\": 0,\"arrowGlacierBlock\": 0,\"grayGlacierBlock\": 0,\"mergeNetsplitBlock\": 0,\"shanghaiTime\": 0,\"cancunTime\": 60,\"pragueTime\": 120,\"terminalTotalDifficulty\": 131072,\"depositContractAddress\": \"0x0000000000000000000000000000000000000000\",\"ethash\": {},\"arbitrum\": {\"InitialArbOSVersion\": 6},\"blobSchedule\": {\"cancun\": { \"target\": 3, \"max\": 6, \"baseFeeUpdateFraction\": 3338477 },\"prague\": { \"target\": 6, \"max\": 9, \"baseFeeUpdateFraction\": 5007716 }}}", "nonce": "0x0", "timestamp": "0x0", From 9fb636d770f44636c00c66394bbba8b21a9c9687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:04:01 +0100 Subject: [PATCH 26/39] Revert "Remove validateParsedInitMessagesMatch" This reverts commit f5fcc374 --- cmd/nitro/init/init.go | 23 +++++++++++---- cmd/nitro/init/init_test.go | 59 +++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 5 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 403235ebbbe..81a8aefc60e 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -4,6 +4,7 @@ package nitroinit import ( + "bytes" "context" "crypto/sha256" "encoding/hex" @@ -17,7 +18,6 @@ import ( "os" "path" "path/filepath" - "reflect" "regexp" "strings" "sync" @@ -647,8 +647,8 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co parsedInitMessage := executionParsedInitMsg if consensusParsedInitMessage != nil { - if !reflect.DeepEqual(executionParsedInitMsg, consensusParsedInitMessage) { - log.Warn("Execution and consensus parsed init messages do not match, verify that --init.genesis-json-file and --init.genesis-override match the on-chain init message") + if err := validateParsedInitMessagesMatch(executionParsedInitMsg, consensusParsedInitMessage); err != nil { + log.Warn("Execution and consensus parsed init messages do not match", "err", err) } parsedInitMessage = consensusParsedInitMessage } @@ -1092,8 +1092,8 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene if err != nil { return nil, fmt.Errorf("error parsing genesis override: %w", err) } - if !reflect.DeepEqual(parsedInitMessage, overrideMsg) { - return nil, fmt.Errorf("genesis and genesis override parsed init messages do not match") + if err := validateParsedInitMessagesMatch(parsedInitMessage, overrideMsg); err != nil { + return nil, fmt.Errorf("genesis and genesis override mismatch: %w", err) } } log.Info("Retrieved parsed init message from genesis.json") @@ -1173,6 +1173,19 @@ func GetParsedInitMsgFromChainConfig(chainConfig *params.ChainConfig) (*arbostyp return parsedInitMessage, nil } +func validateParsedInitMessagesMatch(a, b *arbostypes.ParsedInitMessage) error { + if a.ChainId.Cmp(b.ChainId) != 0 { + return fmt.Errorf("chain ID mismatch: %v vs %v", a.ChainId, b.ChainId) + } + if a.InitialL1BaseFee.Cmp(b.InitialL1BaseFee) != 0 { + return fmt.Errorf("initial L1 base fee mismatch: %v vs %v", a.InitialL1BaseFee, b.InitialL1BaseFee) + } + if !bytes.Equal(a.SerializedChainConfig, b.SerializedChainConfig) { + return fmt.Errorf("serialized chain config mismatch: %s vs %s", string(a.SerializedChainConfig), string(b.SerializedChainConfig)) + } + return nil +} + func getGenesisAssertionCreationInfo(ctx context.Context, rollupAddress common.Address, l1Client *ethclient.Client, genesisHash common.Hash, sendRoot common.Hash) (*protocol.AssertionCreatedInfo, [32]byte, bool, error) { var assertionHash [32]byte diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 80e4b9cb524..92b455a6568 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1520,6 +1520,65 @@ func TestGetExecutionParsedInitMsg(t *testing.T) { }) } +func TestValidateParsedInitMessagesMatch(t *testing.T) { + chainConfig := chaininfo.ArbitrumDevTestChainConfig() + serializedConfig, err := json.Marshal(chainConfig) + require.NoError(t, err) + + baseMsg := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + + t.Run("matching messages", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + require.NoError(t, validateParsedInitMessagesMatch(baseMsg, other)) + }) + + t.Run("chain ID mismatch", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: big.NewInt(999999), + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + err := validateParsedInitMessagesMatch(baseMsg, other) + require.Error(t, err) + require.Contains(t, err.Error(), "chain ID mismatch") + }) + + t.Run("initial L1 base fee mismatch", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: big.NewInt(123), + ChainConfig: chainConfig, + SerializedChainConfig: serializedConfig, + } + err := validateParsedInitMessagesMatch(baseMsg, other) + require.Error(t, err) + require.Contains(t, err.Error(), "initial L1 base fee mismatch") + }) + + t.Run("serialized chain config mismatch", func(t *testing.T) { + other := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: chainConfig, + SerializedChainConfig: []byte(`{"chainId":999}`), + } + err := validateParsedInitMessagesMatch(baseMsg, other) + require.Error(t, err) + require.Contains(t, err.Error(), "serialized chain config mismatch") + }) +} + func TestGenesisOverrideConfig(t *testing.T) { t.Run("IsSet returns false for defaults", func(t *testing.T) { c := conf.GenesisOverrideConfigDefault From 49d1f69e5d4844470cb7802302f049c53fb442cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:09:42 +0100 Subject: [PATCH 27/39] Add info that it was read from parent chain --- cmd/nitro/init/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 81a8aefc60e..56be42ad696 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1077,7 +1077,7 @@ func GetParsedInitMsgFromParentChain(ctx context.Context, chainId *big.Int, l1Cl if parsedInitMessage.ChainId.Cmp(chainId) != 0 { return nil, fmt.Errorf("expected L2 chain ID %v but read L2 chain ID %v from init message in L1 inbox", chainId, parsedInitMessage.ChainId) } - log.Info("Read serialized chain config from init message", "json", string(parsedInitMessage.SerializedChainConfig), "initialL1BaseFee", parsedInitMessage.InitialL1BaseFee) + log.Info("Read serialized chain config from init message on parent chain", "json", string(parsedInitMessage.SerializedChainConfig), "initialL1BaseFee", parsedInitMessage.InitialL1BaseFee) return parsedInitMessage, nil } From 252e24f509a5774dde925a5c48982f75e77ff294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:12:23 +0100 Subject: [PATCH 28/39] Tell operator what is the default --- cmd/conf/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 2094377b2bd..58b9bef5220 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -28,7 +28,7 @@ var GenesisOverrideConfigDefault = GenesisOverrideConfig{ func GenesisOverrideConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".serialized-chain-config", GenesisOverrideConfigDefault.SerializedChainConfig, "JSON-serialized chain config, can be used to provide chain config when genesis.json is not available") - f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, "initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, use default)") + f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, "initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, default is 50 GWei = 50000000000)") } // ParseInitialL1BaseFee parses the InitialL1BaseFee string into a *big.Int. From a9a51d3169a4f6046196ce11d3a14456c126d4c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:17:32 +0100 Subject: [PATCH 29/39] Do not hardcode --- cmd/conf/init.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 58b9bef5220..5531673ca0d 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/util" ) @@ -28,7 +29,7 @@ var GenesisOverrideConfigDefault = GenesisOverrideConfig{ func GenesisOverrideConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".serialized-chain-config", GenesisOverrideConfigDefault.SerializedChainConfig, "JSON-serialized chain config, can be used to provide chain config when genesis.json is not available") - f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, "initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, default is 50 GWei = 50000000000)") + f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, fmt.Sprintf("initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, default is %s)", arbostypes.DefaultInitialL1BaseFee)) } // ParseInitialL1BaseFee parses the InitialL1BaseFee string into a *big.Int. From b6011c4628a4f58d01c3cc51cdea0bc3816a5291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:23:50 +0100 Subject: [PATCH 30/39] Full override validation --- cmd/conf/init.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 5531673ca0d..aee848a4b56 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -3,6 +3,7 @@ package conf import ( + "encoding/json" "fmt" "math/big" "slices" @@ -12,6 +13,7 @@ import ( "github.com/spf13/pflag" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/util" @@ -48,6 +50,19 @@ func (c *GenesisOverrideConfig) ParseInitialL1BaseFee() (*big.Int, error) { return fee, nil } +func (c *GenesisOverrideConfig) Validate() error { + if c.SerializedChainConfig != "" { + var chainConfig params.ChainConfig + if err := json.Unmarshal([]byte(c.SerializedChainConfig), &chainConfig); err != nil { + return fmt.Errorf("failed to unmarshal serialized-chain-config: %w", err) + } + } + if _, err := c.ParseInitialL1BaseFee(); err != nil { + return fmt.Errorf("failed to parse initial-l1-base-fee: %w", err) + } + return nil +} + func (c *GenesisOverrideConfig) IsSet() bool { // Only SerializedChainConfig is required; InitialL1BaseFee falls back to the default if not provided. return c != nil && c.SerializedChainConfig != "" @@ -160,7 +175,7 @@ func (c *InitConfig) Validate() error { if c.Empty && c.GenesisJsonFile != "" { return fmt.Errorf("init config cannot be both empty and have a genesis json file specified") } - if _, err := c.GenesisOverride.ParseInitialL1BaseFee(); err != nil { + if err := c.GenesisOverride.Validate(); err != nil { return fmt.Errorf("invalid init.genesis-override: %w", err) } if c.Force && c.RecreateMissingStateFrom > 0 { From c978a2dec723345aef778713df56dce1d0b95215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:28:24 +0100 Subject: [PATCH 31/39] Clarify log --- cmd/nitro/init/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 56be42ad696..1896fb1252f 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1110,7 +1110,7 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene func validateChainConfigCompatibility(parsedInitMessage *arbostypes.ParsedInitMessage, chainConfig *params.ChainConfig) error { if parsedInitMessage != nil && parsedInitMessage.ChainConfig != nil && chainConfig != nil { if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { - return fmt.Errorf("incompatible chain config read from init message: %w", err) + return fmt.Errorf("local chain config is incompatible with the chain config from the init message: %w", err) } } return nil From 984cd9a2377481fbc34768d9cc9e3979ba1aee5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 10:31:51 +0100 Subject: [PATCH 32/39] run validateChainConfigCompatibility with any parsed message --- cmd/nitro/init/init.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 1896fb1252f..29718792d8c 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -636,10 +636,6 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co return executionDB, nil, nil, err } - if err := validateChainConfigCompatibility(consensusParsedInitMessage, chainConfig); err != nil { - return executionDB, nil, nil, err - } - executionParsedInitMsg, err := GetExecutionParsedInitMsg(genesis, &config.Init.GenesisOverride, chainConfig) if err != nil { return executionDB, nil, nil, err @@ -653,6 +649,10 @@ func OpenInitializeExecutionDB(ctx context.Context, stack *node.Node, config *co parsedInitMessage = consensusParsedInitMessage } + if err := validateChainConfigCompatibility(parsedInitMessage, chainConfig); err != nil { + return executionDB, nil, nil, err + } + var arbosInit *params.ArbOSInit if genesis != nil { arbosInit = genesis.ArbOSInit @@ -1108,7 +1108,7 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene } func validateChainConfigCompatibility(parsedInitMessage *arbostypes.ParsedInitMessage, chainConfig *params.ChainConfig) error { - if parsedInitMessage != nil && parsedInitMessage.ChainConfig != nil && chainConfig != nil { + if parsedInitMessage.ChainConfig != nil && chainConfig != nil { if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { return fmt.Errorf("local chain config is incompatible with the chain config from the init message: %w", err) } From 73cb2de1d7d28cdd088d79519d54da9a950d694f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 13:42:56 +0100 Subject: [PATCH 33/39] Fix test --- cmd/nitro/init/init_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 92b455a6568..c21722ae83c 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1502,7 +1502,7 @@ func TestGetExecutionParsedInitMsg(t *testing.T) { } _, err := GetExecutionParsedInitMsg(genesis, mismatchOverride, chainConfig) require.Error(t, err) - require.Contains(t, err.Error(), "genesis and genesis override parsed init messages do not match") + require.Contains(t, err.Error(), "genesis and genesis override mismatch: initial L1 base fee mismatch") }) t.Run("override only", func(t *testing.T) { From 69fb0af9aff4bd622bcb326934121c49869763e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 13:50:16 +0100 Subject: [PATCH 34/39] Require neither or both parameters in override --- cmd/conf/init.go | 11 +++++++---- cmd/nitro/init/init.go | 7 +++---- cmd/nitro/init/init_test.go | 16 +++++++++++++--- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index aee848a4b56..d887b3741fb 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/util" ) @@ -31,7 +30,7 @@ var GenesisOverrideConfigDefault = GenesisOverrideConfig{ func GenesisOverrideConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".serialized-chain-config", GenesisOverrideConfigDefault.SerializedChainConfig, "JSON-serialized chain config, can be used to provide chain config when genesis.json is not available") - f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, fmt.Sprintf("initial L1 base fee (in wei), can be used to provide this value when genesis.json is not available (empty = not set, default is %s)", arbostypes.DefaultInitialL1BaseFee)) + f.String(prefix+".initial-l1-base-fee", GenesisOverrideConfigDefault.InitialL1BaseFee, "initial L1 base fee (in wei), must be set together with serialized-chain-config") } // ParseInitialL1BaseFee parses the InitialL1BaseFee string into a *big.Int. @@ -51,7 +50,12 @@ func (c *GenesisOverrideConfig) ParseInitialL1BaseFee() (*big.Int, error) { } func (c *GenesisOverrideConfig) Validate() error { - if c.SerializedChainConfig != "" { + chainConfigSet := c.SerializedChainConfig != "" + baseFeeSet := c.InitialL1BaseFee != "" + if chainConfigSet != baseFeeSet { + return fmt.Errorf("genesis-override.serialized-chain-config and genesis-override.initial-l1-base-fee must both be set or both be empty") + } + if chainConfigSet { var chainConfig params.ChainConfig if err := json.Unmarshal([]byte(c.SerializedChainConfig), &chainConfig); err != nil { return fmt.Errorf("failed to unmarshal serialized-chain-config: %w", err) @@ -64,7 +68,6 @@ func (c *GenesisOverrideConfig) Validate() error { } func (c *GenesisOverrideConfig) IsSet() bool { - // Only SerializedChainConfig is required; InitialL1BaseFee falls back to the default if not provided. return c != nil && c.SerializedChainConfig != "" } diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 29718792d8c..7ecec8c11dc 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1141,18 +1141,17 @@ func GetParsedInitMsgFromGenesisOverride(genesisOverride *conf.GenesisOverrideCo return nil, fmt.Errorf("failed to deserialize chain config from genesis override: %w", err) } - initialL1BaseFee := arbostypes.DefaultInitialL1BaseFee fee, err := genesisOverride.ParseInitialL1BaseFee() if err != nil { return nil, err } - if fee != nil { - initialL1BaseFee = fee + if fee == nil { + return nil, fmt.Errorf("genesis override is missing initial-l1-base-fee") } return &arbostypes.ParsedInitMessage{ ChainId: chainConfig.ChainID, - InitialL1BaseFee: initialL1BaseFee, + InitialL1BaseFee: fee, ChainConfig: &chainConfig, SerializedChainConfig: []byte(genesisOverride.SerializedChainConfig), }, nil diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index c21722ae83c..0048abe51a8 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1383,19 +1383,29 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { chainConfig := chaininfo.ArbitrumDevTestChainConfig() serializedConfig := testChainConfigJSON(t) - t.Run("with default initial L1 base fee", func(t *testing.T) { + t.Run("with valid config", func(t *testing.T) { override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig, - InitialL1BaseFee: "", + InitialL1BaseFee: "50000000000", } msg, err := GetParsedInitMsgFromGenesisOverride(override) require.NoError(t, err) require.Equal(t, chainConfig.ChainID, msg.ChainId) - require.Equal(t, arbostypes.DefaultInitialL1BaseFee, msg.InitialL1BaseFee) + require.Equal(t, big.NewInt(50_000_000_000), msg.InitialL1BaseFee) require.Equal(t, serializedConfig, string(msg.SerializedChainConfig)) require.NotNil(t, msg.ChainConfig) }) + t.Run("missing initial L1 base fee returns error", func(t *testing.T) { + override := &conf.GenesisOverrideConfig{ + SerializedChainConfig: serializedConfig, + InitialL1BaseFee: "", + } + _, err := GetParsedInitMsgFromGenesisOverride(override) + require.Error(t, err) + require.Contains(t, err.Error(), "genesis override is missing initial-l1-base-fee") + }) + t.Run("with custom initial L1 base fee", func(t *testing.T) { override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig, From 2983735817632d8272e98bb170c903b51db6d19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Wed, 11 Mar 2026 13:54:06 +0100 Subject: [PATCH 35/39] Remove redundant test --- system_tests/nitro_init_test.go | 47 --------------------------------- 1 file changed, 47 deletions(-) diff --git a/system_tests/nitro_init_test.go b/system_tests/nitro_init_test.go index 93c3c432821..86bc6a57b8c 100644 --- a/system_tests/nitro_init_test.go +++ b/system_tests/nitro_init_test.go @@ -4,67 +4,20 @@ package arbtest import ( "context" - "encoding/json" "math/big" "reflect" "testing" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/params" - "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/cmd/nitro/config" "github.com/offchainlabs/nitro/cmd/nitro/init" "github.com/offchainlabs/nitro/execution/gethexec" ) -func TestGetParsedInitMsgWithoutConsensus(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - cleanup := builder.Build(t) - defer cleanup() - - serializedChainConfig, err := json.Marshal(builder.chainConfig) - Require(t, err) - - // We create an initMessage since builder doesn't create an initMessage without an L1 - expectedInitMessage := &arbostypes.ParsedInitMessage{ - ChainId: builder.chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: builder.chainConfig, - SerializedChainConfig: serializedChainConfig, - } - - // 1. From Genesis - genesis := &core.Genesis{ - SerializedChainConfig: string(serializedChainConfig), - ArbOSInit: ¶ms.ArbOSInit{ - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - }, - } - - initMessage, err := nitroinit.GetParsedInitMsgFromGenesis(genesis) - Require(t, err) - - if success := reflect.DeepEqual(initMessage, expectedInitMessage); !success { - t.Fatalf("diff found in initMessage %v and expectedInitMessage: %v", initMessage, expectedInitMessage) - } - - // 2. Directly from chain config - initMessage, err = nitroinit.GetParsedInitMsgFromChainConfig(builder.chainConfig) - Require(t, err) - - if success := reflect.DeepEqual(initMessage, expectedInitMessage); !success { - t.Fatalf("diff found in initMessage %v and expectedInitMessage: %v", initMessage, expectedInitMessage) - } -} - func TestGetParsedInitMsgFromParentChain(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() From ecb19306fea701556792a75d2b0dc62395d75439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 12 Mar 2026 16:07:34 +0100 Subject: [PATCH 36/39] Parse fee only when chainConfigSet is true --- cmd/conf/init.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index d887b3741fb..ea9564ac31e 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -60,9 +60,9 @@ func (c *GenesisOverrideConfig) Validate() error { if err := json.Unmarshal([]byte(c.SerializedChainConfig), &chainConfig); err != nil { return fmt.Errorf("failed to unmarshal serialized-chain-config: %w", err) } - } - if _, err := c.ParseInitialL1BaseFee(); err != nil { - return fmt.Errorf("failed to parse initial-l1-base-fee: %w", err) + if _, err := c.ParseInitialL1BaseFee(); err != nil { + return fmt.Errorf("failed to parse initial-l1-base-fee: %w", err) + } } return nil } From e638907afb9817f88b37cf538bf2ac71ed4b9f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 12 Mar 2026 16:09:12 +0100 Subject: [PATCH 37/39] Chain config shouldn't be nil --- cmd/nitro/init/init.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index 7ecec8c11dc..dbd9c7f05b5 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1108,7 +1108,7 @@ func GetExecutionParsedInitMsg(genesis *core.Genesis, genesisOverride *conf.Gene } func validateChainConfigCompatibility(parsedInitMessage *arbostypes.ParsedInitMessage, chainConfig *params.ChainConfig) error { - if parsedInitMessage.ChainConfig != nil && chainConfig != nil { + if parsedInitMessage.ChainConfig != nil { if err := parsedInitMessage.ChainConfig.CheckCompatible(chainConfig, chainConfig.ArbitrumChainParams.GenesisBlockNum, 0); err != nil { return fmt.Errorf("local chain config is incompatible with the chain config from the init message: %w", err) } From d5c674a306ffdb0d917f2e3cb91d1d56f7b437a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 12 Mar 2026 16:23:15 +0100 Subject: [PATCH 38/39] Return error if fee not set --- cmd/conf/init.go | 3 +-- cmd/nitro/init/init.go | 3 --- cmd/nitro/init/init_test.go | 10 +++++----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/cmd/conf/init.go b/cmd/conf/init.go index ea9564ac31e..d547bfcbd7c 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -34,10 +34,9 @@ func GenesisOverrideConfigAddOptions(prefix string, f *pflag.FlagSet) { } // ParseInitialL1BaseFee parses the InitialL1BaseFee string into a *big.Int. -// Returns nil if the string is empty (not set). func (c *GenesisOverrideConfig) ParseInitialL1BaseFee() (*big.Int, error) { if c.InitialL1BaseFee == "" { - return nil, nil + return nil, fmt.Errorf("initial-l1-base-fee is not set") } fee, ok := new(big.Int).SetString(c.InitialL1BaseFee, 10) if !ok { diff --git a/cmd/nitro/init/init.go b/cmd/nitro/init/init.go index dbd9c7f05b5..eff0c7169ab 100644 --- a/cmd/nitro/init/init.go +++ b/cmd/nitro/init/init.go @@ -1145,9 +1145,6 @@ func GetParsedInitMsgFromGenesisOverride(genesisOverride *conf.GenesisOverrideCo if err != nil { return nil, err } - if fee == nil { - return nil, fmt.Errorf("genesis override is missing initial-l1-base-fee") - } return &arbostypes.ParsedInitMessage{ ChainId: chainConfig.ChainID, diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 0048abe51a8..7d053a3135c 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1403,7 +1403,7 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { } _, err := GetParsedInitMsgFromGenesisOverride(override) require.Error(t, err) - require.Contains(t, err.Error(), "genesis override is missing initial-l1-base-fee") + require.Contains(t, err.Error(), "initial-l1-base-fee is not set") }) t.Run("with custom initial L1 base fee", func(t *testing.T) { @@ -1609,11 +1609,11 @@ func TestGenesisOverrideConfig(t *testing.T) { require.False(t, c.IsSet()) }) - t.Run("ParseInitialL1BaseFee returns nil when not set", func(t *testing.T) { + t.Run("ParseInitialL1BaseFee errors when not set", func(t *testing.T) { c := conf.GenesisOverrideConfig{} - fee, err := c.ParseInitialL1BaseFee() - require.NoError(t, err) - require.Nil(t, fee) + _, err := c.ParseInitialL1BaseFee() + require.Error(t, err) + require.Contains(t, err.Error(), "initial-l1-base-fee is not set") }) t.Run("ParseInitialL1BaseFee returns value when set", func(t *testing.T) { From b5632247107d4902ebfc641ba4324c7af0564072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Miko=C5=82ajczyk?= Date: Thu, 12 Mar 2026 16:26:17 +0100 Subject: [PATCH 39/39] Remove unneeded test --- cmd/nitro/init/init_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cmd/nitro/init/init_test.go b/cmd/nitro/init/init_test.go index 7d053a3135c..7eca11746b6 100644 --- a/cmd/nitro/init/init_test.go +++ b/cmd/nitro/init/init_test.go @@ -1396,16 +1396,6 @@ func TestGetParsedInitMsgFromGenesisOverride(t *testing.T) { require.NotNil(t, msg.ChainConfig) }) - t.Run("missing initial L1 base fee returns error", func(t *testing.T) { - override := &conf.GenesisOverrideConfig{ - SerializedChainConfig: serializedConfig, - InitialL1BaseFee: "", - } - _, err := GetParsedInitMsgFromGenesisOverride(override) - require.Error(t, err) - require.Contains(t, err.Error(), "initial-l1-base-fee is not set") - }) - t.Run("with custom initial L1 base fee", func(t *testing.T) { override := &conf.GenesisOverrideConfig{ SerializedChainConfig: serializedConfig,