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