eth/tracers: apply block header overrides correctly (#32183)
Some checks are pending
/ Linux Build (push) Waiting to run
/ Linux Build (arm) (push) Waiting to run
/ Windows Build (push) Waiting to run
/ Docker Image (push) Waiting to run

Fixes #32175.

This fixes the scenario where the blockhash opcode would return 0x0
during RPC simulations when using BlockOverrides with a future block
number. The root cause was that BlockOverrides.Apply() only modified the
vm.BlockContext, but GetHashFn() depends on the actual
types.Header.Number to resolve valid historical block hashes. This
caused a mismatch and resulted in incorrect behavior during trace and
call simulations.

---------

Co-authored-by: shantichanal <158101918+shantichanal@users.noreply.github.com>
Co-authored-by: lightclient <lightclient@protonmail.com>
This commit is contained in:
shazam8253 2025-07-16 23:26:33 +02:00 committed by GitHub
parent 66df1f26b8
commit 30e3a49180
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 44 additions and 11 deletions

View file

@ -953,40 +953,53 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
} }
defer release() defer release()
vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) h := block.Header()
blockContext := core.NewEVMBlockContext(h, api.chainContext(ctx), nil)
// Apply the customization rules if required. // Apply the customization rules if required.
if config != nil { if config != nil {
if overrideErr := config.BlockOverrides.Apply(&vmctx); overrideErr != nil { if config.BlockOverrides != nil && config.BlockOverrides.Number.ToInt().Uint64() == h.Number.Uint64()+1 {
return nil, overrideErr // Overriding the block number to n+1 is a common way for wallets to
// simulate transactions, however without the following fix, a contract
// can assert it is being simulated by checking if blockhash(n) == 0x0 and
// can behave differently during the simulation. (#32175 for more info)
// --
// Modify the parent hash and number so that downstream, blockContext's
// GetHash function can correctly return n.
h.ParentHash = h.Hash()
h.Number.Add(h.Number, big.NewInt(1))
} }
rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time) if err := config.BlockOverrides.Apply(&blockContext); err != nil {
return nil, err
}
rules := api.backend.ChainConfig().Rules(blockContext.BlockNumber, blockContext.Random != nil, blockContext.Time)
precompiles = vm.ActivePrecompiledContracts(rules) precompiles = vm.ActivePrecompiledContracts(rules)
if err := config.StateOverrides.Apply(statedb, precompiles); err != nil { if err := config.StateOverrides.Apply(statedb, precompiles); err != nil {
return nil, err return nil, err
} }
} }
// Execute the trace
if err := args.CallDefaults(api.backend.RPCGasCap(), vmctx.BaseFee, api.backend.ChainConfig().ChainID); err != nil { // Execute the trace.
if err := args.CallDefaults(api.backend.RPCGasCap(), blockContext.BaseFee, api.backend.ChainConfig().ChainID); err != nil {
return nil, err return nil, err
} }
var ( var (
msg = args.ToMessage(vmctx.BaseFee, true, true) msg = args.ToMessage(blockContext.BaseFee, true, true)
tx = args.ToTransaction(types.LegacyTxType) tx = args.ToTransaction(types.LegacyTxType)
traceConfig *TraceConfig traceConfig *TraceConfig
) )
// Lower the basefee to 0 to avoid breaking EVM // Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap). // invariants (basefee < feecap).
if msg.GasPrice.Sign() == 0 { if msg.GasPrice.Sign() == 0 {
vmctx.BaseFee = new(big.Int) blockContext.BaseFee = new(big.Int)
} }
if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 { if msg.BlobGasFeeCap != nil && msg.BlobGasFeeCap.BitLen() == 0 {
vmctx.BlobBaseFee = new(big.Int) blockContext.BlobBaseFee = new(big.Int)
} }
if config != nil { if config != nil {
traceConfig = &config.TraceConfig traceConfig = &config.TraceConfig
} }
return api.traceTx(ctx, tx, msg, new(Context), vmctx, statedb, traceConfig, precompiles) return api.traceTx(ctx, tx, msg, new(Context), blockContext, statedb, traceConfig, precompiles)
} }
// traceTx configures a new tracer according to the provided configuration, and // traceTx configures a new tracer according to the provided configuration, and

View file

@ -689,6 +689,7 @@ func TestTracingWithOverrides(t *testing.T) {
Failed bool Failed bool
ReturnValue string ReturnValue string
} }
var testSuite = []struct { var testSuite = []struct {
blockNumber rpc.BlockNumber blockNumber rpc.BlockNumber
call ethapi.TransactionArgs call ethapi.TransactionArgs
@ -788,6 +789,25 @@ func TestTracingWithOverrides(t *testing.T) {
}, },
want: `{"gas":72666,"failed":false,"returnValue":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`, want: `{"gas":72666,"failed":false,"returnValue":"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"}`,
}, },
{ // Override blocknumber with block n+1 and query a blockhash (resolves issue #32175)
blockNumber: rpc.LatestBlockNumber,
call: ethapi.TransactionArgs{
From: &accounts[0].addr,
Input: newRPCBytes([]byte{
byte(vm.PUSH1), byte(genBlocks),
byte(vm.BLOCKHASH),
byte(vm.PUSH1), 0x00,
byte(vm.MSTORE),
byte(vm.PUSH1), 0x20,
byte(vm.PUSH1), 0x00,
byte(vm.RETURN),
}),
},
config: &TraceCallConfig{
BlockOverrides: &override.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(int64(genBlocks + 1)))},
},
want: fmt.Sprintf(`{"gas":59590,"failed":false,"returnValue":"%s"}`, backend.chain.GetHeaderByNumber(uint64(genBlocks)).Hash().Hex()),
},
/* /*
pragma solidity =0.8.12; pragma solidity =0.8.12;