From 430ee4d021b9d246b87b7cc23b849527c489e52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Szil=C3=A1gyi?= Date: Tue, 14 Nov 2017 17:35:11 +0200 Subject: [PATCH] accounts, internal: minor polishes on the gas estimator --- accounts/abi/bind/backends/simulated.go | 32 ++++++++++++------------- internal/ethapi/api.go | 31 ++++++++++++------------ 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 38128f3674..09288d401e 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -41,7 +41,7 @@ import ( var _ bind.ContractBackend = (*SimulatedBackend)(nil) var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block") -var errGasEstimationFailed = errors.New("gas required exceeds block limit or always failing transaction") +var errGasEstimationFailed = errors.New("gas required exceeds allowance or always failing transaction") // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in // the background. Its main purpose is to allow easily testing contract bindings. @@ -204,46 +204,44 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs b.mu.Lock() defer b.mu.Unlock() - // Binary search the gas requirement, as it may be higher than the amount used + // Determine the lowest and highest possible gas limits to binary search in between var ( - lo uint64 = params.TxGas - 1 - hi, gasLimit uint64 + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 ) if call.Gas != nil && call.Gas.Uint64() >= params.TxGas { hi = call.Gas.Uint64() } else { hi = b.pendingBlock.GasLimit().Uint64() } - gasLimit = hi + cap = hi - // Determine whether the gas is adequate based on the result of the execution. - estimate := func(gas uint64) bool { + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) bool { call.Gas = new(big.Int).SetUint64(gas) + snapshot := b.pendingState.Snapshot() _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) b.pendingState.RevertToSnapshot(snapshot) + if err != nil || failed { return false } return true } - + // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { - // Take a guess at the gas, and check transaction validity mid := (hi + lo) / 2 - if !estimate(mid) { - // If the transaction became invalid or execution failed, raise the gas limit + if !executable(mid) { lo = mid } else { - // Otherwise assume the transaction succeeded, lower the gas limit hi = mid } } - - if hi == gasLimit { - // Regard the transaction is invalid if the required gas exceeds block limit - // or simply a bad transaction. Since this type transaction will always fail. - if !estimate(hi) { + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + if !executable(hi) { return nil, errGasEstimationFailed } } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index aae4700b42..59a29d7226 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -649,12 +649,14 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr r return (hexutil.Bytes)(result), err } -// EstimateGas returns an estimate of the amount of gas needed to execute the given transaction. +// EstimateGas returns an estimate of the amount of gas needed to execute the +// given transaction against the current pending block. func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (*hexutil.Big, error) { - // Binary search the gas requirement, as it may be higher than the amount used + // Determine the lowest and highest possible gas limits to binary search in between var ( - lo uint64 = params.TxGas - 1 - hi, gasLimit uint64 + lo uint64 = params.TxGas - 1 + hi uint64 + cap uint64 ) if (*big.Int)(&args.Gas).Uint64() >= params.TxGas { hi = (*big.Int)(&args.Gas).Uint64() @@ -666,9 +668,10 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (* } hi = block.GasLimit().Uint64() } - gasLimit = hi + cap = hi - estimate := func(gas uint64) bool { + // Create a helper to check if a gas allowance results in an executable transaction + executable := func(gas uint64) bool { (*big.Int)(&args.Gas).SetUint64(gas) _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{}) if err != nil || failed { @@ -676,23 +679,19 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (* } return true } - + // Execute the binary search and hone in on an executable gas limit for lo+1 < hi { - // Take a guess at the gas, and check transaction validity mid := (hi + lo) / 2 - if !estimate(mid) { - // If the transaction became invalid or execution failed, raise the gas limit + if !executable(mid) { lo = mid } else { - // Otherwise assume the transaction succeeded, lower the gas limit hi = mid } } - if hi == gasLimit { - // Regard the transaction is invalid if the required gas exceeds block limit - // or simply a bad transaction. Since this type transaction will always fail. - if !estimate(hi) { - return nil, fmt.Errorf("gas required exceeds block limit or always failing transaction") + // Reject the transaction as invalid if it still fails at the highest allowance + if hi == cap { + if !executable(hi) { + return nil, fmt.Errorf("gas required exceeds allowance or always failing transaction") } } return (*hexutil.Big)(new(big.Int).SetUint64(hi)), nil