github.com/ethereum/go-ethereum@v1.16.1/eth/gasestimator/gasestimator.go (about)

     1  // Copyright 2023 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package gasestimator
    18  
    19  import (
    20  	"context"
    21  	"errors"
    22  	"fmt"
    23  	"math"
    24  	"math/big"
    25  
    26  	"github.com/ethereum/go-ethereum/common"
    27  	"github.com/ethereum/go-ethereum/core"
    28  	"github.com/ethereum/go-ethereum/core/state"
    29  	"github.com/ethereum/go-ethereum/core/types"
    30  	"github.com/ethereum/go-ethereum/core/vm"
    31  	"github.com/ethereum/go-ethereum/internal/ethapi/override"
    32  	"github.com/ethereum/go-ethereum/log"
    33  	"github.com/ethereum/go-ethereum/params"
    34  )
    35  
    36  // Options are the contextual parameters to execute the requested call.
    37  //
    38  // Whilst it would be possible to pass a blockchain object that aggregates all
    39  // these together, it would be excessively hard to test. Splitting the parts out
    40  // allows testing without needing a proper live chain.
    41  type Options struct {
    42  	Config         *params.ChainConfig      // Chain configuration for hard fork selection
    43  	Chain          core.ChainContext        // Chain context to access past block hashes
    44  	Header         *types.Header            // Header defining the block context to execute in
    45  	State          *state.StateDB           // Pre-state on top of which to estimate the gas
    46  	BlockOverrides *override.BlockOverrides // Block overrides to apply during the estimation
    47  
    48  	ErrorRatio float64 // Allowed overestimation ratio for faster estimation termination
    49  }
    50  
    51  // Estimate returns the lowest possible gas limit that allows the transaction to
    52  // run successfully with the provided context options. It returns an error if the
    53  // transaction would always revert, or if there are unexpected failures.
    54  func Estimate(ctx context.Context, call *core.Message, opts *Options, gasCap uint64) (uint64, []byte, error) {
    55  	// Binary search the gas limit, as it may need to be higher than the amount used
    56  	var (
    57  		lo uint64 // lowest-known gas limit where tx execution fails
    58  		hi uint64 // lowest-known gas limit where tx execution succeeds
    59  	)
    60  	// Determine the highest gas limit can be used during the estimation.
    61  	hi = opts.Header.GasLimit
    62  	if call.GasLimit >= params.TxGas {
    63  		hi = call.GasLimit
    64  	}
    65  	// Normalize the max fee per gas the call is willing to spend.
    66  	var feeCap *big.Int
    67  	if call.GasFeeCap != nil {
    68  		feeCap = call.GasFeeCap
    69  	} else if call.GasPrice != nil {
    70  		feeCap = call.GasPrice
    71  	} else {
    72  		feeCap = common.Big0
    73  	}
    74  	// Recap the highest gas limit with account's available balance.
    75  	if feeCap.BitLen() != 0 {
    76  		balance := opts.State.GetBalance(call.From).ToBig()
    77  
    78  		available := balance
    79  		if call.Value != nil {
    80  			if call.Value.Cmp(available) >= 0 {
    81  				return 0, nil, core.ErrInsufficientFundsForTransfer
    82  			}
    83  			available.Sub(available, call.Value)
    84  		}
    85  		if opts.Config.IsCancun(opts.Header.Number, opts.Header.Time) && len(call.BlobHashes) > 0 {
    86  			blobGasPerBlob := new(big.Int).SetInt64(params.BlobTxBlobGasPerBlob)
    87  			blobBalanceUsage := new(big.Int).SetInt64(int64(len(call.BlobHashes)))
    88  			blobBalanceUsage.Mul(blobBalanceUsage, blobGasPerBlob)
    89  			blobBalanceUsage.Mul(blobBalanceUsage, call.BlobGasFeeCap)
    90  			if blobBalanceUsage.Cmp(available) >= 0 {
    91  				return 0, nil, core.ErrInsufficientFunds
    92  			}
    93  			available.Sub(available, blobBalanceUsage)
    94  		}
    95  		allowance := new(big.Int).Div(available, feeCap)
    96  
    97  		// If the allowance is larger than maximum uint64, skip checking
    98  		if allowance.IsUint64() && hi > allowance.Uint64() {
    99  			transfer := call.Value
   100  			if transfer == nil {
   101  				transfer = new(big.Int)
   102  			}
   103  			log.Debug("Gas estimation capped by limited funds", "original", hi, "balance", balance,
   104  				"sent", transfer, "maxFeePerGas", feeCap, "fundable", allowance)
   105  			hi = allowance.Uint64()
   106  		}
   107  	}
   108  	// Recap the highest gas allowance with specified gascap.
   109  	if gasCap != 0 && hi > gasCap {
   110  		log.Debug("Caller gas above allowance, capping", "requested", hi, "cap", gasCap)
   111  		hi = gasCap
   112  	}
   113  	// If the transaction is a plain value transfer, short circuit estimation and
   114  	// directly try 21000. Returning 21000 without any execution is dangerous as
   115  	// some tx field combos might bump the price up even for plain transfers (e.g.
   116  	// unused access list items). Ever so slightly wasteful, but safer overall.
   117  	if len(call.Data) == 0 {
   118  		if call.To != nil && opts.State.GetCodeSize(*call.To) == 0 {
   119  			failed, _, err := execute(ctx, call, opts, params.TxGas)
   120  			if !failed && err == nil {
   121  				return params.TxGas, nil, nil
   122  			}
   123  		}
   124  	}
   125  	// We first execute the transaction at the highest allowable gas limit, since if this fails we
   126  	// can return error immediately.
   127  	failed, result, err := execute(ctx, call, opts, hi)
   128  	if err != nil {
   129  		return 0, nil, err
   130  	}
   131  	if failed {
   132  		if result != nil && !errors.Is(result.Err, vm.ErrOutOfGas) {
   133  			return 0, result.Revert(), result.Err
   134  		}
   135  		return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", hi)
   136  	}
   137  	// For almost any transaction, the gas consumed by the unconstrained execution
   138  	// above lower-bounds the gas limit required for it to succeed. One exception
   139  	// is those that explicitly check gas remaining in order to execute within a
   140  	// given limit, but we probably don't want to return the lowest possible gas
   141  	// limit for these cases anyway.
   142  	lo = result.UsedGas - 1
   143  
   144  	// There's a fairly high chance for the transaction to execute successfully
   145  	// with gasLimit set to the first execution's usedGas + gasRefund. Explicitly
   146  	// check that gas amount and use as a limit for the binary search.
   147  	optimisticGasLimit := (result.MaxUsedGas + params.CallStipend) * 64 / 63
   148  	if optimisticGasLimit < hi {
   149  		failed, _, err = execute(ctx, call, opts, optimisticGasLimit)
   150  		if err != nil {
   151  			// This should not happen under normal conditions since if we make it this far the
   152  			// transaction had run without error at least once before.
   153  			log.Error("Execution error in estimate gas", "err", err)
   154  			return 0, nil, err
   155  		}
   156  		if failed {
   157  			lo = optimisticGasLimit
   158  		} else {
   159  			hi = optimisticGasLimit
   160  		}
   161  	}
   162  	// Binary search for the smallest gas limit that allows the tx to execute successfully.
   163  	for lo+1 < hi {
   164  		if opts.ErrorRatio > 0 {
   165  			// It is a bit pointless to return a perfect estimation, as changing
   166  			// network conditions require the caller to bump it up anyway. Since
   167  			// wallets tend to use 20-25% bump, allowing a small approximation
   168  			// error is fine (as long as it's upwards).
   169  			if float64(hi-lo)/float64(hi) < opts.ErrorRatio {
   170  				break
   171  			}
   172  		}
   173  		mid := (hi + lo) / 2
   174  		if mid > lo*2 {
   175  			// Most txs don't need much higher gas limit than their gas used, and most txs don't
   176  			// require near the full block limit of gas, so the selection of where to bisect the
   177  			// range here is skewed to favor the low side.
   178  			mid = lo * 2
   179  		}
   180  		failed, _, err = execute(ctx, call, opts, mid)
   181  		if err != nil {
   182  			// This should not happen under normal conditions since if we make it this far the
   183  			// transaction had run without error at least once before.
   184  			log.Error("Execution error in estimate gas", "err", err)
   185  			return 0, nil, err
   186  		}
   187  		if failed {
   188  			lo = mid
   189  		} else {
   190  			hi = mid
   191  		}
   192  	}
   193  	return hi, nil, nil
   194  }
   195  
   196  // execute is a helper that executes the transaction under a given gas limit and
   197  // returns true if the transaction fails for a reason that might be related to
   198  // not enough gas. A non-nil error means execution failed due to reasons unrelated
   199  // to the gas limit.
   200  func execute(ctx context.Context, call *core.Message, opts *Options, gasLimit uint64) (bool, *core.ExecutionResult, error) {
   201  	// Configure the call for this specific execution (and revert the change after)
   202  	defer func(gas uint64) { call.GasLimit = gas }(call.GasLimit)
   203  	call.GasLimit = gasLimit
   204  
   205  	// Execute the call and separate execution faults caused by a lack of gas or
   206  	// other non-fixable conditions
   207  	result, err := run(ctx, call, opts)
   208  	if err != nil {
   209  		if errors.Is(err, core.ErrIntrinsicGas) {
   210  			return true, nil, nil // Special case, raise gas limit
   211  		}
   212  		return true, nil, err // Bail out
   213  	}
   214  	return result.Failed(), result, nil
   215  }
   216  
   217  // run assembles the EVM as defined by the consensus rules and runs the requested
   218  // call invocation.
   219  func run(ctx context.Context, call *core.Message, opts *Options) (*core.ExecutionResult, error) {
   220  	// Assemble the call and the call context
   221  	var (
   222  		evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil)
   223  		dirtyState = opts.State.Copy()
   224  	)
   225  	if opts.BlockOverrides != nil {
   226  		if err := opts.BlockOverrides.Apply(&evmContext); err != nil {
   227  			return nil, err
   228  		}
   229  	}
   230  	// Lower the basefee to 0 to avoid breaking EVM
   231  	// invariants (basefee < feecap).
   232  	if call.GasPrice.Sign() == 0 {
   233  		evmContext.BaseFee = new(big.Int)
   234  	}
   235  	if call.BlobGasFeeCap != nil && call.BlobGasFeeCap.BitLen() == 0 {
   236  		evmContext.BlobBaseFee = new(big.Int)
   237  	}
   238  	evm := vm.NewEVM(evmContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})
   239  
   240  	// Monitor the outer context and interrupt the EVM upon cancellation. To avoid
   241  	// a dangling goroutine until the outer estimation finishes, create an internal
   242  	// context for the lifetime of this method call.
   243  	ctx, cancel := context.WithCancel(ctx)
   244  	defer cancel()
   245  
   246  	go func() {
   247  		<-ctx.Done()
   248  		evm.Cancel()
   249  	}()
   250  	// Execute the call, returning a wrapped error or the result
   251  	result, err := core.ApplyMessage(evm, call, new(core.GasPool).AddGas(math.MaxUint64))
   252  	if vmerr := dirtyState.Error(); vmerr != nil {
   253  		return nil, vmerr
   254  	}
   255  	if err != nil {
   256  		return result, fmt.Errorf("failed with %d gas: %w", call.GasLimit, err)
   257  	}
   258  	return result, nil
   259  }