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 }