eth/gasestimator: early exit for plain transfer and error allowance

This commit is contained in:
Péter Szilágyi 2023-11-28 15:33:23 +02:00
parent 63979bc9cc
commit bfcb350f36
2 changed files with 33 additions and 9 deletions

View file

@ -42,6 +42,8 @@ type Options struct {
Chain core.ChainContext // Chain context to access past block hashes Chain core.ChainContext // Chain context to access past block hashes
Header *types.Header // Header defining the block context to execute in Header *types.Header // Header defining the block context to execute in
State *state.StateDB // Pre-state on top of which to estimate the gas State *state.StateDB // Pre-state on top of which to estimate the gas
ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination
} }
// Estimate returns the lowest possible gas limit that allows the transaction to // Estimate returns the lowest possible gas limit that allows the transaction to
@ -50,8 +52,8 @@ type Options struct {
func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) { func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) {
// Binary search the gas limit, as it may need to be higher than the amount used // Binary search the gas limit, as it may need to be higher than the amount used
var ( var (
lo uint64 // lowest-known gas limit where tx execution fails lo = params.TxGas - 1 // lowest-known gas limit where tx execution fails
hi uint64 // lowest-known gas limit where tx execution succeeds hi uint64 // lowest-known gas limit where tx execution succeeds
) )
// Determine the highest gas limit can be used during the estimation. // Determine the highest gas limit can be used during the estimation.
hi = opts.Header.GasLimit hi = opts.Header.GasLimit
@ -86,16 +88,28 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
if transfer == nil { if transfer == nil {
transfer = new(big.Int) transfer = new(big.Int)
} }
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance) "sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
hi = allowance.Uint64() hi = allowance.Uint64()
} }
} }
// Recap the highest gas allowance with specified gascap. // Recap the highest gas allowance with specified gascap.
if gasCap != 0 && hi > gasCap { if gasCap != 0 && hi > gasCap {
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
hi = gasCap hi = gasCap
} }
// If the transaction is a plain value transfer, short circuit estimation and
// directly try 21000. Returning 21000 without any execution is dangerous as
// some tx field combos might bump the price up even for plain transfers (e.g.
// unused access list items). Ever so slightly wasteful, but safer overall.
if len(call.Data) == 0 {
if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 {
failed, _, err := execute(ctx, call, opts, params.TxGas)
if !failed && err == nil {
return params.TxGas, nil, nil
}
}
}
// We first execute the transaction at the highest allowable gas limit, since if this fails we // We first execute the transaction at the highest allowable gas limit, since if this fails we
// can return error immediately. // can return error immediately.
failed, result, err := execute(ctx, call, opts, hi) failed, result, err := execute(ctx, call, opts, hi)
@ -114,9 +128,18 @@ func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uin
// given limit, but we probably don't want to return the lowest possible gas // given limit, but we probably don't want to return the lowest possible gas
// limit for these cases anyway. // limit for these cases anyway.
lo = result.UsedGas - 1 lo = result.UsedGas - 1
// Binary search for the smallest gas limit that allows the tx to execute successfully. // Binary search for the smallest gas limit that allows the tx to execute successfully.
for lo+1 < hi { for lo+1 < hi {
if opts.ErrorRatio > 0 {
// It is a bit pointless to return a perfect estimation, as changing
// network conditions require the caller to bump it up anyway. Since
// wallets tend to use 20-25% bump, allowing a small approximation
// error is fine (as long as it's upwards).
if float64(hi-lo)/float64(hi) < opts.ErrorRatio {
break
}
}
mid := (hi + lo) / 2 mid := (hi + lo) / 2
if mid > lo*2 { if mid > lo*2 {
// Most txs don't need much higher gas limit than their gas used, and most txs don't // Most txs don't need much higher gas limit than their gas used, and most txs don't

View file

@ -1189,10 +1189,11 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
} }
// Construct the gas estimator option from the user input // Construct the gas estimator option from the user input
opts := &gasestimator.Options{ opts := &gasestimator.Options{
Config: b.ChainConfig(), Config: b.ChainConfig(),
Chain: NewChainContext(ctx, b), Chain: NewChainContext(ctx, b),
Header: header, Header: header,
State: state, State: state,
ErrorRatio: 0.015,
} }
// Run the gas estimation andwrap any revertals into a custom return // Run the gas estimation andwrap any revertals into a custom return
call, err := args.ToMessage(gasCap, header.BaseFee) call, err := args.ToMessage(gasCap, header.BaseFee)