From 429d7aca05956a866d2c3cb3f09925b2f20627a4 Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Sat, 14 Feb 2026 23:54:00 +0800 Subject: [PATCH] eth/tracers: align traceBlockParallel with pre-exec system calls traceBlockParallel rebuilt per-tx prestate by replaying transactions but did not execute the same pre-exec system calls that traceBlock/traceChain apply before transaction execution. This created a semantic mismatch under post- merge forks (EIP-4788/Prague), where tracer-visible state could diverge from the canonical block execution context. Root cause - The parallel fast-replay path initialized EVM and immediately started tx replay. - It skipped: - ProcessBeaconBlockRoot (EIP-4788) - ProcessParentBlockHash (Prague / EIP-2935) Fix - In traceBlockParallel, after constructing EVM on parent state and before tx replay, execute the same pre-exec system calls as other tracing paths: - ProcessBeaconBlockRoot when block has a beacon root - ProcessParentBlockHash when Prague rules are active - This makes parallel tracing state preparation behaviorally equivalent to traceBlock and traceChain. Regression test - Add TestTraceBlockParallelPragueParentHashSystemCall. - Build a post-merge test chain (beacon+ethash faker), seed history storage contract in genesis, run traceBlockParallel, and assert that the EIP-2935 ring-buffer slot for block-1 stores block.ParentHash(). - The test fails without the fix and passes with it. Validation - go test ./eth/tracers -run TestTraceBlockParallelPragueParentHashSystemCall - go test ./eth/tracers -run 'TestTraceBlock|TestTraceCall|TestTraceTransaction' --- eth/tracers/api.go | 6 ++++ eth/tracers/api_test.go | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5f2f16627a..667eb52df4 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -692,6 +692,12 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat var failed error blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) evm := vm.NewEVM(blockCtx, statedb, api.backend.ChainConfig(), vm.Config{}) + if beaconRoot := block.BeaconRoot(); beaconRoot != nil { + core.ProcessBeaconBlockRoot(*beaconRoot, evm) + } + if api.backend.ChainConfig().IsPrague(block.Number(), block.Time()) { + core.ProcessParentBlockHash(block.ParentHash(), evm) + } txloop: for i, tx := range txs { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index f76c35a1d5..4670945ff2 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -19,6 +19,7 @@ package tracers import ( "context" "crypto/ecdsa" + "encoding/binary" "encoding/json" "errors" "fmt" @@ -284,6 +285,70 @@ func TestStateHooks(t *testing.T) { } } +func TestTraceBlockParallelPragueParentHashSystemCall(t *testing.T) { + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + from = crypto.PubkeyToAddress(key.PublicKey) + to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") + cfg = *params.AllDevChainProtocolChanges + nonce = uint64(0) + ) + + genesis := &core.Genesis{ + Config: &cfg, + Alloc: types.GenesisAlloc{ + from: {Balance: big.NewInt(params.Ether)}, + to: {Balance: big.NewInt(0)}, + params.HistoryStorageAddress: {Nonce: 1, Code: params.HistoryStorageCode, Balance: common.Big0}, + }, + } + backend := newTestMergedBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ + Nonce: nonce, + To: &to, + Value: big.NewInt(1), + Gas: params.TxGas, + GasPrice: b.BaseFee(), + }), types.HomesteadSigner{}, key) + b.AddTx(tx) + nonce++ + }) + defer backend.teardown() + + api := NewAPI(backend) + block := backend.chain.GetBlockByNumber(1) + if block == nil { + t.Fatal("failed to retrieve test block") + } + parent := backend.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + t.Fatal("failed to retrieve parent block") + } + statedb, release, err := backend.StateAtBlock(context.Background(), parent, defaultTraceReexec, nil, true, false) + if err != nil { + t.Fatalf("failed to create parent state: %v", err) + } + defer release() + + results, err := api.traceBlockParallel(context.Background(), block, statedb, nil) + if err != nil { + t.Fatalf("traceBlockParallel failed: %v", err) + } + if len(results) != 1 { + t.Fatalf("unexpected result length: have %d want 1", len(results)) + } + if results[0].Error != "" { + t.Fatalf("unexpected trace error: %v", results[0].Error) + } + var historyKey common.Hash + binary.BigEndian.PutUint64(historyKey[24:], (block.NumberU64()-1)%params.HistoryServeWindow) + have := statedb.GetState(params.HistoryStorageAddress, historyKey) + want := block.ParentHash() + if have != want { + t.Fatalf("unexpected parent hash in storage: have %v want %v", have, want) + } +} + func TestTraceCall(t *testing.T) { t.Parallel()