From d21b735ccbdb46afd964d07e80c698fce6ae9595 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 24 Sep 2025 21:24:06 +0100 Subject: [PATCH 01/25] add draft of debug block --- arbos/block_processor.go | 11 +++++++++ cmd/chaininfo/arbitrum_chain_info.json | 33 +++++++++++++++++++++++++ cmd/nitro/init.go | 6 ++++- cmd/nitro/nitro.go | 4 +-- cmd/util/confighelpers/configuration.go | 25 +++++++++++++++++++ execution/gethexec/executionengine.go | 6 +++-- execution/gethexec/sequencer.go | 20 ++++++++++++--- 7 files changed, 97 insertions(+), 8 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 21a091aeda..44fe67dcc6 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -10,10 +10,13 @@ import ( "math" "math/big" + "github.com/holiman/uint256" + "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" @@ -252,6 +255,14 @@ func ProduceBlockAdvanced( firstTx := types.NewTx(startTx) + if chainConfig.DebugMode() && chainConfig.DebugBlock() == header.Number.Uint64() { + log.Warn("producing debug block, prefunding dev account") + devAddr := common.HexToAddress("0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E") + balance := uint256.MustFromHex("0xfffffffffffffffffffffffffffffffffffffff") + statedb.SetBalance(devAddr, balance, tracing.BalanceChangeUnspecified) + expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) + } + for { // repeatedly process the next tx, doing redeems created along the way in FIFO order diff --git a/cmd/chaininfo/arbitrum_chain_info.json b/cmd/chaininfo/arbitrum_chain_info.json index 6126acf9b0..9a7933306f 100644 --- a/cmd/chaininfo/arbitrum_chain_info.json +++ b/cmd/chaininfo/arbitrum_chain_info.json @@ -211,6 +211,39 @@ } } }, + { + "chain-name": "arb-bench-test", + "chain-config": { + "chainId": 412348, + "homesteadBlock": 0, + "daoForkBlock": null, + "daoForkSupport": true, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "clique": { + "period": 0, + "epoch": 0 + }, + "arbitrum": { + "EnableArbOS": true, + "AllowDebugPrecompiles": false, + "DataAvailabilityCommittee": false, + "InitialArbOSVersion": 50, + "InitialChainOwner": "0x0000000000000000000000000000000000000000", + "GenesisBlockNum": 0, + "DebugBlock":3 + } + } + }, { "chain-id": 421614, "parent-chain-id": 11155111, diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 6c7e340f32..08aca99788 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -40,7 +40,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/bold/chain-abstraction" + protocol "github.com/offchainlabs/nitro/bold/chain-abstraction" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/pruning" @@ -621,6 +621,10 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err := dbutil.UnfinishedConversionCheck(chainData); err != nil { return nil, nil, fmt.Errorf("l2chaindata unfinished database conversion check error: %w", err) } + chainConfig.ArbitrumChainParams.AllowDebugPrecompiles = config.Init.DevInit + if config.Init.DevInit { + chainConfig.ArbitrumChainParams.DebugBlock = config.Init.DevInitBlockNum + } wasmDb, err := stack.OpenDatabaseWithOptions("wasm", node.DatabaseOptions{Cache: config.Execution.Caching.DatabaseCache, Handles: config.Persistent.Handles, MetricsNamespace: "wasm/", PebbleExtraOptions: persistentConfig.Pebble.ExtraOptions("wasm")}) if err != nil { return nil, nil, err diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index 2c419f170b..41f0ea4870 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -48,7 +48,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/resourcemanager" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/blocks_reexecutor" + blocksreexecutor "github.com/offchainlabs/nitro/blocks_reexecutor" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/genericconf" @@ -61,7 +61,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" - "github.com/offchainlabs/nitro/staker/legacy" + legacystaker "github.com/offchainlabs/nitro/staker/legacy" "github.com/offchainlabs/nitro/staker/validatorwallet" nitroutil "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/colors" diff --git a/cmd/util/confighelpers/configuration.go b/cmd/util/confighelpers/configuration.go index 451409cddf..1d58a4f24d 100644 --- a/cmd/util/confighelpers/configuration.go +++ b/cmd/util/confighelpers/configuration.go @@ -215,6 +215,29 @@ func devFlagArgs() []string { return args } +func dev2FlagArgs() []string { + args := []string{ + "--init.dev-init", + // "--init.dev-init-address", "0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E", + "--chain.dev-wallet.private-key", "5cbd86f39447c546b2b43f62c586099b77dfb77837f333b45439590a9f460b47", + "--node.dangerous.no-l1-listener", + "--node.parent-chain-reader.enable=false", + "--parent-chain.id=1337", + "--chain.id=412348", + "--persistent.chain", "/tmp/dev-test", + "--node.sequencer", + "--execution.sequencer.enable", + "--node.dangerous.no-sequencer-coordinator", + "--node.staker.enable=false", + "--init.empty=false", + "--http.port", "8547", + "--http.addr", "127.0.0.1", + "--http.api=net,web3,eth,arb,arbdebug,debug", + "--node.transaction-streamer.track-block-metadata-from=1", + } + return args +} + func BeginCommonParse(f *pflag.FlagSet, args []string) (*koanf.Koanf, error) { var expandedArgs []string for _, arg := range args { @@ -222,6 +245,8 @@ func BeginCommonParse(f *pflag.FlagSet, args []string) (*koanf.Koanf, error) { return nil, ErrVersion } else if arg == "--dev" { expandedArgs = append(expandedArgs, devFlagArgs()...) + } else if arg == "--dev2" { + expandedArgs = append(expandedArgs, dev2FlagArgs()...) } else { expandedArgs = append(expandedArgs, arg) } diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index 011a0ff4e7..0f2d437889 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -593,7 +593,9 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. blockCalcTime := time.Since(startTime) blockExecutionTimer.Update(blockCalcTime.Nanoseconds()) - if len(receipts) == 0 { + debugBlock := s.bc.Config().DebugMode() && block.NumberU64() == s.bc.Config().DebugBlock() + + if len(receipts) == 0 && !debugBlock { return nil, nil } @@ -604,7 +606,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. break } } - if allTxsErrored { + if allTxsErrored && !debugBlock { return nil, nil } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index aacecd55e6..74da2b3688 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -86,8 +86,9 @@ type SequencerConfig struct { } type DangerousConfig struct { - DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"` - DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"` + DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"` + DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"` + SequenceDebugBlock uint64 `koanf:"sequence-debug-block"` } type TimeboostConfig struct { @@ -198,6 +199,7 @@ var DefaultSequencerConfig = SequencerConfig{ var DefaultDangerousConfig = DangerousConfig{ DisableSeqInboxMaxDataSizeCheck: false, + SequenceDebugBlock: 0, } func SequencerConfigAddOptions(prefix string, f *pflag.FlagSet) { @@ -239,6 +241,7 @@ func TimeboostAddOptions(prefix string, f *pflag.FlagSet) { func DangerousAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".disable-seq-inbox-max-data-size-check", DefaultDangerousConfig.DisableSeqInboxMaxDataSizeCheck, "DANGEROUS! disables nitro checks on sequencer MaxTxDataSize against the sequencer inbox MaxDataSize") f.Bool(prefix+".disable-blob-base-fee-check", DefaultDangerousConfig.DisableBlobBaseFeeCheck, "DANGEROUS! disables nitro checks on sequencer for blob base fee") + f.Uint64(prefix+".sequence-debug-block", DefaultDangerousConfig.SequenceDebugBlock, "DANGEROUS! sequences a block that prefunds dev wallet") } type txQueueItem struct { @@ -1112,7 +1115,15 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { var startOfReadingFromTxQueue time.Time - for { + makeEmptyBlock := false + if s.execEngine.bc.Config().DebugMode() { + debugBlock := s.execEngine.bc.Config().ArbitrumChainParams.DebugBlock + if lastBlock.Number.Uint64()+1 == debugBlock { + // if we are about to produce block with DebugBlock number, we don't want to wait for new tranasactions and we just go ahead triggering an empty block (with startTx only) + makeEmptyBlock = true + } + } + for !makeEmptyBlock { if len(queueItems) == 1 { startOfReadingFromTxQueue = time.Now() } else if len(queueItems) > 1 && time.Since(startOfReadingFromTxQueue) > config.ReadFromTxQueueTimeout { @@ -1340,6 +1351,9 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { } queueItem.returnResult(err) } + if block != nil && makeEmptyBlock { + return true + } return madeBlock } From dc794898e6bf3f7c54dd4e7b023271c8da4cb726 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 26 Sep 2025 00:06:00 +0100 Subject: [PATCH 02/25] set chain config in debug block --- arbos/block_processor.go | 10 ++++++++++ cmd/util/confighelpers/configuration.go | 25 ------------------------- go-ethereum | 2 +- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 46b8258d0e..9a7d676b04 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -5,6 +5,7 @@ package arbos import ( "encoding/binary" + "encoding/json" "errors" "fmt" "math" @@ -263,6 +264,15 @@ func ProduceBlockAdvanced( balance := uint256.MustFromHex("0xfffffffffffffffffffffffffffffffffffffff") statedb.SetBalance(devAddr, balance, tracing.BalanceChangeUnspecified) expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) + chainConfig := chainContext.Config() + serializedChainConfig, err := json.Marshal(chainConfig) + if err != nil { + log.Error("debug block: failed to marshal chain config", "err", err) + } else { + if err = arbState.SetChainConfig(serializedChainConfig); err != nil { + log.Error("debug block: failed to set chain config in arbos state", "err", err) + } + } } for { diff --git a/cmd/util/confighelpers/configuration.go b/cmd/util/confighelpers/configuration.go index 1d58a4f24d..451409cddf 100644 --- a/cmd/util/confighelpers/configuration.go +++ b/cmd/util/confighelpers/configuration.go @@ -215,29 +215,6 @@ func devFlagArgs() []string { return args } -func dev2FlagArgs() []string { - args := []string{ - "--init.dev-init", - // "--init.dev-init-address", "0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E", - "--chain.dev-wallet.private-key", "5cbd86f39447c546b2b43f62c586099b77dfb77837f333b45439590a9f460b47", - "--node.dangerous.no-l1-listener", - "--node.parent-chain-reader.enable=false", - "--parent-chain.id=1337", - "--chain.id=412348", - "--persistent.chain", "/tmp/dev-test", - "--node.sequencer", - "--execution.sequencer.enable", - "--node.dangerous.no-sequencer-coordinator", - "--node.staker.enable=false", - "--init.empty=false", - "--http.port", "8547", - "--http.addr", "127.0.0.1", - "--http.api=net,web3,eth,arb,arbdebug,debug", - "--node.transaction-streamer.track-block-metadata-from=1", - } - return args -} - func BeginCommonParse(f *pflag.FlagSet, args []string) (*koanf.Koanf, error) { var expandedArgs []string for _, arg := range args { @@ -245,8 +222,6 @@ func BeginCommonParse(f *pflag.FlagSet, args []string) (*koanf.Koanf, error) { return nil, ErrVersion } else if arg == "--dev" { expandedArgs = append(expandedArgs, devFlagArgs()...) - } else if arg == "--dev2" { - expandedArgs = append(expandedArgs, dev2FlagArgs()...) } else { expandedArgs = append(expandedArgs, arg) } diff --git a/go-ethereum b/go-ethereum index a230ce368c..0bedefe79e 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit a230ce368ceeb6eb09cd80455dd1ac39fe248a44 +Subproject commit 0bedefe79e6e08c226fc115266584fff3922872e From 0fa4d20ee0afbf3a361b40f0367c1663ee3266bd Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 26 Sep 2025 00:09:58 +0100 Subject: [PATCH 03/25] cleanup sequencer config --- cmd/chaininfo/arbitrum_chain_info.json | 33 -------------------------- cmd/nitro/nitro.go | 4 ++-- execution/gethexec/sequencer.go | 7 ++---- 3 files changed, 4 insertions(+), 40 deletions(-) diff --git a/cmd/chaininfo/arbitrum_chain_info.json b/cmd/chaininfo/arbitrum_chain_info.json index 9a7933306f..6126acf9b0 100644 --- a/cmd/chaininfo/arbitrum_chain_info.json +++ b/cmd/chaininfo/arbitrum_chain_info.json @@ -211,39 +211,6 @@ } } }, - { - "chain-name": "arb-bench-test", - "chain-config": { - "chainId": 412348, - "homesteadBlock": 0, - "daoForkBlock": null, - "daoForkSupport": true, - "eip150Block": 0, - "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "berlinBlock": 0, - "londonBlock": 0, - "clique": { - "period": 0, - "epoch": 0 - }, - "arbitrum": { - "EnableArbOS": true, - "AllowDebugPrecompiles": false, - "DataAvailabilityCommittee": false, - "InitialArbOSVersion": 50, - "InitialChainOwner": "0x0000000000000000000000000000000000000000", - "GenesisBlockNum": 0, - "DebugBlock":3 - } - } - }, { "chain-id": 421614, "parent-chain-id": 11155111, diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index 41f0ea4870..2c419f170b 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -48,7 +48,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/resourcemanager" "github.com/offchainlabs/nitro/arbutil" - blocksreexecutor "github.com/offchainlabs/nitro/blocks_reexecutor" + "github.com/offchainlabs/nitro/blocks_reexecutor" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/cmd/genericconf" @@ -61,7 +61,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" - legacystaker "github.com/offchainlabs/nitro/staker/legacy" + "github.com/offchainlabs/nitro/staker/legacy" "github.com/offchainlabs/nitro/staker/validatorwallet" nitroutil "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/colors" diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 74da2b3688..ef16a4a0e0 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -86,9 +86,8 @@ type SequencerConfig struct { } type DangerousConfig struct { - DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"` - DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"` - SequenceDebugBlock uint64 `koanf:"sequence-debug-block"` + DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"` + DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"` } type TimeboostConfig struct { @@ -199,7 +198,6 @@ var DefaultSequencerConfig = SequencerConfig{ var DefaultDangerousConfig = DangerousConfig{ DisableSeqInboxMaxDataSizeCheck: false, - SequenceDebugBlock: 0, } func SequencerConfigAddOptions(prefix string, f *pflag.FlagSet) { @@ -241,7 +239,6 @@ func TimeboostAddOptions(prefix string, f *pflag.FlagSet) { func DangerousAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".disable-seq-inbox-max-data-size-check", DefaultDangerousConfig.DisableSeqInboxMaxDataSizeCheck, "DANGEROUS! disables nitro checks on sequencer MaxTxDataSize against the sequencer inbox MaxDataSize") f.Bool(prefix+".disable-blob-base-fee-check", DefaultDangerousConfig.DisableBlobBaseFeeCheck, "DANGEROUS! disables nitro checks on sequencer for blob base fee") - f.Uint64(prefix+".sequence-debug-block", DefaultDangerousConfig.SequenceDebugBlock, "DANGEROUS! sequences a block that prefunds dev wallet") } type txQueueItem struct { From 94f58b26a053f80370fe0ebf0415760f4de46b56 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 26 Sep 2025 00:34:39 +0100 Subject: [PATCH 04/25] open arbosstate for writing chain config in debug block --- arbos/block_processor.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 9a7d676b04..c74a8b1056 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -265,13 +265,12 @@ func ProduceBlockAdvanced( statedb.SetBalance(devAddr, balance, tracing.BalanceChangeUnspecified) expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) chainConfig := chainContext.Config() - serializedChainConfig, err := json.Marshal(chainConfig) - if err != nil { + if serializedChainConfig, err := json.Marshal(chainConfig); err != nil { log.Error("debug block: failed to marshal chain config", "err", err) - } else { - if err = arbState.SetChainConfig(serializedChainConfig); err != nil { - log.Error("debug block: failed to set chain config in arbos state", "err", err) - } + } else if arbStateWrite, err := arbosState.OpenSystemArbosState(statedb, nil, false); err != nil { + log.Error("debug block: failed to open arbos state for writing", "err", err) + } else if err = arbStateWrite.SetChainConfig(serializedChainConfig); err != nil { + log.Error("debug block: failed to set chain config in arbos state", "err", err) } } From b7583c512edddb2a4b1e978da131e22bda527dce Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 26 Sep 2025 23:31:17 +0100 Subject: [PATCH 05/25] use chainConfig.ArbitrumChainParams.DebugAddress in debug block --- arbos/block_processor.go | 14 +++++++++----- execution/gethexec/executionengine.go | 2 +- go-ethereum | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/arbos/block_processor.go b/arbos/block_processor.go index c74a8b1056..6710030a60 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -258,12 +258,15 @@ func ProduceBlockAdvanced( firstTx := types.NewTx(startTx) - if chainConfig.DebugMode() && chainConfig.DebugBlock() == header.Number.Uint64() { - log.Warn("producing debug block, prefunding dev account") - devAddr := common.HexToAddress("0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E") - balance := uint256.MustFromHex("0xfffffffffffffffffffffffffffffffffffffff") - statedb.SetBalance(devAddr, balance, tracing.BalanceChangeUnspecified) + if chainConfig.DebugMode() && header.Number.Uint64() == chainConfig.ArbitrumChainParams.DebugBlock { + // fund debug account + balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) + statedb.SetBalance(chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) + + // save current chain config to arbos state in case it was changed to enable debug mode and debug block + // replay binary reads chain config from arbos state, that will enable successful validation of future blocks + // (debug block will still fail validation if chain config was changed off-chain) chainConfig := chainContext.Config() if serializedChainConfig, err := json.Marshal(chainConfig); err != nil { log.Error("debug block: failed to marshal chain config", "err", err) @@ -272,6 +275,7 @@ func ProduceBlockAdvanced( } else if err = arbStateWrite.SetChainConfig(serializedChainConfig); err != nil { log.Error("debug block: failed to set chain config in arbos state", "err", err) } + log.Warn("DANGER! Producing debug block and funding debug account", "debugAddress", chainConfig.ArbitrumChainParams.DebugAddress) } for { diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index f8cacea3a3..245c991028 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -597,7 +597,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. blockCalcTime := time.Since(startTime) blockExecutionTimer.Update(blockCalcTime.Nanoseconds()) - debugBlock := s.bc.Config().DebugMode() && block.NumberU64() == s.bc.Config().DebugBlock() + debugBlock := s.bc.Config().DebugMode() && block.NumberU64() == s.bc.Config().ArbitrumChainParams.DebugBlock if len(receipts) == 0 && !debugBlock { return nil, nil diff --git a/go-ethereum b/go-ethereum index 0bedefe79e..400642d1c3 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 0bedefe79e6e08c226fc115266584fff3922872e +Subproject commit 400642d1c3e37c0ef9515d46a3db453e3c322002 From 9664d998655fff2a36cc80d227213d6a8b1f4a7f Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 29 Sep 2025 23:27:28 +0100 Subject: [PATCH 06/25] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 400642d1c3..6c501440c7 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 400642d1c3e37c0ef9515d46a3db453e3c322002 +Subproject commit 6c501440c72b737c903913295b879fb4b5f1ab95 From 0089af3ea75f21a680271b489db1fde4947b9814 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 30 Sep 2025 21:56:30 +0100 Subject: [PATCH 07/25] use build tag to guard debug block injection --- Makefile | 8 ++++++++ arbos/block_processor.go | 22 +-------------------- arbos/debug_block.go | 34 +++++++++++++++++++++++++++++++++ arbos/debug_block_stub.go | 15 +++++++++++++++ execution/gethexec/sequencer.go | 2 +- 5 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 arbos/debug_block.go create mode 100644 arbos/debug_block_stub.go diff --git a/Makefile b/Makefile index 9e5fb2bb33..9eab2cc394 100644 --- a/Makefile +++ b/Makefile @@ -272,6 +272,10 @@ tests-all: tests test-go-challenge test-go-stylus test-gen-proofs wasm-ci-build: $(arbitrator_wasm_libs) $(arbitrator_test_wasms) $(stylus_test_wasms) $(output_latest)/user_test.wasm @printf $(done) +.PHONY: build-nitro-debugblock +build-nitro-debugblock: $(output_root)/bin/nitro-debugblock + @printf $(done) + .PHONY: clean clean: go clean -testcache @@ -348,6 +352,10 @@ $(output_root)/bin/seq-coordinator-manager: $(DEP_PREDICATE) build-node-deps $(output_root)/bin/dbconv: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/dbconv" +# nitro built with debug block injection support +$(output_root)/bin/nitro-debugblock: $(DEP_PREDICATE) build-node-deps + go build $(GOLANG_PARAMS) --tags debugblock -o $@ "$(CURDIR)/cmd/nitro" + # recompile wasm, but don't change timestamp unless files differ $(replay_wasm): $(DEP_PREDICATE) $(go_source) .make/solgen mkdir -p `dirname $(replay_wasm)` diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 6710030a60..da3a7b6486 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -5,19 +5,15 @@ package arbos import ( "encoding/binary" - "encoding/json" "errors" "fmt" "math" "math/big" - "github.com/holiman/uint256" - "github.com/ethereum/go-ethereum/arbitrum_types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" @@ -259,23 +255,7 @@ func ProduceBlockAdvanced( firstTx := types.NewTx(startTx) if chainConfig.DebugMode() && header.Number.Uint64() == chainConfig.ArbitrumChainParams.DebugBlock { - // fund debug account - balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) - statedb.SetBalance(chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) - expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) - - // save current chain config to arbos state in case it was changed to enable debug mode and debug block - // replay binary reads chain config from arbos state, that will enable successful validation of future blocks - // (debug block will still fail validation if chain config was changed off-chain) - chainConfig := chainContext.Config() - if serializedChainConfig, err := json.Marshal(chainConfig); err != nil { - log.Error("debug block: failed to marshal chain config", "err", err) - } else if arbStateWrite, err := arbosState.OpenSystemArbosState(statedb, nil, false); err != nil { - log.Error("debug block: failed to open arbos state for writing", "err", err) - } else if err = arbStateWrite.SetChainConfig(serializedChainConfig); err != nil { - log.Error("debug block: failed to set chain config in arbos state", "err", err) - } - log.Warn("DANGER! Producing debug block and funding debug account", "debugAddress", chainConfig.ArbitrumChainParams.DebugAddress) + debugBlockStateUpdate(statedb, expectedBalanceDelta, chainConfig) } for { diff --git a/arbos/debug_block.go b/arbos/debug_block.go new file mode 100644 index 0000000000..0fb4eb47ee --- /dev/null +++ b/arbos/debug_block.go @@ -0,0 +1,34 @@ +//go:build debugblock + +package arbos + +import ( + "encoding/json" + "math/big" + + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" + "github.com/offchainlabs/nitro/arbos/arbosState" +) + +func debugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int, chainConfig *params.ChainConfig) { + // fund debug account + balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) + statedb.SetBalance(chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) + expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) + + // save current chain config to arbos state in case it was changed to enable debug mode and debug block + // replay binary reads chain config from arbos state, that will enable successful validation of future blocks + // (debug block will still fail validation if chain config was changed off-chain) + if serializedChainConfig, err := json.Marshal(chainConfig); err != nil { + log.Error("debug block: failed to marshal chain config", "err", err) + } else if arbStateWrite, err := arbosState.OpenSystemArbosState(statedb, nil, false); err != nil { + log.Error("debug block: failed to open arbos state for writing", "err", err) + } else if err = arbStateWrite.SetChainConfig(serializedChainConfig); err != nil { + log.Error("debug block: failed to set chain config in arbos state", "err", err) + } + log.Warn("DANGER! Producing debug block and funding debug account", "debugAddress", chainConfig.ArbitrumChainParams.DebugAddress) +} diff --git a/arbos/debug_block_stub.go b/arbos/debug_block_stub.go new file mode 100644 index 0000000000..1805bce425 --- /dev/null +++ b/arbos/debug_block_stub.go @@ -0,0 +1,15 @@ +//go:build !debugblock + +package arbos + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +func debugBlockStateUpdate(_ *state.StateDB, _ *big.Int, _ *params.ChainConfig) { + log.Warn("debugBlockStateUpdate is not supported in this build") +} diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index cd2e5fb8dd..ed0acbde3b 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -1116,7 +1116,7 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { if s.execEngine.bc.Config().DebugMode() { debugBlock := s.execEngine.bc.Config().ArbitrumChainParams.DebugBlock if lastBlock.Number.Uint64()+1 == debugBlock { - // if we are about to produce block with DebugBlock number, we don't want to wait for new tranasactions and we just go ahead triggering an empty block (with startTx only) + // if we are about to produce block with DebugBlock number, we don't want to wait for new tranasactions and we just trigger an empty block (with startTx only) makeEmptyBlock = true } } From f591e5a4b09c0141311d2fee0aca0129aab989c7 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Wed, 1 Oct 2025 23:34:57 +0100 Subject: [PATCH 08/25] add dangerous config for debugblock, guard impl with build tag --- arbos/block_processor.go | 3 +- arbos/debug_block_stub.go | 15 ------ cmd/nitro/init.go | 5 +- execution/gethexec/node.go | 21 ++++++++ execution/gethexec/sequencer.go | 52 +++++++++---------- experimental/debugblock/config.go | 16 ++++++ .../debugblock}/debug_block.go | 34 +++++++++++- experimental/debugblock/debug_block_stub.go | 32 ++++++++++++ 8 files changed, 131 insertions(+), 47 deletions(-) delete mode 100644 arbos/debug_block_stub.go create mode 100644 experimental/debugblock/config.go rename {arbos => experimental/debugblock}/debug_block.go (53%) create mode 100644 experimental/debugblock/debug_block_stub.go diff --git a/arbos/block_processor.go b/arbos/block_processor.go index da3a7b6486..0a41bc2bee 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -24,6 +24,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/experimental/debugblock" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -255,7 +256,7 @@ func ProduceBlockAdvanced( firstTx := types.NewTx(startTx) if chainConfig.DebugMode() && header.Number.Uint64() == chainConfig.ArbitrumChainParams.DebugBlock { - debugBlockStateUpdate(statedb, expectedBalanceDelta, chainConfig) + debugblock.DebugBlockStateUpdate(statedb, expectedBalanceDelta, chainConfig) } for { diff --git a/arbos/debug_block_stub.go b/arbos/debug_block_stub.go deleted file mode 100644 index 1805bce425..0000000000 --- a/arbos/debug_block_stub.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build !debugblock - -package arbos - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/params" -) - -func debugBlockStateUpdate(_ *state.StateDB, _ *big.Int, _ *params.ChainConfig) { - log.Warn("debugBlockStateUpdate is not supported in this build") -} diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index 08aca99788..a9eefe34e8 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -621,9 +621,8 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err := dbutil.UnfinishedConversionCheck(chainData); err != nil { return nil, nil, fmt.Errorf("l2chaindata unfinished database conversion check error: %w", err) } - chainConfig.ArbitrumChainParams.AllowDebugPrecompiles = config.Init.DevInit - if config.Init.DevInit { - chainConfig.ArbitrumChainParams.DebugBlock = config.Init.DevInitBlockNum + if config.Execution.Dangerous.DebugBlock.OverwriteChainConfig { + config.Execution.Dangerous.DebugBlock.Apply(chainConfig) } wasmDb, err := stack.OpenDatabaseWithOptions("wasm", node.DatabaseOptions{Cache: config.Execution.Caching.DatabaseCache, Handles: config.Persistent.Handles, MetricsNamespace: "wasm/", PebbleExtraOptions: persistentConfig.Pebble.ExtraOptions("wasm")}) if err != nil { diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 44ce00d7d6..168667b934 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -30,6 +30,7 @@ import ( "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/experimental/debugblock" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" @@ -127,6 +128,7 @@ type Config struct { BlockMetadataApiBlocksLimit uint64 `koanf:"block-metadata-api-blocks-limit"` VmTrace LiveTracingConfig `koanf:"vmtrace"` ExposeMultiGas bool `koanf:"expose-multi-gas"` + Dangerous DangerousConfig `koanf:"dangerous"` forwardingTarget string } @@ -155,6 +157,9 @@ func (c *Config) Validate() error { if err := c.RPC.Validate(); err != nil { return err } + if err := c.Dangerous.Validate(); err != nil { + return err + } return nil } @@ -176,6 +181,7 @@ func ConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Uint64(prefix+".block-metadata-api-blocks-limit", ConfigDefault.BlockMetadataApiBlocksLimit, "maximum number of blocks allowed to be queried for blockMetadata per arb_getRawBlockMetadata query. Enabled by default, set 0 to disable the limit") f.Bool(prefix+".expose-multi-gas", false, "experimental: expose multi-dimensional gas in transaction receipts") LiveTracingConfigAddOptions(prefix+".vmtrace", f) + DangerousConfigAddOptions(prefix+".dangerous", f) } type LiveTracingConfig struct { @@ -193,6 +199,21 @@ func LiveTracingConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".json-config", DefaultLiveTracingConfig.JSONConfig, "(experimental) Tracer configuration in JSON format") } +type DangerousConfig struct { + DebugBlock debugblock.Config `koanf:"debug-block"` +} + +var DefaultDangerousConfig = DangerousConfig{ + DebugBlock: debugblock.ConfigDefault, +} + +func DangerousConfigAddOptions(prefix string, f *pflag.FlagSet) { + debugblock.ConfigAddOptions(prefix+".debug-block", f) +} +func (c *DangerousConfig) Validate() error { + return c.DebugBlock.Validate() +} + var ConfigDefault = Config{ RPC: arbitrum.DefaultConfig, TxIndexer: DefaultTxIndexerConfig, diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index ed0acbde3b..2654874faf 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -62,30 +62,30 @@ var ( ) type SequencerConfig struct { - Enable bool `koanf:"enable"` - MaxBlockSpeed time.Duration `koanf:"max-block-speed" reload:"hot"` - ReadFromTxQueueTimeout time.Duration `koanf:"read-from-tx-queue-timeout" reload:"hot"` - MaxRevertGasReject uint64 `koanf:"max-revert-gas-reject" reload:"hot"` - MaxAcceptableTimestampDelta time.Duration `koanf:"max-acceptable-timestamp-delta" reload:"hot"` - SenderWhitelist []string `koanf:"sender-whitelist"` - Forwarder ForwarderConfig `koanf:"forwarder"` - QueueSize int `koanf:"queue-size"` - QueueTimeout time.Duration `koanf:"queue-timeout" reload:"hot"` - NonceCacheSize int `koanf:"nonce-cache-size" reload:"hot"` - MaxTxDataSize int `koanf:"max-tx-data-size" reload:"hot"` - NonceFailureCacheSize int `koanf:"nonce-failure-cache-size" reload:"hot"` - NonceFailureCacheExpiry time.Duration `koanf:"nonce-failure-cache-expiry" reload:"hot"` - ExpectedSurplusGasPriceMode string `koanf:"expected-surplus-gas-price-mode"` - ExpectedSurplusSoftThreshold string `koanf:"expected-surplus-soft-threshold" reload:"hot"` - ExpectedSurplusHardThreshold string `koanf:"expected-surplus-hard-threshold" reload:"hot"` - EnableProfiling bool `koanf:"enable-profiling" reload:"hot"` - Timeboost TimeboostConfig `koanf:"timeboost"` - Dangerous DangerousConfig `koanf:"dangerous"` + Enable bool `koanf:"enable"` + MaxBlockSpeed time.Duration `koanf:"max-block-speed" reload:"hot"` + ReadFromTxQueueTimeout time.Duration `koanf:"read-from-tx-queue-timeout" reload:"hot"` + MaxRevertGasReject uint64 `koanf:"max-revert-gas-reject" reload:"hot"` + MaxAcceptableTimestampDelta time.Duration `koanf:"max-acceptable-timestamp-delta" reload:"hot"` + SenderWhitelist []string `koanf:"sender-whitelist"` + Forwarder ForwarderConfig `koanf:"forwarder"` + QueueSize int `koanf:"queue-size"` + QueueTimeout time.Duration `koanf:"queue-timeout" reload:"hot"` + NonceCacheSize int `koanf:"nonce-cache-size" reload:"hot"` + MaxTxDataSize int `koanf:"max-tx-data-size" reload:"hot"` + NonceFailureCacheSize int `koanf:"nonce-failure-cache-size" reload:"hot"` + NonceFailureCacheExpiry time.Duration `koanf:"nonce-failure-cache-expiry" reload:"hot"` + ExpectedSurplusGasPriceMode string `koanf:"expected-surplus-gas-price-mode"` + ExpectedSurplusSoftThreshold string `koanf:"expected-surplus-soft-threshold" reload:"hot"` + ExpectedSurplusHardThreshold string `koanf:"expected-surplus-hard-threshold" reload:"hot"` + EnableProfiling bool `koanf:"enable-profiling" reload:"hot"` + Timeboost TimeboostConfig `koanf:"timeboost"` + Dangerous SequencerDangerousConfig `koanf:"dangerous"` expectedSurplusSoftThreshold int expectedSurplusHardThreshold int } -type DangerousConfig struct { +type SequencerDangerousConfig struct { DisableSeqInboxMaxDataSizeCheck bool `koanf:"disable-seq-inbox-max-data-size-check"` DisableBlobBaseFeeCheck bool `koanf:"disable-blob-base-fee-check"` } @@ -193,10 +193,10 @@ var DefaultSequencerConfig = SequencerConfig{ ExpectedSurplusHardThreshold: "default", EnableProfiling: false, Timeboost: DefaultTimeboostConfig, - Dangerous: DefaultDangerousConfig, + Dangerous: DefaultSequencerDangerousConfig, } -var DefaultDangerousConfig = DangerousConfig{ +var DefaultSequencerDangerousConfig = SequencerDangerousConfig{ DisableSeqInboxMaxDataSizeCheck: false, } @@ -210,7 +210,7 @@ func SequencerConfigAddOptions(prefix string, f *pflag.FlagSet) { AddOptionsForSequencerForwarderConfig(prefix+".forwarder", f) TimeboostAddOptions(prefix+".timeboost", f) - DangerousAddOptions(prefix+".dangerous", f) + SequencerDangerousAddOptions(prefix+".dangerous", f) f.Int(prefix+".queue-size", DefaultSequencerConfig.QueueSize, "size of the pending tx queue") f.Duration(prefix+".queue-timeout", DefaultSequencerConfig.QueueTimeout, "maximum amount of time transaction can wait in queue") f.Int(prefix+".nonce-cache-size", DefaultSequencerConfig.NonceCacheSize, "size of the tx sender nonce cache") @@ -236,9 +236,9 @@ func TimeboostAddOptions(prefix string, f *pflag.FlagSet) { f.Uint64(prefix+".queue-timeout-in-blocks", DefaultTimeboostConfig.QueueTimeoutInBlocks, "maximum amount of time (measured in blocks) that Express Lane transactions can wait in the sequencer's queue") } -func DangerousAddOptions(prefix string, f *pflag.FlagSet) { - f.Bool(prefix+".disable-seq-inbox-max-data-size-check", DefaultDangerousConfig.DisableSeqInboxMaxDataSizeCheck, "DANGEROUS! disables nitro checks on sequencer MaxTxDataSize against the sequencer inbox MaxDataSize") - f.Bool(prefix+".disable-blob-base-fee-check", DefaultDangerousConfig.DisableBlobBaseFeeCheck, "DANGEROUS! disables nitro checks on sequencer for blob base fee") +func SequencerDangerousAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".disable-seq-inbox-max-data-size-check", DefaultSequencerDangerousConfig.DisableSeqInboxMaxDataSizeCheck, "DANGEROUS! disables nitro checks on sequencer MaxTxDataSize against the sequencer inbox MaxDataSize") + f.Bool(prefix+".disable-blob-base-fee-check", DefaultSequencerDangerousConfig.DisableBlobBaseFeeCheck, "DANGEROUS! disables nitro checks on sequencer for blob base fee") } type txQueueItem struct { diff --git a/experimental/debugblock/config.go b/experimental/debugblock/config.go new file mode 100644 index 0000000000..40a47f4af5 --- /dev/null +++ b/experimental/debugblock/config.go @@ -0,0 +1,16 @@ +// DANGER! this file is included in all builds +// DANGER! do not place any of the experimental logic and features here + +package debugblock + +type Config struct { + OverwriteChainConfig bool `koanf:"overwrite-chain-config"` + DebugAddress string `koanf:"debug-address"` + DebugBlockNum uint64 `koanf:"debug-blocknum"` +} + +var ConfigDefault = Config{ + OverwriteChainConfig: false, + DebugAddress: "", + DebugBlockNum: 0, +} diff --git a/arbos/debug_block.go b/experimental/debugblock/debug_block.go similarity index 53% rename from arbos/debug_block.go rename to experimental/debugblock/debug_block.go index 0fb4eb47ee..caec1c3631 100644 --- a/arbos/debug_block.go +++ b/experimental/debugblock/debug_block.go @@ -1,20 +1,50 @@ //go:build debugblock -package arbos +package debugblock import ( "encoding/json" + "errors" "math/big" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/spf13/pflag" ) -func debugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int, chainConfig *params.ChainConfig) { +func ConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".overwrite-chain-config", ConfigDefault.OverwriteChainConfig, "DANGEROUS! overwrites chain when opening existing database; chain debug mode will be enabled") + f.String(prefix+".debug-address", ConfigDefault.DebugAddress, "DANGEROUS! address of debug account to be pre-funded") + f.Uint64(prefix+".debug-blocknum", ConfigDefault.DebugBlockNum, "DANGEROUS! block number of injected debug block") +} + +func (c *Config) Validate() error { + if c.OverwriteChainConfig { + log.Warn("DANGER! overwrite-chain-config set, chain config will be over-written") + } + if c.DebugAddress != "" && !common.IsHexAddress(c.DebugAddress) { + return errors.New("invalid debug-address, hex address expected") + } + if c.DebugBlockNum != 0 { + log.Warn("DANGER! debug-blocknum set", "blocknum", c.DebugBlockNum) + } + return nil +} + +func (c *Config) Apply(chainConfig *params.ChainConfig) { + if c.OverwriteChainConfig { + chainConfig.ArbitrumChainParams.AllowDebugPrecompiles = true + chainConfig.ArbitrumChainParams.DebugAddress = common.HexToAddress(c.DebugAddress) + chainConfig.ArbitrumChainParams.DebugBlock = c.DebugBlockNum + } +} + +func DebugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int, chainConfig *params.ChainConfig) { // fund debug account balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) statedb.SetBalance(chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) diff --git a/experimental/debugblock/debug_block_stub.go b/experimental/debugblock/debug_block_stub.go new file mode 100644 index 0000000000..a10eeb99b6 --- /dev/null +++ b/experimental/debugblock/debug_block_stub.go @@ -0,0 +1,32 @@ +//go:build !debugblock + +package debugblock + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + "github.com/spf13/pflag" +) + +func (c *Config) Validate() error { + if c.OverwriteChainConfig || c.DebugAddress != "" || c.DebugBlockNum != 0 { + errors.New("debug block injection is not supported in this build") + } + return nil +} + +func (c *Config) Apply(_ *params.ChainConfig) { + // do nothing +} + +func ConfigAddOptions(_ string, _ *pflag.FlagSet) { + // don't add any of debug block options +} + +func DebugBlockStateUpdate(_ *state.StateDB, _ *big.Int, _ *params.ChainConfig) { + log.Warn("debugBlockStateUpdate is not supported in this build") +} From b6deb1613f343747dbabd0fdccce785996e6b10e Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 2 Oct 2025 01:48:27 +0100 Subject: [PATCH 09/25] publish debug tx to trigger next block --- execution/gethexec/executionengine.go | 6 ++--- execution/gethexec/sequencer.go | 24 +++++++++++------- experimental/debugblock/debug_block.go | 28 +++++++++++++++++++++ experimental/debugblock/debug_block_stub.go | 8 +++++- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index 245c991028..d80b5ab10c 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -597,9 +597,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. blockCalcTime := time.Since(startTime) blockExecutionTimer.Update(blockCalcTime.Nanoseconds()) - debugBlock := s.bc.Config().DebugMode() && block.NumberU64() == s.bc.Config().ArbitrumChainParams.DebugBlock - - if len(receipts) == 0 && !debugBlock { + if len(receipts) == 0 { return nil, nil } @@ -610,7 +608,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. break } } - if allTxsErrored && !debugBlock { + if allTxsErrored { return nil, nil } diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 2654874faf..d9141288e7 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -36,6 +36,7 @@ import ( "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/experimental/debugblock" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/containers" @@ -1112,15 +1113,23 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { var startOfReadingFromTxQueue time.Time - makeEmptyBlock := false if s.execEngine.bc.Config().DebugMode() { - debugBlock := s.execEngine.bc.Config().ArbitrumChainParams.DebugBlock - if lastBlock.Number.Uint64()+1 == debugBlock { - // if we are about to produce block with DebugBlock number, we don't want to wait for new tranasactions and we just trigger an empty block (with startTx only) - makeEmptyBlock = true + chainConfig := s.execEngine.bc.Config() + if lastBlock.Number.Uint64()+1 == chainConfig.ArbitrumChainParams.DebugBlock { + // publish transaction to trigger next block + tx := debugblock.PrepareDebugTransaction(chainConfig) + if tx != nil { + go func() { + if err := s.PublishTransaction(ctx, tx, nil); err != nil { + log.Error("debug block: failed to publish tx", "err", err) + } else { + log.Warn("published tx", "txHash", tx.Hash()) + } + }() + } } } - for !makeEmptyBlock { + for { if len(queueItems) == 1 { startOfReadingFromTxQueue = time.Now() } else if len(queueItems) > 1 && time.Since(startOfReadingFromTxQueue) > config.ReadFromTxQueueTimeout { @@ -1348,9 +1357,6 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { } queueItem.returnResult(err) } - if block != nil && makeEmptyBlock { - return true - } return madeBlock } diff --git a/experimental/debugblock/debug_block.go b/experimental/debugblock/debug_block.go index caec1c3631..f2c557e09a 100644 --- a/experimental/debugblock/debug_block.go +++ b/experimental/debugblock/debug_block.go @@ -10,6 +10,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/tracing" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" @@ -44,6 +46,32 @@ func (c *Config) Apply(chainConfig *params.ChainConfig) { } } +func PrepareDebugTransaction(chainConfig *params.ChainConfig) *types.Transaction { + devPrivKey, err := crypto.HexToECDSA("") //TODO + if err != nil { + log.Error("debug block: failed to HexToECDSA", "err", err) + return nil + } + devAddr := crypto.PubkeyToAddress(devPrivKey.PublicKey) + txData := &types.DynamicFeeTx{ + To: &devAddr, + Gas: 1e9, + GasTipCap: big.NewInt(params.Ether), + GasFeeCap: big.NewInt(params.Ether), + Value: big.NewInt(1), + Nonce: 0, + Data: nil, + } + signer := types.LatestSigner(chainConfig) // TODO + tx := types.NewTx(txData) + tx, err = types.SignTx(tx, signer, devPrivKey) + if err != nil { + log.Error("debug block: failed to sign tx", "err", err) + return nil + } + return tx +} + func DebugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int, chainConfig *params.ChainConfig) { // fund debug account balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) diff --git a/experimental/debugblock/debug_block_stub.go b/experimental/debugblock/debug_block_stub.go index a10eeb99b6..a40d6c986a 100644 --- a/experimental/debugblock/debug_block_stub.go +++ b/experimental/debugblock/debug_block_stub.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/spf13/pflag" @@ -27,6 +28,11 @@ func ConfigAddOptions(_ string, _ *pflag.FlagSet) { // don't add any of debug block options } +func PrepareDebugTransaction(_ *params.ChainConfig) *types.Transaction { + log.Warn("PrepareDebugTransaction is not supported in this build") + return nil +} + func DebugBlockStateUpdate(_ *state.StateDB, _ *big.Int, _ *params.ChainConfig) { - log.Warn("debugBlockStateUpdate is not supported in this build") + log.Warn("DebugBlockStateUpdate is not supported in this build") } From 8f96031793c001201e504b4a8d270787c621a426 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 00:31:24 +0100 Subject: [PATCH 10/25] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 6c501440c7..d4af679316 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 6c501440c72b737c903913295b879fb4b5f1ab95 +Subproject commit d4af679316f9795ba9261822050865a8c1491e38 From 45039a6f48c512525f1ee543dafe15c589b5b1d5 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 00:35:31 +0100 Subject: [PATCH 11/25] fix returning error from stubbed debugblock.ConfigAddOptions --- experimental/debugblock/debug_block_stub.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/experimental/debugblock/debug_block_stub.go b/experimental/debugblock/debug_block_stub.go index a40d6c986a..a0f0edc126 100644 --- a/experimental/debugblock/debug_block_stub.go +++ b/experimental/debugblock/debug_block_stub.go @@ -6,16 +6,17 @@ import ( "errors" "math/big" + "github.com/spf13/pflag" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" - "github.com/spf13/pflag" ) func (c *Config) Validate() error { if c.OverwriteChainConfig || c.DebugAddress != "" || c.DebugBlockNum != 0 { - errors.New("debug block injection is not supported in this build") + return errors.New("debug block injection is not supported in this build") } return nil } From c55d177a8234c254443eacf259c23d7b4d638b2b Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 01:03:59 +0100 Subject: [PATCH 12/25] use hardcoded account to trigger debug block --- execution/gethexec/sequencer.go | 2 +- experimental/debugblock/debug_block.go | 55 ++++++++++++++++----- experimental/debugblock/debug_block_stub.go | 2 +- system_tests/common_test.go | 6 ++- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index d9141288e7..65c88dd04e 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -1117,7 +1117,7 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { chainConfig := s.execEngine.bc.Config() if lastBlock.Number.Uint64()+1 == chainConfig.ArbitrumChainParams.DebugBlock { // publish transaction to trigger next block - tx := debugblock.PrepareDebugTransaction(chainConfig) + tx := debugblock.PrepareDebugTransaction(chainConfig, lastBlock) if tx != nil { go func() { if err := s.PublishTransaction(ctx, tx, nil); err != nil { diff --git a/experimental/debugblock/debug_block.go b/experimental/debugblock/debug_block.go index f2c557e09a..e028ac1b29 100644 --- a/experimental/debugblock/debug_block.go +++ b/experimental/debugblock/debug_block.go @@ -3,6 +3,7 @@ package debugblock import ( + "crypto/ecdsa" "encoding/json" "errors" "math/big" @@ -16,6 +17,8 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/holiman/uint256" "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/offchainlabs/nitro/util" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/spf13/pflag" ) @@ -46,33 +49,61 @@ func (c *Config) Apply(chainConfig *params.ChainConfig) { } } -func PrepareDebugTransaction(chainConfig *params.ChainConfig) *types.Transaction { - devPrivKey, err := crypto.HexToECDSA("") //TODO +// private key and address of account to be used by PrepareDebugTransaction +func triggerPrivateKeyAndAddress() (*ecdsa.PrivateKey, common.Address, error) { + key, err := crypto.HexToECDSA("acb2d96fc54f5db4530d6c5a6adfd10964b1b62222d875e08b68b72cc9b9935c") if err != nil { - log.Error("debug block: failed to HexToECDSA", "err", err) + return nil, common.Address{}, err + } + return key, crypto.PubkeyToAddress(key.PublicKey), nil +} + +// prepares transaction used to trigger debug block creation +// the transaction needs pre-funding within DebugBlockStateUpdate (executed in the begging of debug block, before the trigger transaction) +func PrepareDebugTransaction(chainConfig *params.ChainConfig, lastHeader *types.Header) *types.Transaction { + if !chainConfig.DebugMode() { return nil } - devAddr := crypto.PubkeyToAddress(devPrivKey.PublicKey) + privateKey, address, err := triggerPrivateKeyAndAddress() + if err != nil { + log.Error("debug block: failed to get hardcoded private key and address", "err", err) + return nil + } + transferGas := util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for L1 costs txData := &types.DynamicFeeTx{ - To: &devAddr, - Gas: 1e9, - GasTipCap: big.NewInt(params.Ether), - GasFeeCap: big.NewInt(params.Ether), - Value: big.NewInt(1), + To: &address, + Gas: transferGas, + GasTipCap: big.NewInt(0), + GasFeeCap: big.NewInt(params.GWei), + Value: big.NewInt(0), Nonce: 0, Data: nil, } - signer := types.LatestSigner(chainConfig) // TODO + nextHeaderNumber := arbmath.BigAdd(lastHeader.Number, common.Big1) + arbosVersion := types.DeserializeHeaderExtraInformation(lastHeader).ArbOSFormatVersion + signer := types.MakeSigner(chainConfig, nextHeaderNumber, lastHeader.Time, arbosVersion) tx := types.NewTx(txData) - tx, err = types.SignTx(tx, signer, devPrivKey) + tx, err = types.SignTx(tx, signer, privateKey) if err != nil { - log.Error("debug block: failed to sign tx", "err", err) + log.Error("debug block: failed to sign trigger tx", "address", address, "err", err) return nil } + log.Warn("debug block: PrepareDebugTransaction", "address", address) return tx } func DebugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int, chainConfig *params.ChainConfig) { + // fund trigger account - used to send a transaction that will trigger this block + transferGas := util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for L1 costs + triggerCost := uint256.MustFromBig(new(big.Int).Mul(big.NewInt(int64(transferGas)), big.NewInt(params.GWei))) + _, triggerAddress, err := triggerPrivateKeyAndAddress() + if err != nil { + log.Error("debug block: failed to get hardcoded address", "err", err) + return + } + statedb.SetBalance(triggerAddress, triggerCost, tracing.BalanceChangeUnspecified) + expectedBalanceDelta.Add(expectedBalanceDelta, triggerCost.ToBig()) + // fund debug account balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) statedb.SetBalance(chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) diff --git a/experimental/debugblock/debug_block_stub.go b/experimental/debugblock/debug_block_stub.go index a0f0edc126..864a9d9038 100644 --- a/experimental/debugblock/debug_block_stub.go +++ b/experimental/debugblock/debug_block_stub.go @@ -29,7 +29,7 @@ func ConfigAddOptions(_ string, _ *pflag.FlagSet) { // don't add any of debug block options } -func PrepareDebugTransaction(_ *params.ChainConfig) *types.Transaction { +func PrepareDebugTransaction(_ *params.ChainConfig, _ *types.Header) *types.Transaction { log.Warn("PrepareDebugTransaction is not supported in this build") return nil } diff --git a/system_tests/common_test.go b/system_tests/common_test.go index eecbcf1e43..d2e2debc83 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -889,7 +889,11 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { dataSigner = signature.DataSignerFromPrivateKey(b.L1Info.GetInfoWithPrivKey("Sequencer").PrivateKey) l1Client = b.L1.Client } - currentNode, err := arbnode.CreateNodeFullExecutionClient(b.ctx, stack, execNode, execNode, execNode, execNode, arbDb, NewFetcherFromConfig(b.nodeConfig), blockchain.Config(), l1Client, b.addresses, validatorTxOpts, sequencerTxOpts, dataSigner, feedErrChan, big.NewInt(1337), nil, locator.LatestWasmModuleRoot()) + chainConfig := blockchain.Config() + if b.execConfig.Dangerous.DebugBlock.OverwriteChainConfig { + b.execConfig.Dangerous.DebugBlock.Apply(chainConfig) + } + currentNode, err := arbnode.CreateNodeFullExecutionClient(b.ctx, stack, execNode, execNode, execNode, execNode, arbDb, NewFetcherFromConfig(b.nodeConfig), chainConfig, l1Client, b.addresses, validatorTxOpts, sequencerTxOpts, dataSigner, feedErrChan, big.NewInt(1337), nil, locator.LatestWasmModuleRoot()) Require(t, err) Require(t, currentNode.Start(b.ctx)) From 0e1dd8414014a9d5c99a1cfb1df5687e2e753c2b Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 01:17:07 +0100 Subject: [PATCH 13/25] add system test for debug block injection --- system_tests/debug_block_test.go | 66 ++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 system_tests/debug_block_test.go diff --git a/system_tests/debug_block_test.go b/system_tests/debug_block_test.go new file mode 100644 index 0000000000..f84aee01e7 --- /dev/null +++ b/system_tests/debug_block_test.go @@ -0,0 +1,66 @@ +//go:build debugblock + +package arbtest + +import ( + "context" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" +) + +func TestDebugBlockInjection(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + cleanup := builder.Build(t) + defer cleanup() + + // send a transaction to advance the chain + builder.L2Info.GenerateAccount("SomeUser") + tx := builder.L2Info.PrepareTx("Owner", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) + err := builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // make sure that DebugUser can't send a tx just yet + builder.L2Info.GenerateAccount("DebugUser") + debugUsertx := builder.L2Info.PrepareTx("DebugUser", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) + err = builder.L2.Client.SendTransaction(ctx, debugUsertx) + if err == nil { + t.Fatal("debugUserTx shouldn't have succeeded before prefunding DebugUser account") + } + + lastBlock, err := builder.L2.Client.BlockNumber(ctx) + Require(t, err) + + builder.L2.cleanup() + builder.L2.cleanup = func() {} + t.Log("l2 node stopped") + + // configure debug block injection + debugBlockNum := lastBlock + 1 + builder.execConfig.Dangerous.DebugBlock.OverwriteChainConfig = true + builder.execConfig.Dangerous.DebugBlock.DebugBlockNum = debugBlockNum + builder.execConfig.Dangerous.DebugBlock.DebugAddress = builder.L2Info.GetInfoWithPrivKey("DebugUser").Address.String() + + builder.RestartL2Node(t) + t.Log("restarted l2 node") + + current, err := builder.L2.Client.BlockNumber(ctx) + Require(t, err) + for current < debugBlockNum { + <-time.After(100 * time.Millisecond) + current, err = builder.L2.Client.BlockNumber(ctx) + Require(t, err) + } + + // make sure that DebugUser can send a tx now + err = builder.L2.Client.SendTransaction(ctx, debugUsertx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) +} From 4352417aeae70819d42c56ba1b720efe2ba515a5 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 01:23:26 +0100 Subject: [PATCH 14/25] expand trigger account comment --- experimental/debugblock/debug_block.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/debugblock/debug_block.go b/experimental/debugblock/debug_block.go index e028ac1b29..259b61692b 100644 --- a/experimental/debugblock/debug_block.go +++ b/experimental/debugblock/debug_block.go @@ -93,7 +93,7 @@ func PrepareDebugTransaction(chainConfig *params.ChainConfig, lastHeader *types. } func DebugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int, chainConfig *params.ChainConfig) { - // fund trigger account - used to send a transaction that will trigger this block + // fund trigger account - used to send the transaction that triggered this block and needs pre-funding to succeed (at least one successful tx is required for the block to be appended to the chain) transferGas := util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for L1 costs triggerCost := uint256.MustFromBig(new(big.Int).Mul(big.NewInt(int64(transferGas)), big.NewInt(params.GWei))) _, triggerAddress, err := triggerPrivateKeyAndAddress() From 01a3e3c73c5d35ea52ea0966db41711ec06cc0b9 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 23:17:26 +0100 Subject: [PATCH 15/25] test that debug block injection doesn't affect prod build --- system_tests/common_test.go | 77 +++++++++++++-- system_tests/debug_block_stub_test.go | 11 +++ system_tests/debug_block_test.go | 57 +----------- system_tests/debug_block_test_common.go | 119 ++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 66 deletions(-) create mode 100644 system_tests/debug_block_stub_test.go create mode 100644 system_tests/debug_block_test_common.go diff --git a/system_tests/common_test.go b/system_tests/common_test.go index d2e2debc83..e784d94e7b 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -277,6 +277,8 @@ type NodeBuilder struct { delayBufferThreshold uint64 withL1ClientWrapper bool + ignoreExecConfigValidationError bool + // Created nodes L1 *TestClient L2 *TestClient @@ -417,7 +419,7 @@ func (b *NodeBuilder) RequireScheme(t *testing.T, scheme string) *NodeBuilder { if b.defaultDbScheme != scheme && b.execConfig != nil { b.execConfig.Caching.StateScheme = scheme b.execConfig.RPC.StateScheme = scheme - Require(t, b.execConfig.Validate()) + b.validateExecConfig(t) } b.defaultDbScheme = scheme return b @@ -444,6 +446,25 @@ func (b *NodeBuilder) TakeOwnership() *NodeBuilder { return b } +func (b *NodeBuilder) IgnoreExecConfigValidationError() *NodeBuilder { + b.ignoreExecConfigValidationError = true + return b +} + +func (b *NodeBuilder) validateExecConfig(t *testing.T) { + validateExecConfig(t, b.execConfig, b.ignoreExecConfigValidationError) +} + +func validateExecConfig(t *testing.T, execConfig *gethexec.Config, ignoreExecConfigValidationError bool) { + t.Helper() + err := execConfig.Validate() + if err != nil && ignoreExecConfigValidationError { + log.Warn("ignoring execution config validation error", "err", err) + return + } + Require(t, err) +} + func (b *NodeBuilder) Build(t *testing.T) func() { if b.parallelise { b.parallelise = false @@ -634,6 +655,8 @@ func buildOnParentChain( initMessage *arbostypes.ParsedInitMessage, addresses *chaininfo.RollupAddresses, + + ignoreExecConfigValidationError bool, ) *TestClient { if parentChainTestClient == nil { t.Fatal("must build parent chain before building chain") @@ -645,7 +668,8 @@ func buildOnParentChain( var arbDb ethdb.Database var blockchain *core.BlockChain _, chainTestClient.Stack, chainDb, arbDb, blockchain = createNonL1BlockChainWithStackConfig( - t, chainInfo, dataDir, chainConfig, arbOSInit, initMessage, stackConfig, execConfig) + t, chainInfo, dataDir, chainConfig, arbOSInit, initMessage, stackConfig, execConfig, + ignoreExecConfigValidationError) var sequencerTxOptsPtr *bind.TransactOpts var dataSigner signature.DataSignerFunc @@ -668,7 +692,7 @@ func buildOnParentChain( AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", valnodeConfig.Wasm.RootPath) - Require(t, execConfig.Validate()) + validateExecConfig(t, execConfig, ignoreExecConfigValidationError) execConfigToBeUsedInConfigFetcher := execConfig execConfigFetcher := func() *gethexec.Config { return execConfigToBeUsedInConfigFetcher } execNode, err := gethexec.CreateExecutionNode(ctx, chainTestClient.Stack, chainDb, blockchain, parentChainTestClient.Client, execConfigFetcher, parentChainId, 0) @@ -739,6 +763,8 @@ func (b *NodeBuilder) BuildL3OnL2(t *testing.T) func() { b.l3InitMessage, b.l3Addresses, + + b.ignoreExecConfigValidationError, ) return func() { @@ -768,6 +794,8 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { b.initMessage, b.addresses, + + b.ignoreExecConfigValidationError, ) if b.takeOwnership { @@ -812,9 +840,10 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { var arbDb ethdb.Database var blockchain *core.BlockChain b.L2Info, b.L2.Stack, chainDb, arbDb, blockchain = createNonL1BlockChainWithStackConfig( - t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, nil, b.l2StackConfig, b.execConfig) + t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, nil, b.l2StackConfig, b.execConfig, + b.ignoreExecConfigValidationError) - Require(t, b.execConfig.Validate()) + b.validateExecConfig(t) execConfig := b.execConfig execConfigFetcher := func() *gethexec.Config { return execConfig } execNode, err := gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) @@ -868,7 +897,7 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { } b.L2.cleanup() - l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, b.initMessage, b.l2StackConfig, b.execConfig) + l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, b.initMessage, b.l2StackConfig, b.execConfig, b.ignoreExecConfigValidationError) execConfigFetcher := func() *gethexec.Config { return b.execConfig } execNode, err := gethexec.CreateExecutionNode(b.ctx, stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) @@ -930,6 +959,8 @@ func build2ndNode( addresses *chaininfo.RollupAddresses, initMessage *arbostypes.ParsedInitMessage, + + ignoreExecConfigValidationError bool, ) (*TestClient, func()) { if params.nodeConfig == nil { params.nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() @@ -964,7 +995,7 @@ func build2ndNode( testClient := NewTestClient(ctx) testClient.Client, testClient.ConsensusNode = - Create2ndNodeWithConfig(t, ctx, firstNodeTestClient.ConsensusNode, parentChainTestClient.Stack, parentChainInfo, params.initData, params.nodeConfig, params.execConfig, params.stackConfig, valnodeConfig, params.addresses, initMessage, params.useExecutionClientOnly) + Create2ndNodeWithConfig(t, ctx, firstNodeTestClient.ConsensusNode, parentChainTestClient.Stack, parentChainInfo, params.initData, params.nodeConfig, params.execConfig, params.stackConfig, valnodeConfig, params.addresses, initMessage, params.useExecutionClientOnly, ignoreExecConfigValidationError) testClient.ExecNode = getExecNode(t, testClient.ConsensusNode) testClient.cleanup = func() { testClient.ConsensusNode.StopAndWait() } return testClient, func() { testClient.cleanup() } @@ -996,6 +1027,8 @@ func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*Tes b.addresses, b.initMessage, + + b.ignoreExecConfigValidationError, ) } @@ -1022,6 +1055,8 @@ func (b *NodeBuilder) Build2ndNodeOnL3(t *testing.T, params *SecondNodeParams) ( b.l3Addresses, b.l3InitMessage, + + b.ignoreExecConfigValidationError, ) } @@ -1658,7 +1693,7 @@ func deployOnParentChain( } func createNonL1BlockChainWithStackConfig( - t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, arbOSInit *params.ArbOSInit, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, + t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, arbOSInit *params.ArbOSInit, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, ignoreExecConfigValidationError bool, ) (*BlockchainTestInfo, *node.Node, ethdb.Database, ethdb.Database, *core.BlockChain) { if info == nil { info = NewArbTestInfo(t, chainConfig.ChainID) @@ -1669,7 +1704,8 @@ func createNonL1BlockChainWithStackConfig( if execConfig == nil { execConfig = ExecConfigDefaultTest(t, env.GetTestStateScheme()) } - Require(t, execConfig.Validate()) + + validateExecConfig(t, execConfig, ignoreExecConfigValidationError) stack, err := node.New(stackConfig) Require(t, err) @@ -1752,6 +1788,7 @@ func Create2ndNodeWithConfig( addresses *chaininfo.RollupAddresses, initMessage *arbostypes.ParsedInitMessage, useExecutionClientOnly bool, + ignoreExecConfigValidationError bool, ) (*ethclient.Client, *arbnode.Node) { if nodeConfig == nil { nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() @@ -1759,7 +1796,7 @@ func Create2ndNodeWithConfig( if execConfig == nil { t.Fatal("should not be nil") } - Require(t, execConfig.Validate()) + validateExecConfig(t, execConfig, ignoreExecConfigValidationError) feedErrChan := make(chan error, 10) parentChainRpcClient := parentChainStack.Attach() @@ -2156,3 +2193,23 @@ func populateMachineDir(t *testing.T, cr *github.ConsensusRelease) string { Require(t, err) return machineDir } + +// will call foo with specified interval, until foo returns true or specified timeout elapses +// if timeout elapses fails with t.Fatal with timeoutMessage appended to the message +// note: use pollWithDeadlineDefault if you don't care much about the interval and timeout, should make it easier to globally tune the tests +func pollWithDeadline(t *testing.T, interval time.Duration, timeout time.Duration, foo func() bool) bool { + t.Helper() + deadline := time.After(timeout) + for !foo() { + select { + case <-deadline: + return false + case <-time.After(interval): + } + } + return true +} + +func pollWithDeadlineDefault(t *testing.T, foo func() bool) bool { + return pollWithDeadline(t, 20*time.Millisecond, 5*time.Second, foo) +} diff --git a/system_tests/debug_block_stub_test.go b/system_tests/debug_block_stub_test.go new file mode 100644 index 0000000000..8dc338805b --- /dev/null +++ b/system_tests/debug_block_stub_test.go @@ -0,0 +1,11 @@ +//go:build !debugblock + +package arbtest + +import ( + "testing" +) + +func TestDebugBlockInjection(t *testing.T) { + t.Run("production", func(t *testing.T) { testDebugBlockInjection(t, true) }) +} diff --git a/system_tests/debug_block_test.go b/system_tests/debug_block_test.go index f84aee01e7..862b5f0a3c 100644 --- a/system_tests/debug_block_test.go +++ b/system_tests/debug_block_test.go @@ -3,64 +3,9 @@ package arbtest import ( - "context" "testing" - "time" - - "github.com/ethereum/go-ethereum/common" ) func TestDebugBlockInjection(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - cleanup := builder.Build(t) - defer cleanup() - - // send a transaction to advance the chain - builder.L2Info.GenerateAccount("SomeUser") - tx := builder.L2Info.PrepareTx("Owner", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) - err := builder.L2.Client.SendTransaction(ctx, tx) - Require(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - Require(t, err) - - // make sure that DebugUser can't send a tx just yet - builder.L2Info.GenerateAccount("DebugUser") - debugUsertx := builder.L2Info.PrepareTx("DebugUser", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) - err = builder.L2.Client.SendTransaction(ctx, debugUsertx) - if err == nil { - t.Fatal("debugUserTx shouldn't have succeeded before prefunding DebugUser account") - } - - lastBlock, err := builder.L2.Client.BlockNumber(ctx) - Require(t, err) - - builder.L2.cleanup() - builder.L2.cleanup = func() {} - t.Log("l2 node stopped") - - // configure debug block injection - debugBlockNum := lastBlock + 1 - builder.execConfig.Dangerous.DebugBlock.OverwriteChainConfig = true - builder.execConfig.Dangerous.DebugBlock.DebugBlockNum = debugBlockNum - builder.execConfig.Dangerous.DebugBlock.DebugAddress = builder.L2Info.GetInfoWithPrivKey("DebugUser").Address.String() - - builder.RestartL2Node(t) - t.Log("restarted l2 node") - - current, err := builder.L2.Client.BlockNumber(ctx) - Require(t, err) - for current < debugBlockNum { - <-time.After(100 * time.Millisecond) - current, err = builder.L2.Client.BlockNumber(ctx) - Require(t, err) - } - - // make sure that DebugUser can send a tx now - err = builder.L2.Client.SendTransaction(ctx, debugUsertx) - Require(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - Require(t, err) + t.Run("debugblock", func(t *testing.T) { testDebugBlockInjection(t, false) }) } diff --git a/system_tests/debug_block_test_common.go b/system_tests/debug_block_test_common.go new file mode 100644 index 0000000000..d81f044d83 --- /dev/null +++ b/system_tests/debug_block_test_common.go @@ -0,0 +1,119 @@ +package arbtest + +import ( + "context" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func testDebugBlockInjection(t *testing.T, production bool) { + t.Run("with-other-tx", func(t *testing.T) { + testDebugBlockInjectionImpl(t, production, true) + }) + t.Run("without-other-tx", func(t *testing.T) { + testDebugBlockInjectionImpl(t, production, false) + }) +} + +func testDebugBlockInjectionImpl(t *testing.T, production bool, withOtherTx bool) { + expectInject := !production + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + cleanup := builder.Build(t) + defer cleanup() + + startBlock, err := builder.L2.Client.BlockNumber(ctx) + Require(t, err) + + // send a transaction to advance the chain + builder.L2Info.GenerateAccount("SomeUser") + tx := builder.L2Info.PrepareTx("Owner", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) + builder.L2.SendWaitTestTransactions(t, types.Transactions{tx}) + + // make sure that DebugUser can't send a tx just yet + builder.L2Info.GenerateAccount("DebugUser") + debugUserTx := builder.L2Info.PrepareTx("DebugUser", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) + err = builder.L2.Client.SendTransaction(ctx, debugUserTx) + if err == nil { + t.Fatal("debugUserTx shouldn't have succeeded before prefunding DebugUser account") + } + + // make sure the chain advanced + lastBlock := startBlock + advanced := pollWithDeadlineDefault(t, func() bool { + var err error + lastBlock, err = builder.L2.Client.BlockNumber(ctx) + Require(t, err) + return lastBlock > startBlock + }) + if !advanced { + t.Fatal("failed to advance chain: timeout exceeded") + } + + builder.L2.cleanup() + builder.L2.cleanup = func() {} + t.Log("l2 node stopped") + + // configure debug block injection + debugBlockNum := lastBlock + 1 + builder.execConfig.Dangerous.DebugBlock.OverwriteChainConfig = true + builder.execConfig.Dangerous.DebugBlock.DebugBlockNum = debugBlockNum + builder.execConfig.Dangerous.DebugBlock.DebugAddress = builder.L2Info.GetInfoWithPrivKey("DebugUser").Address.String() + + if production { + err := builder.execConfig.Validate() + if err == nil { + t.Fatal("execConfig validation should have failed in production build when chain config overwrite is specified") + } else if !strings.Contains(err.Error(), "debug block injection is not supported") { + t.Fatal("execConfig validation failed with unexpected error, err:", err) + } + // ignore execConfig validation failure during restart, to test that the dangerous configs don't have effect in production + builder.IgnoreExecConfigValidationError() + } + + builder.RestartL2Node(t) + t.Log("restarted l2 node") + + if withOtherTx { + tx := builder.L2Info.PrepareTx("Owner", "SomeUser", builder.L2Info.TransferGas, common.Big1, nil) + builder.L2.SendWaitTestTransactions(t, types.Transactions{tx}) + } + + interval := 25 * time.Millisecond + timeout := 5 * time.Second + if !expectInject && !withOtherTx { + // shorter deadline for expected timeout + timeout = 100 * time.Millisecond + } + + debugBlockReached := pollWithDeadline(t, interval, timeout, func() bool { + current, err := builder.L2.Client.BlockNumber(ctx) + Require(t, err) + t.Log("current block:", current, "debug block:", debugBlockNum) + return current >= debugBlockNum + }) + + if expectInject { + if !debugBlockReached { + t.Fatalf("debug block number not reached: %v timeout exceeded", timeout) + } + // make sure that DebugUser can send a tx now + builder.L2.SendWaitTestTransactions(t, types.Transactions{debugUserTx}) + } else { + if debugBlockReached && !withOtherTx { + t.Error("debug block number reached with no other txes to advance chain") + } + // make sure that DebugUser still can't send a tx + err = builder.L2.Client.SendTransaction(ctx, debugUserTx) + if err == nil { + t.Fatal("debugUserTx shouldn't have succeeded in production build") + } + } + +} From 7c10a5727f64b9efe396f4a286fc18fdcacaa15a Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Fri, 3 Oct 2025 23:27:57 +0100 Subject: [PATCH 16/25] rename debug block test file --- .../{debug_block_test_common.go => debug_block_common_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename system_tests/{debug_block_test_common.go => debug_block_common_test.go} (100%) diff --git a/system_tests/debug_block_test_common.go b/system_tests/debug_block_common_test.go similarity index 100% rename from system_tests/debug_block_test_common.go rename to system_tests/debug_block_common_test.go From e7378809fac958ecaa1cb346b663cafec0bf008b Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 6 Oct 2025 14:26:12 +0100 Subject: [PATCH 17/25] fix challenge test build --- system_tests/bold_challenge_protocol_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_tests/bold_challenge_protocol_test.go b/system_tests/bold_challenge_protocol_test.go index d528eadcf3..418b81fd73 100644 --- a/system_tests/bold_challenge_protocol_test.go +++ b/system_tests/bold_challenge_protocol_test.go @@ -604,7 +604,7 @@ func createTestNodeOnL1ForBoldProtocol( execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) Require(t, execConfig.Validate()) initMessage := getInitMessage(ctx, t, l1client, addresses) - _, l2stack, l2chainDb, l2arbDb, l2blockchain = createNonL1BlockChainWithStackConfig(t, l2info, "", chainConfig, nil, initMessage, nil, execConfig) + _, l2stack, l2chainDb, l2arbDb, l2blockchain = createNonL1BlockChainWithStackConfig(t, l2info, "", chainConfig, nil, initMessage, nil, execConfig, false) var sequencerTxOptsPtr *bind.TransactOpts var dataSigner signature.DataSignerFunc if isSequencer { From 967954dd1a8c500aec09bf47f059e997ffa53cb0 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Mon, 6 Oct 2025 15:35:40 +0100 Subject: [PATCH 18/25] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index d4af679316..46ae33023b 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit d4af679316f9795ba9261822050865a8c1491e38 +Subproject commit 46ae33023b4eec6d83d23b5564b66ab27b560196 From 3fcbad71913620305a8082d7b1103cde63d95955 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Oct 2025 13:52:45 +0100 Subject: [PATCH 19/25] add experimental tooling tests to ci workflow --- .github/workflows/_go-tests.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/_go-tests.yml b/.github/workflows/_go-tests.yml index f10b5802d3..e4559b0497 100644 --- a/.github/workflows/_go-tests.yml +++ b/.github/workflows/_go-tests.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - test-mode: [defaults, flaky, pathdb, challenge, stylus, l3challenge] + test-mode: [defaults, flaky, pathdb, challenge, stylus, l3challenge, experimental] services: redis: image: redis @@ -137,7 +137,7 @@ jobs: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags challengetest --run TestL3Challenge --timeout 120m --cover - # --------------------- CHALLENGE MODES --------------------- + # --------------------- STYLUS MODE -------------------------- - name: run stylus tests if: matrix.test-mode == 'stylus' @@ -145,6 +145,13 @@ jobs: ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags stylustest --run TestProgramArbitrator --timeout 60m --cover + # --------------------- EXPERIMENTAL MODE -------------------- + - name: run experimental tooling tests + if: matrix.test-mode == 'experimental' + run: >- + ${{ github.workspace }}/.github/workflows/gotestsum.sh + --tags debugblock --timeout 60m --cover + # --------------------- ARCHIVE LOGS FOR ALL MODES --------------------- - name: Archive detailed run log From 6d324156af783d4d33c050ab0398ad218c909bdf Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Oct 2025 14:46:25 +0100 Subject: [PATCH 20/25] execute only TestExperimental tests in experimental ci step --- .github/workflows/_go-tests.yml | 2 +- system_tests/debug_block_stub_test.go | 4 ++-- system_tests/debug_block_test.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/_go-tests.yml b/.github/workflows/_go-tests.yml index e4559b0497..e1bc041693 100644 --- a/.github/workflows/_go-tests.yml +++ b/.github/workflows/_go-tests.yml @@ -150,7 +150,7 @@ jobs: if: matrix.test-mode == 'experimental' run: >- ${{ github.workspace }}/.github/workflows/gotestsum.sh - --tags debugblock --timeout 60m --cover + --tags debugblock --run TestExperimental --timeout 60m --cover # --------------------- ARCHIVE LOGS FOR ALL MODES --------------------- diff --git a/system_tests/debug_block_stub_test.go b/system_tests/debug_block_stub_test.go index 8dc338805b..08331401ca 100644 --- a/system_tests/debug_block_stub_test.go +++ b/system_tests/debug_block_stub_test.go @@ -6,6 +6,6 @@ import ( "testing" ) -func TestDebugBlockInjection(t *testing.T) { - t.Run("production", func(t *testing.T) { testDebugBlockInjection(t, true) }) +func TestDebugBlockInjectionStub(t *testing.T) { + testDebugBlockInjection(t, true) } diff --git a/system_tests/debug_block_test.go b/system_tests/debug_block_test.go index 862b5f0a3c..b2a72e8bf5 100644 --- a/system_tests/debug_block_test.go +++ b/system_tests/debug_block_test.go @@ -6,6 +6,6 @@ import ( "testing" ) -func TestDebugBlockInjection(t *testing.T) { - t.Run("debugblock", func(t *testing.T) { testDebugBlockInjection(t, false) }) +func TestExperimentalDebugBlockInjection(t *testing.T) { + testDebugBlockInjection(t, false) } From 6a4986c43cddedad741203fc64aa85637766c647 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Oct 2025 14:47:00 +0100 Subject: [PATCH 21/25] add nitro-node-experimental to Dockerfile --- Dockerfile | 6 ++++++ Makefile | 10 +++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index f2b15f7dd5..c6addf96fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -383,5 +383,11 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ USER user +FROM nitro-node AS nitro-node-experimental +USER root +COPY --from=node-builder /workspace/target/bin/nitro-experimental /usr/local/bin/ +ENTRYPOINT [ "/usr/local/bin/nitro-experimental" , "--validation.wasm.allowed-wasm-module-roots", "/home/user/nitro-legacy/machines,/home/user/target/machines"] +USER user + FROM nitro-node AS nitro-node-default # Just to ensure nitro-node-dist is default diff --git a/Makefile b/Makefile index 9eab2cc394..5c70fed6e1 100644 --- a/Makefile +++ b/Makefile @@ -165,7 +165,7 @@ all: build build-replay-env test-gen-proofs @touch .make/all .PHONY: build -build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daprovider daserver autonomous-auctioneer bidder-client datool el-proxy mockexternalsigner seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv genesis-generator) +build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daprovider daserver autonomous-auctioneer bidder-client datool el-proxy mockexternalsigner seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv genesis-generator nitro-experimental) @printf $(done) .PHONY: build-node-deps @@ -272,10 +272,6 @@ tests-all: tests test-go-challenge test-go-stylus test-gen-proofs wasm-ci-build: $(arbitrator_wasm_libs) $(arbitrator_test_wasms) $(stylus_test_wasms) $(output_latest)/user_test.wasm @printf $(done) -.PHONY: build-nitro-debugblock -build-nitro-debugblock: $(output_root)/bin/nitro-debugblock - @printf $(done) - .PHONY: clean clean: go clean -testcache @@ -352,8 +348,8 @@ $(output_root)/bin/seq-coordinator-manager: $(DEP_PREDICATE) build-node-deps $(output_root)/bin/dbconv: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/dbconv" -# nitro built with debug block injection support -$(output_root)/bin/nitro-debugblock: $(DEP_PREDICATE) build-node-deps +# nitro built with experimental tooling enabled +$(output_root)/bin/nitro-experimental: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) --tags debugblock -o $@ "$(CURDIR)/cmd/nitro" # recompile wasm, but don't change timestamp unless files differ From a989d74b91a39511445d1a10280896f996f25115 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Tue, 7 Oct 2025 16:00:21 +0100 Subject: [PATCH 22/25] copy debugblock package into wasm-bin-builder --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index c6addf96fd..992fe9ca50 100644 --- a/Dockerfile +++ b/Dockerfile @@ -100,6 +100,7 @@ COPY ./contracts/package.json ./contracts/yarn.lock ./contracts/ COPY ./safe-smart-account ./safe-smart-account COPY ./solgen/gen.go ./solgen/ COPY ./go-ethereum ./go-ethereum +COPY ./experimental/debugblock ./experimental/debugblock COPY scripts/remove_reference_types.sh scripts/ COPY --from=brotli-wasm-export / target/ COPY --from=contracts-builder workspace/contracts-local/out/precompiles/ contracts-local/out/precompiles/ From 009dbb1bb3ecd4192be7f39a9d99ffd55405e300 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 9 Oct 2025 01:11:31 +0100 Subject: [PATCH 23/25] use DebugAddress as pointer --- experimental/debugblock/debug_block.go | 15 +++++++++------ go-ethereum | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/experimental/debugblock/debug_block.go b/experimental/debugblock/debug_block.go index 259b61692b..f750c69114 100644 --- a/experimental/debugblock/debug_block.go +++ b/experimental/debugblock/debug_block.go @@ -44,8 +44,9 @@ func (c *Config) Validate() error { func (c *Config) Apply(chainConfig *params.ChainConfig) { if c.OverwriteChainConfig { chainConfig.ArbitrumChainParams.AllowDebugPrecompiles = true - chainConfig.ArbitrumChainParams.DebugAddress = common.HexToAddress(c.DebugAddress) chainConfig.ArbitrumChainParams.DebugBlock = c.DebugBlockNum + debugAddress := common.HexToAddress(c.DebugAddress) + chainConfig.ArbitrumChainParams.DebugAddress = &debugAddress } } @@ -88,7 +89,6 @@ func PrepareDebugTransaction(chainConfig *params.ChainConfig, lastHeader *types. log.Error("debug block: failed to sign trigger tx", "address", address, "err", err) return nil } - log.Warn("debug block: PrepareDebugTransaction", "address", address) return tx } @@ -105,9 +105,12 @@ func DebugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int expectedBalanceDelta.Add(expectedBalanceDelta, triggerCost.ToBig()) // fund debug account - balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) - statedb.SetBalance(chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) - expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) + if chainConfig.ArbitrumChainParams.DebugAddress != nil { + balance := uint256.MustFromBig(new(big.Int).Lsh(big.NewInt(1), 254)) + statedb.SetBalance(*chainConfig.ArbitrumChainParams.DebugAddress, balance, tracing.BalanceChangeUnspecified) + expectedBalanceDelta.Add(expectedBalanceDelta, balance.ToBig()) + log.Warn("DANGER! debug block: funding debug account", "debugAddress", chainConfig.ArbitrumChainParams.DebugAddress) + } // save current chain config to arbos state in case it was changed to enable debug mode and debug block // replay binary reads chain config from arbos state, that will enable successful validation of future blocks @@ -119,5 +122,5 @@ func DebugBlockStateUpdate(statedb *state.StateDB, expectedBalanceDelta *big.Int } else if err = arbStateWrite.SetChainConfig(serializedChainConfig); err != nil { log.Error("debug block: failed to set chain config in arbos state", "err", err) } - log.Warn("DANGER! Producing debug block and funding debug account", "debugAddress", chainConfig.ArbitrumChainParams.DebugAddress) + log.Warn("DANGER! debug block: state update applied") } diff --git a/go-ethereum b/go-ethereum index 46ae33023b..3b8509053a 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 46ae33023b4eec6d83d23b5564b66ab27b560196 +Subproject commit 3b8509053a76db5338b45b477a535447dcfc0da9 From 1d954f8255faf2a36dcfcd71d2e7d60cd346e5b3 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik Date: Thu, 9 Oct 2025 01:21:04 +0100 Subject: [PATCH 24/25] update geth pin --- go-ethereum | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go-ethereum b/go-ethereum index 3b8509053a..bb1d23856b 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit 3b8509053a76db5338b45b477a535447dcfc0da9 +Subproject commit bb1d23856bb56a12db8316187ff0bd05da1d9754 From 373946df31aceb12835c12b2effa2efad8f7c9f9 Mon Sep 17 00:00:00 2001 From: Maciej Kulawik <10907694+magicxyyz@users.noreply.github.com> Date: Fri, 10 Oct 2025 14:30:07 +0100 Subject: [PATCH 25/25] add missing newline Co-authored-by: Kolby Moroz Liebl <31669092+KolbyML@users.noreply.github.com> --- execution/gethexec/node.go | 1 + 1 file changed, 1 insertion(+) diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index b8f972a503..f8d67640f4 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -210,6 +210,7 @@ var DefaultDangerousConfig = DangerousConfig{ func DangerousConfigAddOptions(prefix string, f *pflag.FlagSet) { debugblock.ConfigAddOptions(prefix+".debug-block", f) } + func (c *DangerousConfig) Validate() error { return c.DebugBlock.Validate() }