Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions execution/gethexec/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
Expand Down Expand Up @@ -263,6 +264,7 @@ type ExecutionNode struct {
ClassicOutbox *ClassicOutboxRetriever
started atomic.Bool
bulkBlockMetadataFetcher *BulkBlockMetadataFetcher
rpcClient *rpc.Client // for debug tracing operations
}

func CreateExecutionNode(
Expand Down Expand Up @@ -448,6 +450,9 @@ func (n *ExecutionNode) Initialize(ctx context.Context) error {
return fmt.Errorf("error setting sync backend: %w", err)
}

// Initialize RPC client for debug tracing
n.rpcClient = n.Backend.Stack().Attach()

return nil
}

Expand Down Expand Up @@ -494,6 +499,9 @@ func (n *ExecutionNode) StopAndWait() {
if err := n.Backend.Stop(); err != nil {
log.Error("backend stop", "err", err)
}
if n.rpcClient != nil {
n.rpcClient.Close()
}
// TODO after separation
// if err := n.Stack.Close(); err != nil {
// log.Error("error on stak close", "err", err)
Expand Down Expand Up @@ -614,6 +622,26 @@ func (n *ExecutionNode) SetConsensusSyncData(syncData *execution.ConsensusSyncDa
return containers.NewReadyPromise(struct{}{}, nil)
}

// DebugTraceTransaction calls debug_traceTransaction on the Geth node's RPC API
func (n *ExecutionNode) DebugTraceTransaction(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.ExecutionResult, error) {
if n.rpcClient == nil {
return native.ExecutionResult{}, errors.New("RPC client not initialized")
}
var result native.ExecutionResult
err := n.rpcClient.CallContext(ctx, &result, "debug_traceTransaction", txHash, tracerConfig)
return result, err
}

// DebugTraceTransactionByOpcode calls debug_traceTransaction with txGasDimensionByOpcode tracer on the Geth node's RPC API
func (n *ExecutionNode) DebugTraceTransactionByOpcode(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.TxGasDimensionByOpcodeExecutionResult, error) {
if n.rpcClient == nil {
return native.TxGasDimensionByOpcodeExecutionResult{}, errors.New("RPC client not initialized")
}
var result native.TxGasDimensionByOpcodeExecutionResult
err := n.rpcClient.CallContext(ctx, &result, "debug_traceTransaction", txHash, tracerConfig)
return result, err
}

func (n *ExecutionNode) InitializeTimeboost(ctx context.Context, chainConfig *params.ChainConfig) error {
execNodeConfig := n.configFetcher.Get()
if execNodeConfig.Sequencer.Timeboost.Enable {
Expand Down
17 changes: 17 additions & 0 deletions execution/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/eth/tracers/native"

"github.com/offchainlabs/nitro/arbos/arbostypes"
"github.com/offchainlabs/nitro/arbutil"
Expand Down Expand Up @@ -118,3 +119,19 @@ type FullConsensusClient interface {
ConsensusInfo
ConsensusSequencer
}

// ExecutionDebugger provides debug/tracing operations on execution client
type ExecutionDebugger interface {
// DebugTraceTransaction traces a transaction using txGasDimensionLogger tracer
DebugTraceTransaction(
ctx context.Context,
txHash common.Hash,
tracerConfig map[string]interface{},
) (native.ExecutionResult, error)
// DebugTraceTransactionByOpcode traces a transaction using txGasDimensionByOpcode tracer
DebugTraceTransactionByOpcode(
ctx context.Context,
txHash common.Hash,
tracerConfig map[string]interface{},
) (native.TxGasDimensionByOpcodeExecutionResult, error)
}
79 changes: 74 additions & 5 deletions execution/nethexec/compare_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package nethexec
import (
"context"
"fmt"
"math/big"
"time"

"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"

Expand Down Expand Up @@ -116,11 +119,21 @@ func compare[T any](op string, intRes T, intErr error, extRes T, extErr error) e
case intErr == nil && extErr != nil:
return fmt.Errorf("external operation failed: %w", extErr)
default:
if !cmp.Equal(intRes, extRes) {
opts := cmp.Options{
cmp.Transformer("HashHex", func(h common.Hash) string { return h.Hex() }),
}
diff := cmp.Diff(intRes, extRes, opts)
opts := cmp.Options{
cmp.Transformer("HashHex", func(h common.Hash) string { return h.Hex() }),
cmp.Comparer(func(x, y *big.Int) bool {
if x == nil && y == nil {
return true
}
if x == nil || y == nil {
return false
}
return x.Cmp(y) == 0
}),
cmpopts.EquateEmpty(),
}
if !cmp.Equal(intRes, extRes, opts...) {
diff := cmp.Diff(intRes, extRes, opts...)
// Log the detailed diff using fmt.Printf to avoid escaping
fmt.Printf("ERROR: Execution mismatch detected in operation: %s\n", op)
fmt.Printf("Diff details:\n%s\n", diff)
Expand Down Expand Up @@ -400,3 +413,59 @@ func (w *compareExecutionClient) SetConsensusClient(consensus execution.FullCons
func (w *compareExecutionClient) Initialize(ctx context.Context) error {
return w.gethExecutionClient.Initialize(ctx)
}

// DebugTraceTransaction calls debug_traceTransaction on both Geth and Nethermind and compares results.
func (w *compareExecutionClient) DebugTraceTransaction(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.ExecutionResult, error) {
start := time.Now()
tracer := tracerConfig["tracer"]
log.Info("CompareExecutionClient: DebugTraceTransaction", "txHash", txHash, "tracer", tracer)

gethResult, gethErr := w.gethExecutionClient.DebugTraceTransaction(ctx, txHash, tracerConfig)
nethResult, nethErr := w.nethermindExecutionClient.DebugTraceTransaction(ctx, txHash, tracerConfig)

log.Info("CompareExecutionClient: DebugTraceTransaction completed",
"txHash", txHash,
"tracer", tracer,
"gethErr", gethErr,
"nethErr", nethErr,
"elapsed", time.Since(start))

if err := compare("DebugTraceTransaction", gethResult, gethErr, nethResult, nethErr); err != nil {
select {
case w.fatalErrChan <- fmt.Errorf("compareExecutionClient DebugTraceTransaction: %s", err.Error()):
default:
log.Error("Non-fatal trace comparison error", "txHash", txHash, "err", err)
}
return gethResult, err
}

return gethResult, gethErr
}

// DebugTraceTransactionByOpcode calls debug_traceTransaction with txGasDimensionByOpcode tracer on both Geth and Nethermind and compares results.
func (w *compareExecutionClient) DebugTraceTransactionByOpcode(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.TxGasDimensionByOpcodeExecutionResult, error) {
start := time.Now()
tracer := tracerConfig["tracer"]
log.Info("CompareExecutionClient: DebugTraceTransactionByOpcode", "txHash", txHash, "tracer", tracer)

gethResult, gethErr := w.gethExecutionClient.DebugTraceTransactionByOpcode(ctx, txHash, tracerConfig)
nethResult, nethErr := w.nethermindExecutionClient.DebugTraceTransactionByOpcode(ctx, txHash, tracerConfig)

log.Info("CompareExecutionClient: DebugTraceTransactionByOpcode completed",
"txHash", txHash,
"tracer", tracer,
"gethErr", gethErr,
"nethErr", nethErr,
"elapsed", time.Since(start))

if err := compare("DebugTraceTransactionByOpcode", gethResult, gethErr, nethResult, nethErr); err != nil {
select {
case w.fatalErrChan <- fmt.Errorf("compareExecutionClient DebugTraceTransactionByOpcode: %s", err.Error()):
default:
log.Error("Non-fatal trace comparison error", "txHash", txHash, "err", err)
}
return gethResult, err
}

return gethResult, gethErr
}
11 changes: 11 additions & 0 deletions execution/nethexec/execution_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"

Expand Down Expand Up @@ -301,3 +302,13 @@ func (p *nethermindExecutionClient) BlockNumber(ctx context.Context) (uint64, er
func (p *nethermindExecutionClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
return p.rpcClient.BalanceAt(ctx, account, blockNumber)
}

// DebugTraceTransaction calls debug_traceTransaction on Nethermind
func (p *nethermindExecutionClient) DebugTraceTransaction(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.ExecutionResult, error) {
return p.rpcClient.DebugTraceTransaction(ctx, txHash, tracerConfig)
}

// DebugTraceTransactionByOpcode calls debug_traceTransaction with txGasDimensionByOpcode tracer on Nethermind
func (p *nethermindExecutionClient) DebugTraceTransactionByOpcode(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.TxGasDimensionByOpcodeExecutionResult, error) {
return p.rpcClient.DebugTraceTransactionByOpcode(ctx, txHash, tracerConfig)
}
24 changes: 24 additions & 0 deletions execution/nethexec/nethrpcclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"

Expand Down Expand Up @@ -371,3 +372,26 @@ func (c *nethRpcClient) FullSyncProgressMap(ctx context.Context) (map[string]int
}
return result, nil
}

// DebugTraceTransaction calls debug_traceTransaction on the Nethermind node
// tracerConfig should include "tracer" key specifying the tracer name
func (c *nethRpcClient) DebugTraceTransaction(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.ExecutionResult, error) {
log.Debug("Making JSON-RPC call to debug_traceTransaction", "url", c.url, "txHash", txHash, "tracer", tracerConfig["tracer"])
var result native.ExecutionResult
if err := c.client.CallContext(ctx, &result, "debug_traceTransaction", txHash, tracerConfig); err != nil {
log.Error("Failed to call debug_traceTransaction", "error", err, "txHash", txHash)
return native.ExecutionResult{}, fmt.Errorf("failed to call debug_traceTransaction: %w", err)
}
return result, nil
}

// DebugTraceTransactionByOpcode calls debug_traceTransaction with txGasDimensionByOpcode tracer on the Nethermind node
func (c *nethRpcClient) DebugTraceTransactionByOpcode(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.TxGasDimensionByOpcodeExecutionResult, error) {
log.Debug("Making JSON-RPC call to debug_traceTransaction (byOpcode)", "url", c.url, "txHash", txHash, "tracer", tracerConfig["tracer"])
var result native.TxGasDimensionByOpcodeExecutionResult
if err := c.client.CallContext(ctx, &result, "debug_traceTransaction", txHash, tracerConfig); err != nil {
log.Error("Failed to call debug_traceTransaction (byOpcode)", "error", err, "txHash", txHash)
return native.TxGasDimensionByOpcodeExecutionResult{}, fmt.Errorf("failed to call debug_traceTransaction: %w", err)
}
return result, nil
}
29 changes: 28 additions & 1 deletion system_tests/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/eth/tracers"
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/eth/tracers/native"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
Expand All @@ -73,6 +73,7 @@ import (
"github.com/offchainlabs/nitro/daprovider/das/dastree"
"github.com/offchainlabs/nitro/daprovider/das/dasutil"
"github.com/offchainlabs/nitro/deploy"
"github.com/offchainlabs/nitro/execution"
"github.com/offchainlabs/nitro/execution/gethexec"
_ "github.com/offchainlabs/nitro/execution/nodeInterface"
"github.com/offchainlabs/nitro/solgen/go/bridgegen"
Expand Down Expand Up @@ -220,6 +221,32 @@ func (tc *TestClient) BalanceDifferenceAtBlock(address common.Address, blockNum
return arbmath.BigSub(newBalance, prevBalance), nil
}

// DebugTraceTransaction calls debug_traceTransaction on the execution client.
// Routes through geth, nethermind, or comparison client based on ExecutionClientMode.
func (tc *TestClient) DebugTraceTransaction(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.ExecutionResult, error) {
if tc.ConsensusNode == nil {
return native.ExecutionResult{}, fmt.Errorf("consensus node is nil")
}
debugger, ok := tc.ConsensusNode.ExecutionClient.(execution.ExecutionDebugger)
if !ok {
return native.ExecutionResult{}, fmt.Errorf("execution client does not implement ExecutionDebugger")
}
return debugger.DebugTraceTransaction(ctx, txHash, tracerConfig)
}

// DebugTraceTransactionByOpcode calls debug_traceTransaction with txGasDimensionByOpcode tracer on the execution client.
// Routes through geth, nethermind, or comparison client based on ExecutionClientMode.
func (tc *TestClient) DebugTraceTransactionByOpcode(ctx context.Context, txHash common.Hash, tracerConfig map[string]interface{}) (native.TxGasDimensionByOpcodeExecutionResult, error) {
if tc.ConsensusNode == nil {
return native.TxGasDimensionByOpcodeExecutionResult{}, fmt.Errorf("consensus node is nil")
}
debugger, ok := tc.ConsensusNode.ExecutionClient.(execution.ExecutionDebugger)
if !ok {
return native.TxGasDimensionByOpcodeExecutionResult{}, fmt.Errorf("execution client does not implement ExecutionDebugger")
}
return debugger.DebugTraceTransactionByOpcode(ctx, txHash, tracerConfig)
}

// GetCodeHash retrieves the code hash for a contract address via RPC.
// This works with any execution client (geth or external like Nethermind).
func GetCodeHash(t *testing.T, ctx context.Context, client *ethclient.Client, address common.Address) common.Hash {
Expand Down
Loading