From f04f51cd0c2e484340c1dbee90873fbb5ef12791 Mon Sep 17 00:00:00 2001 From: Shane Bammel Date: Tue, 15 Feb 2022 21:26:18 -0600 Subject: [PATCH] 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. --- internal/ethapi/api.go | 71 ++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 48 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 65e34752b..c5df292b7 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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) { - // Binary search the gas requirement, as it may be higher than the amount used - var ( - lo uint64 = params.TxGas - 1 - hi uint64 - cap uint64 - ) + var maxGas uint64 // Use zero address if sender unspecified. if args.From == nil { args.From = new(common.Address) } // Determine the highest gas limit can be used during the estimation. if args.Gas != nil && uint64(*args.Gas) >= params.TxGas { - hi = uint64(*args.Gas) + maxGas = uint64(*args.Gas) } else { // Retrieve the block to act as the gas ceiling block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash) @@ -1017,7 +1012,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr if block == nil { 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. 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) // 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 if transfer == nil { 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) - hi = allowance.Uint64() + maxGas = allowance.Uint64() } } // Recap the highest gas allowance with specified gascap. - if gasCap != 0 && hi > gasCap { - log.Warn("Caller gas above allowance, capping", "requested", hi, "cap", gasCap) - hi = gasCap + if gasCap != 0 && maxGas > gasCap { + log.Warn("Caller gas above allowance, capping", "requested", maxGas, "cap", gasCap) + maxGas = gasCap } - cap = hi - // Create a helper to check if a gas allowance results in an executable transaction executable := func(gas uint64) (bool, *core.ExecutionResult, error) { args.Gas = (*hexutil.Uint64)(&gas) @@ -1077,41 +1070,23 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr } 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 - if hi == cap { - failed, result, err := executable(hi) - if err != nil { - 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) - } + failed, result, err := executable(maxGas) + if err != nil { + return 0, err } - 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