From 85f19f6a2e3349058842939a51784cf07efabd3c Mon Sep 17 00:00:00 2001 From: stavrosvl7 Date: Wed, 7 Jan 2026 19:29:03 +0200 Subject: [PATCH 1/5] contract tx text and single node approach --- system_tests/common_test.go | 104 +++++++++++++++++++++++++++++-- system_tests/contract_tx_test.go | 71 +++++++++++++++++++-- 2 files changed, 165 insertions(+), 10 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index aaf80fa383..830d26020c 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -333,6 +333,7 @@ type NodeBuilder struct { withProdConfirmPeriodBlocks bool delayBufferThreshold uint64 withL1ClientWrapper bool + executionClientMode ExecutionClientMode // ADD THIS // Created nodes L1 *TestClient @@ -505,6 +506,11 @@ func (b *NodeBuilder) WithTakeOwnership(takeOwnership bool) *NodeBuilder { return b } +func (b *NodeBuilder) WithExecutionClientMode(mode ExecutionClientMode) *NodeBuilder { + b.executionClientMode = mode + return b +} + func (b *NodeBuilder) Build(t *testing.T) func() { if b.parallelise { b.parallelise = false @@ -888,11 +894,81 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, nil, b.l2StackConfig, b.execConfig) execConfigFetcher := NewCommonConfigFetcher(b.execConfig) - execNode, err := gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) - Require(t, err) - b.L2.ExecutionConfigFetcher = execConfigFetcher + // Create execution client based on mode + var execNode nethexec.FullExecutionClient fatalErrChan := make(chan error, 10) + + switch b.executionClientMode { + case ExecutionClientModeInternal, 0: // 0 for default/unset + // Original behavior - internal geth + execNode, err = gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) + Require(t, err) + + case ExecutionClientModeExternal: + // External Nethermind + nethermindUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + if nethermindUrl == "" { + nethermindUrl = "http://localhost:20545" + } + nethermindWsUrl := os.Getenv("PR_NETH_WS_URL") + if nethermindWsUrl == "" { + nethermindWsUrl = "ws://localhost:28551" + } + nethermindExecClient, err := nethexec.NewNethermindExecutionClient(nethermindUrl, nethermindWsUrl) + Require(t, err) + + // Initialize Nethermind with genesis - CREATE INIT MESSAGE + serializedChainConfig, err := json.Marshal(b.chainConfig) + Require(t, err) + initMsg := &arbostypes.ParsedInitMessage{ + ChainId: b.chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: b.chainConfig, + SerializedChainConfig: serializedChainConfig, + } + result := nethermindExecClient.DigestInitMessage(b.ctx, initMsg.InitialL1BaseFee, initMsg.SerializedChainConfig) + if result == nil { + Fatal(t, "DigestInitMessage returned nil for external execution client") + } + + execNode = nethermindExecClient + + case ExecutionClientModeComparison: + // Both - create comparison client + gethExec, err := gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) + Require(t, err) + + nethermindUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + if nethermindUrl == "" { + nethermindUrl = "http://localhost:20545" + } + nethermindWsUrl := os.Getenv("PR_NETH_WS_URL") + if nethermindWsUrl == "" { + nethermindWsUrl = "ws://localhost:28551" + } + nethExec, err := nethexec.NewNethermindExecutionClient(nethermindUrl, nethermindWsUrl) + Require(t, err) + + // Initialize Nethermind with genesis - CREATE INIT MESSAGE + serializedChainConfig, err := json.Marshal(b.chainConfig) + Require(t, err) + initMsg := &arbostypes.ParsedInitMessage{ + ChainId: b.chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: b.chainConfig, + SerializedChainConfig: serializedChainConfig, + } + result := nethExec.DigestInitMessage(b.ctx, initMsg.InitialL1BaseFee, initMsg.SerializedChainConfig) + if result == nil { + Fatal(t, "DigestInitMessage returned nil for external execution client") + } + + execNode = nethexec.NewCompareExecutionClient(gethExec, nethExec, fatalErrChan) + } + + b.L2.ExecutionConfigFetcher = execConfigFetcher + locator, err := server_common.NewMachineLocator(b.valnodeConfig.Wasm.RootPath) Require(t, err) consensusConfigFetcher := NewCommonConfigFetcher(b.nodeConfig) @@ -909,7 +985,21 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { err = b.L2.ConsensusNode.Start(b.ctx) Require(t, err) - b.L2.Client = ClientForStack(t, b.L2.Stack) + // Set client based on execution mode + switch b.executionClientMode { + case ExecutionClientModeExternal: + // For external execution client, connect directly to Nethermind for RPC calls + nethRpcUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + if nethRpcUrl == "" { + nethRpcUrl = "http://localhost:20545" + } + externalRpcClient, err := rpc.Dial(nethRpcUrl) + Require(t, err) + b.L2.Client = ethclient.NewClient(externalRpcClient) + default: + // For internal and comparison modes, use internal geth client + b.L2.Client = ClientForStack(t, b.L2.Stack) + } if b.takeOwnership { debugAuth := b.L2Info.GetDefaultTransactOpts("Owner", b.ctx) @@ -927,7 +1017,11 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { StartWatchChanErr(t, b.ctx, fatalErrChan, b.L2.ConsensusNode) - b.L2.ExecNode = getExecNode(t, b.L2.ConsensusNode) + // Only set ExecNode if it's internal geth + if b.executionClientMode == ExecutionClientModeInternal || b.executionClientMode == 0 { + b.L2.ExecNode = getExecNode(t, b.L2.ConsensusNode) + } + b.L2.cleanup = func() { b.L2.ConsensusNode.StopAndWait() } return func() { b.L2.cleanup() diff --git a/system_tests/contract_tx_test.go b/system_tests/contract_tx_test.go index 4accfcff24..549d533e34 100644 --- a/system_tests/contract_tx_test.go +++ b/system_tests/contract_tx_test.go @@ -1,6 +1,5 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md - package arbtest import ( @@ -18,29 +17,74 @@ import ( "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/statetransfer" "github.com/offchainlabs/nitro/util/arbmath" ) -func TestContractTxDeploy(t *testing.T) { +func testContractTxDeploy(t *testing.T, executionClientMode ExecutionClientMode) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + + // Create builder but don't build yet builder := NewNodeBuilder(ctx).DefaultConfig(t, false) builder.takeOwnership = false + + // Fund the test account at GENESIS - before building + from := common.HexToAddress("0x123412341234") + + // Add to genesis allocation in L2Info + // This ensures BOTH geth and Nethermind have the account from block 0 + if builder.L2Info.ArbInitData.Accounts == nil { + builder.L2Info.ArbInitData.Accounts = []statetransfer.AccountInitializationInfo{} + } + + builder.L2Info.ArbInitData.Accounts = append(builder.L2Info.ArbInitData.Accounts, + statetransfer.AccountInitializationInfo{ + Addr: from, + EthBalance: big.NewInt(1e18), + Nonce: 0, + ContractInfo: nil, + }) + + // Also add to the L2Info.Accounts map for test helpers to use + // Use SetFullAccountInfo which handles the atomic.Uint64 correctly + builder.L2Info.SetFullAccountInfo("TestAccount", &AccountInfo{ + Address: from, + PrivateKey: nil, // No private key needed for ArbitrumContractTx + }) + + // NOW set execution mode and build + builder = builder.WithExecutionClientMode(executionClientMode) + cleanup := builder.Build(t) defer cleanup() - from := common.HexToAddress("0x123412341234") - builder.L2.TransferBalanceTo(t, "Faucet", from, big.NewInt(1e18), builder.L2Info) + // Wait for initialization to complete + if executionClientMode == ExecutionClientModeComparison || executionClientMode == ExecutionClientModeExternal { + time.Sleep(time.Second * 2) + } + + // Verify account was funded at genesis in both clients + balance, err := builder.L2.Client.BalanceAt(ctx, from, nil) + Require(t, err) + if balance.Cmp(big.NewInt(1e18)) != 0 { + Fatal(t, "Test account not funded at genesis, got balance:", balance) + } + t.Log("Verified account funded at genesis with balance:", balance) + + // NO TransferBalance call - account is already funded! for stateNonce := uint64(0); stateNonce < 2; stateNonce++ { msgCount, err := builder.L2.ConsensusNode.TxStreamer.GetMessageCount() Require(t, err) + var delayedMessagesRead uint64 if msgCount > 0 { lastMessage, err := builder.L2.ConsensusNode.TxStreamer.GetMessage(msgCount - 1) Require(t, err) delayedMessagesRead = lastMessage.DelayedMessagesRead } + // Deploys a single 0xFE (INVALID) byte as a smart contract deployCode := []byte{ 0x60, 0xFE, // PUSH1 0xFE @@ -50,9 +94,11 @@ func TestContractTxDeploy(t *testing.T) { 0x60, 0x00, // PUSH1 0 0xF3, // RETURN } + var requestId common.Hash // #nosec G115 requestId[0] = uint8(stateNonce) + contractTx := &types.ArbitrumContractTx{ ChainId: chaininfo.ArbitrumDevTestChainConfig().ChainID, RequestId: requestId, @@ -63,6 +109,7 @@ func TestContractTxDeploy(t *testing.T) { Value: big.NewInt(0), Data: deployCode, } + l2Msg := []byte{arbos.L2MessageKind_ContractTx} l2Msg = append(l2Msg, arbmath.Uint64ToU256Bytes(contractTx.Gas)...) l2Msg = append(l2Msg, arbmath.U256Bytes(contractTx.GasFeeCap)...) @@ -92,6 +139,7 @@ func TestContractTxDeploy(t *testing.T) { txHash := types.NewTx(contractTx).Hash() t.Log("made contract tx", contractTx, "with hash", txHash) + receipt, err := WaitForTx(ctx, builder.L2.Client, txHash, time.Second*10) Require(t, err) if receipt.Status != types.ReceiptStatusSuccessful { @@ -103,12 +151,25 @@ func TestContractTxDeploy(t *testing.T) { Fatal(t, "expected address", from, "nonce", stateNonce, "to deploy to", expectedAddr, "but got", receipt.ContractAddress) } t.Log("deployed contract", receipt.ContractAddress, "from address", from, "with nonce", stateNonce) - stateNonce++ code, err := builder.L2.Client.CodeAt(ctx, receipt.ContractAddress, nil) Require(t, err) if !bytes.Equal(code, []byte{0xFE}) { Fatal(t, "expected contract", receipt.ContractAddress, "code of 0xFE but got", hex.EncodeToString(code)) } + + stateNonce++ } } + +func TestContractTxDeployInternal(t *testing.T) { + testContractTxDeploy(t, ExecutionClientModeInternal) +} + +func TestContractTxDeployExternal(t *testing.T) { + testContractTxDeploy(t, ExecutionClientModeExternal) +} + +func TestContractTxDeployComparison(t *testing.T) { + testContractTxDeploy(t, ExecutionClientModeComparison) +} From d0c806eba7dea567f6db72b333b9a0c6646e52e5 Mon Sep 17 00:00:00 2001 From: stavrosvl7 Date: Wed, 7 Jan 2026 23:28:03 +0200 Subject: [PATCH 2/5] minor fix --- system_tests/common_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 830d26020c..ee509d84e4 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -333,7 +333,7 @@ type NodeBuilder struct { withProdConfirmPeriodBlocks bool delayBufferThreshold uint64 withL1ClientWrapper bool - executionClientMode ExecutionClientMode // ADD THIS + executionClientMode ExecutionClientMode // Created nodes L1 *TestClient From 4bf43a820e1039dbf0e0b34247096cb7906637cf Mon Sep 17 00:00:00 2001 From: stavrosvl7 Date: Fri, 9 Jan 2026 16:06:59 +0200 Subject: [PATCH 3/5] improvements --- system_tests/common_test.go | 69 +++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index ee509d84e4..86464fe57a 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -460,6 +460,11 @@ func (b *NodeBuilder) WithExtraArchs(targets []string) *NodeBuilder { return b } +func (b *NodeBuilder) WithExecutionClientMode(mode ExecutionClientMode) *NodeBuilder { + b.executionClientMode = mode + return b +} + // WithDelayBuffer sets the delay-buffer threshold, which is the number of blocks the batch-poster // is allowed to delay a batch with a delayed message. // Setting the threshold to zero disabled the delay buffer (default behaviour). @@ -506,11 +511,6 @@ func (b *NodeBuilder) WithTakeOwnership(takeOwnership bool) *NodeBuilder { return b } -func (b *NodeBuilder) WithExecutionClientMode(mode ExecutionClientMode) *NodeBuilder { - b.executionClientMode = mode - return b -} - func (b *NodeBuilder) Build(t *testing.T) func() { if b.parallelise { b.parallelise = false @@ -967,8 +967,6 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { execNode = nethexec.NewCompareExecutionClient(gethExec, nethExec, fatalErrChan) } - b.L2.ExecutionConfigFetcher = execConfigFetcher - locator, err := server_common.NewMachineLocator(b.valnodeConfig.Wasm.RootPath) Require(t, err) consensusConfigFetcher := NewCommonConfigFetcher(b.nodeConfig) @@ -2459,3 +2457,60 @@ func populateMachineDir(t *testing.T, cr *github.ConsensusRelease) string { Require(t, err) return machineDir } + +// BuildReplicaWithExecutionMode builds a replica node with the specified execution client mode +// Returns the replica's test client and cleanup function +func BuildReplicaWithExecutionMode(t *testing.T, builder *NodeBuilder, executionClientMode ExecutionClientMode) (*TestClient, func()) { + replicaConfig := arbnode.ConfigDefaultL1NonSequencerTest() + replicaParams := &SecondNodeParams{ + nodeConfig: replicaConfig, + useExecutionClientOnly: true, + executionClientMode: executionClientMode, + } + replica, cleanup := builder.Build2ndNode(t, replicaParams) + + // Wait for replica to initialize + time.Sleep(time.Second * 2) + + return replica, cleanup +} + +// WaitForReplicaSync waits for replica to catch up to primary's block number +// Returns an error if replica fails to sync within the timeout +func WaitForReplicaSync(ctx context.Context, t *testing.T, primaryClient, replicaClient *ethclient.Client, maxAttempts int) { + primaryBlock, err := primaryClient.BlockNumber(ctx) + Require(t, err) + + for i := 0; i < maxAttempts; i++ { + replicaBlock, err := replicaClient.BlockNumber(ctx) + Require(t, err) + if replicaBlock >= primaryBlock { + return + } + time.Sleep(time.Millisecond * 100) + } + + // Final check and fail if not synced + replicaBlock, err := replicaClient.BlockNumber(ctx) + Require(t, err) + if replicaBlock < primaryBlock { + Fatal(t, "Replica at block", replicaBlock, "failed to catch up to primary at block", primaryBlock) + } +} + +// ReplicaTestFunc is a test function that takes an execution client mode parameter +type ReplicaTestFunc func(t *testing.T, executionClientMode ExecutionClientMode) + +// CreateReplicaTestVariants generates Internal, External, and Comparison test functions +// from a single test implementation +func CreateReplicaTestVariants(testFunc ReplicaTestFunc) (internal, external, comparison func(*testing.T)) { + return func(t *testing.T) { + testFunc(t, ExecutionClientModeInternal) + }, + func(t *testing.T) { + testFunc(t, ExecutionClientModeExternal) + }, + func(t *testing.T) { + testFunc(t, ExecutionClientModeComparison) + } +} From 20d89279a212ff94ed6f810d21082d5a6b8e7381 Mon Sep 17 00:00:00 2001 From: stavrosvl7 Date: Fri, 9 Jan 2026 16:40:11 +0200 Subject: [PATCH 4/5] fix flag --- system_tests/common_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 86464fe57a..325705da37 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -907,11 +907,11 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { case ExecutionClientModeExternal: // External Nethermind - nethermindUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + nethermindUrl := b.execConfig.NethermindUrl if nethermindUrl == "" { nethermindUrl = "http://localhost:20545" } - nethermindWsUrl := os.Getenv("PR_NETH_WS_URL") + nethermindWsUrl := b.execConfig.NethermindWsUrl if nethermindWsUrl == "" { nethermindWsUrl = "ws://localhost:28551" } @@ -939,11 +939,11 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { gethExec, err := gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) Require(t, err) - nethermindUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + nethermindUrl := b.execConfig.NethermindUrl if nethermindUrl == "" { nethermindUrl = "http://localhost:20545" } - nethermindWsUrl := os.Getenv("PR_NETH_WS_URL") + nethermindWsUrl := b.execConfig.NethermindWsUrl if nethermindWsUrl == "" { nethermindWsUrl = "ws://localhost:28551" } @@ -987,7 +987,7 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { switch b.executionClientMode { case ExecutionClientModeExternal: // For external execution client, connect directly to Nethermind for RPC calls - nethRpcUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + nethRpcUrl := b.execConfig.NethermindUrl if nethRpcUrl == "" { nethRpcUrl = "http://localhost:20545" } @@ -2012,11 +2012,11 @@ func Create2ndNodeWithConfig( case ExecutionClientModeExternal: // Create external Nethermind execution client - nethermindUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + nethermindUrl := execConfig.NethermindUrl if nethermindUrl == "" { nethermindUrl = "http://localhost:20545" } - nethermindWsUrl := os.Getenv("PR_NETH_WS_URL") + nethermindWsUrl := execConfig.NethermindWsUrl if nethermindWsUrl == "" { nethermindWsUrl = "ws://localhost:28551" } @@ -2037,11 +2037,11 @@ func Create2ndNodeWithConfig( gethExecClient, err := gethexec.CreateExecutionNode(ctx, chainStack, chainDb, blockchain, parentChainClient, execConfigFetcher, big.NewInt(1337), 0) Require(t, err) - nethermindUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + nethermindUrl := execConfig.NethermindUrl if nethermindUrl == "" { nethermindUrl = "http://localhost:20545" } - nethermindWsUrl := os.Getenv("PR_NETH_WS_URL") + nethermindWsUrl := execConfig.NethermindWsUrl if nethermindWsUrl == "" { nethermindWsUrl = "ws://localhost:28551" } @@ -2079,7 +2079,7 @@ func Create2ndNodeWithConfig( switch executionClientMode { case ExecutionClientModeExternal: // For external execution client, connect directly to Nethermind for RPC calls - nethRpcUrl := os.Getenv("PR_NETH_RPC_CLIENT_URL") + nethRpcUrl := execConfig.NethermindUrl if nethRpcUrl == "" { nethRpcUrl = "http://localhost:20545" } From 6cde01449d28711716caaf0d5c2bef9544a481a4 Mon Sep 17 00:00:00 2001 From: stavrosvl7 Date: Mon, 12 Jan 2026 12:43:18 +0200 Subject: [PATCH 5/5] refactoring --- system_tests/common_test.go | 132 +++++++++++++++--------------------- 1 file changed, 55 insertions(+), 77 deletions(-) diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 325705da37..3af46fe360 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -26,6 +26,7 @@ import ( "testing" "time" + "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/execution/nethexec" "github.com/offchainlabs/nitro/util/colors" "github.com/redis/go-redis/v9" @@ -899,6 +900,19 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { var execNode nethexec.FullExecutionClient fatalErrChan := make(chan error, 10) + var initMessage *arbostypes.ParsedInitMessage + if b.executionClientMode == ExecutionClientModeExternal || + b.executionClientMode == ExecutionClientModeComparison { + serializedChainConfig, err := json.Marshal(b.chainConfig) + Require(t, err) + initMessage = &arbostypes.ParsedInitMessage{ + ChainId: b.chainConfig.ChainID, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, + ChainConfig: b.chainConfig, + SerializedChainConfig: serializedChainConfig, + } + } + switch b.executionClientMode { case ExecutionClientModeInternal, 0: // 0 for default/unset // Original behavior - internal geth @@ -907,31 +921,10 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { case ExecutionClientModeExternal: // External Nethermind - nethermindUrl := b.execConfig.NethermindUrl - if nethermindUrl == "" { - nethermindUrl = "http://localhost:20545" - } - nethermindWsUrl := b.execConfig.NethermindWsUrl - if nethermindWsUrl == "" { - nethermindWsUrl = "ws://localhost:28551" - } + nethermindUrl, nethermindWsUrl := getNethermindURLsFromConfig(b.execConfig) nethermindExecClient, err := nethexec.NewNethermindExecutionClient(nethermindUrl, nethermindWsUrl) Require(t, err) - - // Initialize Nethermind with genesis - CREATE INIT MESSAGE - serializedChainConfig, err := json.Marshal(b.chainConfig) - Require(t, err) - initMsg := &arbostypes.ParsedInitMessage{ - ChainId: b.chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: b.chainConfig, - SerializedChainConfig: serializedChainConfig, - } - result := nethermindExecClient.DigestInitMessage(b.ctx, initMsg.InitialL1BaseFee, initMsg.SerializedChainConfig) - if result == nil { - Fatal(t, "DigestInitMessage returned nil for external execution client") - } - + Require(t, initializeExternalClient(b.ctx, nethermindExecClient, initMessage)) execNode = nethermindExecClient case ExecutionClientModeComparison: @@ -939,30 +932,10 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { gethExec, err := gethexec.CreateExecutionNode(b.ctx, b.L2.Stack, chainDb, blockchain, nil, execConfigFetcher, big.NewInt(1337), 0) Require(t, err) - nethermindUrl := b.execConfig.NethermindUrl - if nethermindUrl == "" { - nethermindUrl = "http://localhost:20545" - } - nethermindWsUrl := b.execConfig.NethermindWsUrl - if nethermindWsUrl == "" { - nethermindWsUrl = "ws://localhost:28551" - } + nethermindUrl, nethermindWsUrl := getNethermindURLsFromConfig(b.execConfig) nethExec, err := nethexec.NewNethermindExecutionClient(nethermindUrl, nethermindWsUrl) Require(t, err) - - // Initialize Nethermind with genesis - CREATE INIT MESSAGE - serializedChainConfig, err := json.Marshal(b.chainConfig) - Require(t, err) - initMsg := &arbostypes.ParsedInitMessage{ - ChainId: b.chainConfig.ChainID, - InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee, - ChainConfig: b.chainConfig, - SerializedChainConfig: serializedChainConfig, - } - result := nethExec.DigestInitMessage(b.ctx, initMsg.InitialL1BaseFee, initMsg.SerializedChainConfig) - if result == nil { - Fatal(t, "DigestInitMessage returned nil for external execution client") - } + Require(t, initializeExternalClient(b.ctx, nethExec, initMessage)) execNode = nethexec.NewCompareExecutionClient(gethExec, nethExec, fatalErrChan) } @@ -2011,54 +1984,30 @@ func Create2ndNodeWithConfig( Require(t, err) case ExecutionClientModeExternal: - // Create external Nethermind execution client - nethermindUrl := execConfig.NethermindUrl - if nethermindUrl == "" { - nethermindUrl = "http://localhost:20545" - } - nethermindWsUrl := execConfig.NethermindWsUrl - if nethermindWsUrl == "" { - nethermindWsUrl = "ws://localhost:28551" - } + nethermindUrl, nethermindWsUrl := getNethermindURLsFromConfig(execConfig) nethermindExecClient, err := nethexec.NewNethermindExecutionClient(nethermindUrl, nethermindWsUrl) Require(t, err) - // Call DigestInitMessage to initialize the external execution client with genesis block - if initMessage != nil { - result := nethermindExecClient.DigestInitMessage(ctx, initMessage.InitialL1BaseFee, initMessage.SerializedChainConfig) - if result == nil { - t.Fatal("DigestInitMessage returned nil for external execution client") - } + err = initializeExternalClient(ctx, nethermindExecClient, initMessage) + if err != nil { + t.Fatal(err) } currentExec = nethermindExecClient case ExecutionClientModeComparison: - // Create both internal geth and external Nethermind execution clients gethExecClient, err := gethexec.CreateExecutionNode(ctx, chainStack, chainDb, blockchain, parentChainClient, execConfigFetcher, big.NewInt(1337), 0) Require(t, err) - nethermindUrl := execConfig.NethermindUrl - if nethermindUrl == "" { - nethermindUrl = "http://localhost:20545" - } - nethermindWsUrl := execConfig.NethermindWsUrl - if nethermindWsUrl == "" { - nethermindWsUrl = "ws://localhost:28551" - } + nethermindUrl, nethermindWsUrl := getNethermindURLsFromConfig(execConfig) nethermindExecClient, err := nethexec.NewNethermindExecutionClient(nethermindUrl, nethermindWsUrl) Require(t, err) - // Call DigestInitMessage to initialize the external execution client with genesis block - if initMessage != nil { - result := nethermindExecClient.DigestInitMessage(ctx, initMessage.InitialL1BaseFee, initMessage.SerializedChainConfig) - if result == nil { - t.Fatal("DigestInitMessage returned nil for external execution client") - } + err = initializeExternalClient(ctx, nethermindExecClient, initMessage) + if err != nil { + t.Fatal(err) } - // Wrap both in comparison client currentExec = nethexec.NewCompareExecutionClient(gethExecClient, nethermindExecClient, feedErrChan) - default: t.Fatalf("unsupported execution client mode: %v", executionClientMode) } @@ -2514,3 +2463,32 @@ func CreateReplicaTestVariants(testFunc ReplicaTestFunc) (internal, external, co testFunc(t, ExecutionClientModeComparison) } } + +func getNethermindURLsFromConfig(execConfig *gethexec.Config) (rpcUrl string, wsUrl string) { + rpcUrl = execConfig.NethermindUrl + if rpcUrl == "" { + rpcUrl = "http://localhost:20545" + } + wsUrl = execConfig.NethermindWsUrl + if wsUrl == "" { + wsUrl = "ws://localhost:28551" + } + return +} + +func initializeExternalClient( + ctx context.Context, + client interface { + DigestInitMessage(context.Context, *big.Int, []byte) *execution.MessageResult + }, + initMessage *arbostypes.ParsedInitMessage, +) error { + if initMessage == nil { + return nil + } + result := client.DigestInitMessage(ctx, initMessage.InitialL1BaseFee, initMessage.SerializedChainConfig) + if result == nil { + return fmt.Errorf("DigestInitMessage returned nil") + } + return nil +}