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
This commit is contained in:
Daniel Liu 2026-02-17 17:30:55 +08:00 committed by GitHub
parent bba58ec20b
commit 296d612167
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 73 additions and 0 deletions

View file

@ -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 {

View file

@ -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()