Simplify DoEstimateGas with RequiredGas field

By tracking the RequiredGas independently from refunds,
DoEstimateGas() can return an accurate gas estimate without the
complexity of the binary search.
This commit is contained in:
Shane Bammel 2022-02-15 21:26:18 -06:00
parent f6ce963881
commit f04f51cd0c

View File

@ -995,19 +995,14 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl
} }
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) { func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used var maxGas uint64
var (
lo uint64 = params.TxGas - 1
hi uint64
cap uint64
)
// Use zero address if sender unspecified. // Use zero address if sender unspecified.
if args.From == nil { if args.From == nil {
args.From = new(common.Address) args.From = new(common.Address)
} }
// Determine the highest gas limit can be used during the estimation. // Determine the highest gas limit can be used during the estimation.
if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { if args.Gas != nil && uint64(*args.Gas) >= params.TxGas {
hi = uint64(*args.Gas) maxGas = uint64(*args.Gas)
} else { } else {
// Retrieve the block to act as the gas ceiling // Retrieve the block to act as the gas ceiling
block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash) block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
@ -1017,7 +1012,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
if block == nil { if block == nil {
return 0, errors.New("block not found") return 0, errors.New("block not found")
} }
hi = block.GasLimit() maxGas = block.GasLimit()
} }
// Normalize the max fee per gas the call is willing to spend. // Normalize the max fee per gas the call is willing to spend.
var feeCap *big.Int var feeCap *big.Int
@ -1047,23 +1042,21 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
allowance := new(big.Int).Div(available, feeCap) allowance := new(big.Int).Div(available, feeCap)
// If the allowance is larger than maximum uint64, skip checking // If the allowance is larger than maximum uint64, skip checking
if allowance.IsUint64() && hi > allowance.Uint64() { if allowance.IsUint64() && maxGas > allowance.Uint64() {
transfer := args.Value transfer := args.Value
if transfer == nil { if transfer == nil {
transfer = new(hexutil.Big) transfer = new(hexutil.Big)
} }
log.Warn("Gas estimation capped by limited funds", "original", hi, "balance", balance, log.Warn("Gas estimation capped by limited funds", "original", maxGas, "balance", balance,
"sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance) "sent", transfer.ToInt(), "maxFeePerGas", feeCap, "fundable", allowance)
hi = allowance.Uint64() maxGas = 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 && maxGas > gasCap {
log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) log.Warn("Caller gas above allowance, capping", "requested", maxGas, "cap", gasCap)
hi = gasCap maxGas = gasCap
} }
cap = hi
// Create a helper to check if a gas allowance results in an executable transaction // Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (bool, *core.ExecutionResult, error) { executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas) args.Gas = (*hexutil.Uint64)(&gas)
@ -1077,41 +1070,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
} }
return result.Failed(), result, nil return result.Failed(), result, nil
} }
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
failed, _, err := executable(mid)
// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigned. Return the error directly, don't struggle any more.
if err != nil {
return 0, err
}
if failed {
lo = mid
} else {
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance // Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap { failed, result, err := executable(maxGas)
failed, result, err := executable(hi) if err != nil {
if err != nil { return 0, err
return 0, err
}
if failed {
if result != nil && result.Err != vm.ErrOutOfGas {
if len(result.Revert()) > 0 {
return 0, newRevertError(result)
}
return 0, result.Err
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}
} }
return hexutil.Uint64(hi), nil if failed {
if result != nil && result.Err != vm.ErrOutOfGas {
if len(result.Revert()) > 0 {
return 0, newRevertError(result)
}
return 0, result.Err
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", maxGas)
}
return hexutil.Uint64(result.RequiredGas), nil
} }
// EstimateGas returns an estimate of the amount of gas needed to execute the // EstimateGas returns an estimate of the amount of gas needed to execute the