internal/ethapi, eth/gasestimator: honor block overrides in estimate gas

Apply block overrides consistently for eth_estimateGas by deriving an overridden header in DoEstimateGas and using it for estimator setup.

Since header does not carry blobBaseFee, plumb BlobBaseFee explicitly into gasestimator options and apply it to the EVM block context during execution.

Also add regression coverage for:

- gasLimit override bounding estimation allowance

- blobBaseFee override triggering ErrBlobFeeCapTooLow when BlobFeeCap is too low
This commit is contained in:
Daniel Liu 2026-03-24 14:29:38 +08:00
parent fc43170cdd
commit 5dc734c150
3 changed files with 46 additions and 28 deletions

View file

@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/ethapi/override"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
)
@ -38,11 +37,12 @@ import (
// these together, it would be excessively hard to test. Splitting the parts out
// allows testing without needing a proper live chain.
type Options struct {
Config *params.ChainConfig // Chain configuration for hard fork selection
Chain core.ChainContext // Chain context to access past block hashes
Header *types.Header // Header defining the block context to execute in
State *state.StateDB // Pre-state on top of which to estimate the gas
BlockOverrides *override.BlockOverrides // Block overrides to apply during the estimation
Config *params.ChainConfig // Chain configuration for hard fork selection
Chain core.ChainContext // Chain context to access past block hashes
Header *types.Header // Header defining the block context to execute in
State *state.StateDB // Pre-state on top of which to estimate the gas
BlobBaseFee *big.Int // BlobBaseFee optionally overrides the blob base fee in the execution context.
ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination
}
@ -64,16 +64,7 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// Cap the maximum gas allowance according to EIP-7825 if the estimation targets Osaka
if hi > params.MaxTxGas {
blockNumber, blockTime := opts.Header.Number, opts.Header.Time
if opts.BlockOverrides != nil {
if opts.BlockOverrides.Number != nil {
blockNumber = opts.BlockOverrides.Number.ToInt()
}
if opts.BlockOverrides.Time != nil {
blockTime = uint64(*opts.BlockOverrides.Time)
}
}
if opts.Config.IsOsaka(blockNumber, blockTime) {
if opts.Config.IsOsaka(opts.Header.Number, opts.Header.Time) {
hi = params.MaxTxGas
}
}
@ -241,10 +232,8 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
dirtyState = opts.State.Copy()
)
if opts.BlockOverrides != nil {
if err := opts.BlockOverrides.Apply(&evmContext); err != nil {
return nil, err
}
if opts.BlobBaseFee != nil {
evmContext.BlobBaseFee = new(big.Int).Set(opts.BlobBaseFee)
}
// Lower the basefee to 0 to avoid breaking EVM
// invariants (basefee < feecap).

View file

@ -897,6 +897,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
if err := blockOverrides.Apply(&blockCtx); err != nil {
return 0, err
}
header = blockOverrides.MakeHeader(header)
}
rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time)
precompiles := vm.ActivePrecompiledContracts(rules)
@ -904,13 +905,17 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
return 0, err
}
// Construct the gas estimator option from the user input
var blobBaseFee *big.Int
if blockOverrides != nil && blockOverrides.BlobBaseFee != nil {
blobBaseFee = blockOverrides.BlobBaseFee.ToInt()
}
opts := &gasestimator.Options{
Config: b.ChainConfig(),
Chain: NewChainContext(ctx, b),
Header: header,
BlockOverrides: blockOverrides,
State: state,
ErrorRatio: estimateGasErrorRatio,
Config: b.ChainConfig(),
Chain: NewChainContext(ctx, b),
Header: header,
State: state,
BlobBaseFee: blobBaseFee,
ErrorRatio: estimateGasErrorRatio,
}
// Set any required transaction default, but make sure the gas cap itself is not messed with
// if it was not specified in the original argument list.

View file

@ -780,6 +780,17 @@ func TestEstimateGas(t *testing.T) {
expectErr: core.ErrInsufficientFunds,
want: 21000,
},
// block override gas limit should bound estimation search space.
{
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{
From: &accounts[0].addr,
Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"),
Gas: func() *hexutil.Uint64 { v := hexutil.Uint64(0); return &v }(),
},
blockOverrides: override.BlockOverrides{GasLimit: func() *hexutil.Uint64 { v := hexutil.Uint64(50000); return &v }()},
expectErr: errors.New("gas required exceeds allowance (50000)"),
},
// empty create
{
blockNumber: rpc.LatestBlockNumber,
@ -861,6 +872,19 @@ func TestEstimateGas(t *testing.T) {
},
want: 21000,
},
// blob base fee block override should be applied during estimation.
{
blockNumber: rpc.LatestBlockNumber,
call: TransactionArgs{
From: &accounts[0].addr,
To: &accounts[1].addr,
Value: (*hexutil.Big)(big.NewInt(1)),
BlobHashes: []common.Hash{{0x01, 0x22}},
BlobFeeCap: (*hexutil.Big)(big.NewInt(1)),
},
blockOverrides: override.BlockOverrides{BlobBaseFee: (*hexutil.Big)(big.NewInt(2))},
expectErr: core.ErrBlobFeeCapTooLow,
},
// // SPDX-License-Identifier: GPL-3.0
//pragma solidity >=0.8.2 <0.9.0;
//
@ -1014,7 +1038,7 @@ func TestCall(t *testing.T) {
Balance: big.NewInt(params.Ether),
Nonce: 1,
Storage: map[common.Hash]common.Hash{
common.Hash{}: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
{}: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
},
},
},
@ -3795,7 +3819,7 @@ func TestCreateAccessListWithStateOverrides(t *testing.T) {
Balance: (*hexutil.Big)(big.NewInt(1000000000000000000)),
Nonce: &nonce,
State: map[common.Hash]common.Hash{
common.Hash{}: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a"),
{}: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a"),
},
},
}