From bba58ec20bd12c3ac2ff6dabaf54cb9e6c0bb2cf Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Tue, 17 Feb 2026 17:14:47 +0800 Subject: [PATCH] fix(eth): fix state hooks in API #30830 (#2066) --- eth/tracers/api.go | 4 +- eth/tracers/api_test.go | 107 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index f6462c6c7f..40d3ca4cb2 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -896,8 +896,8 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor return nil, err } } - // The actual TxContext will be created as part of ApplyTransactionWithEVM. - evm := vm.NewEVM(vmctx, statedb, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true}) + tracingStateDB := state.NewHookedState(statedb, tracer.Hooks) + evm := vm.NewEVM(vmctx, tracingStateDB, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer.Hooks, NoBaseFee: true}) evm.SetTxContext(vm.TxContext{GasPrice: message.GasPrice}) // Define a meaningful timeout of a single transaction trace diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 5b36093daa..4b8b75e7ae 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -38,6 +38,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/tracing" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" "github.com/XinFinOrg/XDPoSChain/crypto" @@ -191,6 +192,112 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) } +type stateTracer struct { + Balance map[common.Address]*hexutil.Big + Nonce map[common.Address]hexutil.Uint64 + Storage map[common.Address]map[common.Hash]common.Hash +} + +func newStateTracer(ctx *Context, cfg json.RawMessage, chainCfg *params.ChainConfig) (*Tracer, error) { + t := &stateTracer{ + Balance: make(map[common.Address]*hexutil.Big), + Nonce: make(map[common.Address]hexutil.Uint64), + Storage: make(map[common.Address]map[common.Hash]common.Hash), + } + return &Tracer{ + GetResult: func() (json.RawMessage, error) { + return json.Marshal(t) + }, + Hooks: &tracing.Hooks{ + OnBalanceChange: func(addr common.Address, prev, new *big.Int, reason tracing.BalanceChangeReason) { + t.Balance[addr] = (*hexutil.Big)(new) + }, + OnNonceChange: func(addr common.Address, prev, new uint64) { + t.Nonce[addr] = hexutil.Uint64(new) + }, + OnStorageChange: func(addr common.Address, slot common.Hash, prev, new common.Hash) { + if t.Storage[addr] == nil { + t.Storage[addr] = make(map[common.Hash]common.Hash) + } + t.Storage[addr][slot] = new + }, + }, + }, nil +} + +func TestStateHooks(t *testing.T) { + t.Parallel() + + // Initialize test accounts + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + from = crypto.PubkeyToAddress(key.PublicKey) + to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") + config = *params.TestChainConfig + genesis = &core.Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + from: {Balance: big.NewInt(params.Ether)}, + to: { + Code: []byte{ + byte(vm.PUSH1), 0x2a, // stack: [42] + byte(vm.PUSH1), 0x0, // stack: [0, 42] + byte(vm.SSTORE), // stack: [] + byte(vm.STOP), + }, + }, + }, + } + genBlocks = 2 + signer = types.HomesteadSigner{} + nonce = uint64(0) + ) + config.Eip1559Block = big.NewInt(0) + backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &to, + Value: big.NewInt(1000), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + Data: nil}), + signer, key) + b.AddTx(tx) + nonce++ + }) + defer backend.teardown() + DefaultDirectory.Register("stateTracer", newStateTracer, false) + api := NewAPI(backend) + tracer := "stateTracer" + res, err := api.TraceCall(context.Background(), ethapi.TransactionArgs{From: &from, To: &to, Value: (*hexutil.Big)(big.NewInt(1000))}, rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), &TraceCallConfig{TraceConfig: TraceConfig{Tracer: &tracer}}) + if err != nil { + t.Fatalf("failed to trace call: %v", err) + } + payload, ok := res.(json.RawMessage) + if !ok { + t.Fatalf("unexpected trace result type %T", res) + } + var got stateTracer + if err := json.Unmarshal(payload, &got); err != nil { + t.Fatalf("failed to unmarshal trace result: %v", err) + } + if got.Balance[to] == nil || (*big.Int)(got.Balance[to]).Cmp(big.NewInt(1000)) != 0 { + t.Fatalf("unexpected receiver balance: %v", got.Balance[to]) + } + if senderBal := got.Balance[from]; senderBal == nil || (*big.Int)(senderBal).Sign() <= 0 || (*big.Int)(senderBal).Cmp(big.NewInt(params.Ether)) >= 0 { + t.Fatalf("unexpected sender balance: %v", senderBal) + } + if got.Nonce[from] != hexutil.Uint64(3) { + t.Fatalf("unexpected sender nonce: %v", got.Nonce[from]) + } + if got.Storage[to][common.Hash{}] != common.HexToHash("0x2a") { + t.Fatalf("unexpected storage value: %v", got.Storage[to][common.Hash{}]) + } +} + func TestTraceCall(t *testing.T) { t.Parallel()