From 269b0fbc7a3712841b083981d4fbf89fca44faaa Mon Sep 17 00:00:00 2001 From: Philip Su Date: Wed, 11 Dec 2024 19:02:51 -0800 Subject: [PATCH 1/4] Add defer recovery for failed txs when tracing --- eth/tracers/api.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 2886fcd0cb..e3fa5c8620 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -611,12 +611,21 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config) - if err != nil { - results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} - } else { + + func() { + defer func() { + if r := recover(); r != nil { + results[i] = &txTraceResult{TxHash: tx.Hash(), Error: fmt.Sprintf("panic: %v", r)} + } + }() + + res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config) + if err != nil { + results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} + return + } results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} - } + }() } return results, nil } From 77dfc466062ed8da8fdc563eabe16e05b4649fcd Mon Sep 17 00:00:00 2001 From: Philip Su Date: Wed, 11 Dec 2024 19:15:18 -0800 Subject: [PATCH 2/4] Recover at traceTx --- eth/tracers/api.go | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index e3fa5c8620..cb472aebc3 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -612,20 +612,12 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac TxHash: tx.Hash(), } - func() { - defer func() { - if r := recover(); r != nil { - results[i] = &txTraceResult{TxHash: tx.Hash(), Error: fmt.Sprintf("panic: %v", r)} - } - }() - - res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config) - if err != nil { - results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} - return - } + res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config) + if err != nil { + results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} + } else { results[i] = &txTraceResult{TxHash: tx.Hash(), Result: res} - }() + } } return results, nil } @@ -953,13 +945,20 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb vm.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *core.Message, txctx *Context, vmctx vm.BlockContext, statedb vm.StateDB, config *TraceConfig) (value interface{}, returnErr error) { + // Note that traceTx could panic, so we want to catch it and send a generic error message var ( tracer *Tracer err error timeout = defaultTraceTimeout usedGas uint64 ) + defer func() { + if r := recover(); r != nil { + value = nil + returnErr = fmt.Errorf("panic occured: %s, could not trace tx: %s", r, tx.Hash()) + } + }() if config == nil { config = &TraceConfig{} } From d0418a2013e7501f29601ddb24c9e3ad0e1a7810 Mon Sep 17 00:00:00 2001 From: Philip Su Date: Thu, 12 Dec 2024 07:55:32 -0800 Subject: [PATCH 3/4] Add panic tx test --- eth/tracers/api.go | 1 - eth/tracers/api_test.go | 68 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index cb472aebc3..2d2cf1f67f 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -611,7 +611,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(ctx, tx, msg, txctx, blockCtx, statedb, config) if err != nil { results[i] = &txTraceResult{TxHash: tx.Hash(), Error: err.Error()} diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 2f5bf4f7d5..fba30b018f 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -457,6 +457,74 @@ func TestTraceTransaction(t *testing.T) { } } +func TestTracePanicTransaction(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{ + Config: params.TestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + accounts[1].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + target := common.Hash{} + signer := types.HomesteadSigner{} + backend := newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &accounts[1].addr, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, accounts[0].key) + b.AddTx(tx) + target = tx.Hash() + }) + defer backend.chain.Stop() + + // Create API with a backend that uses a panic-inducing StateDB + panicBackend := &panicBackend{backend} + api := NewAPI(panicBackend) + + result, err := api.TraceTransaction(context.Background(), target, nil) + + // Verify panic was caught and handled + if err == nil { + t.Fatal("Expected error from panic recovery, got nil") + } + if result != nil { + t.Errorf("Expected nil result after panic, got %v", result) + } +} + +type panicBackend struct { + Backend +} + +// StateAtTransaction overrides the backend's StateAtTransaction to use a panic-inducing StateDB +func (b *panicBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (*types.Transaction, vm.BlockContext, vm.StateDB, StateReleaseFunc, error) { + tx, vmctx, _, release, err := b.Backend.StateAtTransaction(ctx, block, txIndex, reexec) + if err != nil { + return nil, vm.BlockContext{}, nil, nil, err + } + // Return a StateDB that panics on ApplyTransactionWithEVM + return tx, vmctx, &panicStateDB{}, release, nil +} + +// panicStateDB is a mock StateDB that panics during ApplyTransactionWithEVM +type panicStateDB struct { + vm.StateDB +} + +// ApplyTransactionWithEVM is overridden to panic +func (s *panicStateDB) ApplyTransactionWithEVM(message *core.Message, config *params.ChainConfig, gasPool *core.GasPool, statedb vm.StateDB, blockNumber *big.Int, blockHash common.Hash, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) ([]byte, error) { + panic("intentional panic for testing") +} + func TestTraceBlock(t *testing.T) { t.Parallel() From 4b99c0f4c63ae7b47ee040e88149a49ce8f3450d Mon Sep 17 00:00:00 2001 From: Philip Su Date: Thu, 12 Dec 2024 07:56:27 -0800 Subject: [PATCH 4/4] stfmt --- eth/tracers/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 2d2cf1f67f..67e4ce98bb 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -955,7 +955,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor defer func() { if r := recover(); r != nil { value = nil - returnErr = fmt.Errorf("panic occured: %s, could not trace tx: %s", r, tx.Hash()) + returnErr = fmt.Errorf("panic occured: %v, could not trace tx: %s", r, tx.Hash()) } }() if config == nil {