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 }