From 296d612167b17f3e06b292a5c665f7d93a8d6067 Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Tue, 17 Feb 2026 17:30:55 +0800 Subject: [PATCH] fix(eth): align traceBlockParallel with Prague parent-hash pre-exec (#2067) traceBlockParallel replays transactions from the parent state to build per-tx prestate for tracers. In the previous flow, Prague's parent-hash system contract processing was not applied before replay, so the prepared state could diverge from canonical block execution semantics. This commit keeps the parallel tracing path consistent with block processing rules by applying ProcessParentBlockHash before tx replay when Prague is active. The regression test TestTraceBlockParallelPragueParentHashSystemCall is strengthened to assert the actual EIP-2935 history slot value (ring-buffer key for block-1) equals block.ParentHash(), instead of relying on an indirect EXTCODESIZE side effect. This makes the test deterministic and directly tied to the bug. Validation: go test ./eth/tracers -run TestTraceBlockParallelPragueParentHashSystemCall --- eth/tracers/api.go | 3 ++ eth/tracers/api_test.go | 70 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 40d3ca4cb2..41b30df579 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -694,6 +694,9 @@ 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, nil, api.backend.ChainConfig(), vm.Config{}) + if api.backend.ChainConfig().IsPrague(block.Number()) { + core.ProcessParentBlockHash(block.ParentHash(), evm, statedb) + } txloop: for i, tx := range txs { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 4b8b75e7ae..50096a621c 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" @@ -298,6 +299,75 @@ func TestStateHooks(t *testing.T) { } } +func TestTraceBlockParallelPragueParentHashSystemCall(t *testing.T) { + t.Parallel() + + var ( + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + from = crypto.PubkeyToAddress(key.PublicKey) + to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") + config = *params.TestChainConfig + nonce = uint64(0) + ) + config.Eip1559Block = big.NewInt(0) + config.PragueBlock = big.NewInt(0) + + genesis := &core.Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + from: {Balance: big.NewInt(params.Ether)}, + to: {Balance: big.NewInt(0)}, + }, + } + + backend := newTestBackend(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(), + Data: nil, + }), 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 history storage: have %v want %v", have, want) + } +} + func TestTraceCall(t *testing.T) { t.Parallel()