accounts, internal: minor polishes on the gas estimator

This commit is contained in:
Péter Szilágyi 2017-11-14 17:35:11 +02:00
parent e6a9ff9d4f
commit 430ee4d021
No known key found for this signature in database
GPG key ID: E9AE538CEDF8293D
2 changed files with 30 additions and 33 deletions

View file

@ -41,7 +41,7 @@ import (
var _ bind.ContractBackend = (*SimulatedBackend)(nil) var _ bind.ContractBackend = (*SimulatedBackend)(nil)
var errBlockNumberUnsupported = errors.New("SimulatedBackend cannot access blocks other than the latest block") 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 // SimulatedBackend implements bind.ContractBackend, simulating a blockchain in
// the background. Its main purpose is to allow easily testing contract bindings. // 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() b.mu.Lock()
defer b.mu.Unlock() 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 ( var (
lo uint64 = params.TxGas - 1 lo uint64 = params.TxGas - 1
hi, gasLimit uint64 hi uint64
cap uint64
) )
if call.Gas != nil && call.Gas.Uint64() >= params.TxGas { if call.Gas != nil && call.Gas.Uint64() >= params.TxGas {
hi = call.Gas.Uint64() hi = call.Gas.Uint64()
} else { } else {
hi = b.pendingBlock.GasLimit().Uint64() hi = b.pendingBlock.GasLimit().Uint64()
} }
gasLimit = hi cap = hi
// Determine whether the gas is adequate based on the result of the execution. // Create a helper to check if a gas allowance results in an executable transaction
estimate := func(gas uint64) bool { executable := func(gas uint64) bool {
call.Gas = new(big.Int).SetUint64(gas) call.Gas = new(big.Int).SetUint64(gas)
snapshot := b.pendingState.Snapshot() snapshot := b.pendingState.Snapshot()
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState) _, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
b.pendingState.RevertToSnapshot(snapshot) b.pendingState.RevertToSnapshot(snapshot)
if err != nil || failed { if err != nil || failed {
return false return false
} }
return true return true
} }
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi { for lo+1 < hi {
// Take a guess at the gas, and check transaction validity
mid := (hi + lo) / 2 mid := (hi + lo) / 2
if !estimate(mid) { if !executable(mid) {
// If the transaction became invalid or execution failed, raise the gas limit
lo = mid lo = mid
} else { } else {
// Otherwise assume the transaction succeeded, lower the gas limit
hi = mid hi = mid
} }
} }
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == gasLimit { if hi == cap {
// Regard the transaction is invalid if the required gas exceeds block limit if !executable(hi) {
// or simply a bad transaction. Since this type transaction will always fail.
if !estimate(hi) {
return nil, errGasEstimationFailed return nil, errGasEstimationFailed
} }
} }

View file

@ -649,12 +649,14 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNr r
return (hexutil.Bytes)(result), err 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) { 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 ( var (
lo uint64 = params.TxGas - 1 lo uint64 = params.TxGas - 1
hi, gasLimit uint64 hi uint64
cap uint64
) )
if (*big.Int)(&args.Gas).Uint64() >= params.TxGas { if (*big.Int)(&args.Gas).Uint64() >= params.TxGas {
hi = (*big.Int)(&args.Gas).Uint64() hi = (*big.Int)(&args.Gas).Uint64()
@ -666,9 +668,10 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (*
} }
hi = block.GasLimit().Uint64() 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) (*big.Int)(&args.Gas).SetUint64(gas)
_, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{}) _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{})
if err != nil || failed { if err != nil || failed {
@ -676,23 +679,19 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (*
} }
return true return true
} }
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi { for lo+1 < hi {
// Take a guess at the gas, and check transaction validity
mid := (hi + lo) / 2 mid := (hi + lo) / 2
if !estimate(mid) { if !executable(mid) {
// If the transaction became invalid or execution failed, raise the gas limit
lo = mid lo = mid
} else { } else {
// Otherwise assume the transaction succeeded, lower the gas limit
hi = mid hi = mid
} }
} }
if hi == gasLimit { // Reject the transaction as invalid if it still fails at the highest allowance
// Regard the transaction is invalid if the required gas exceeds block limit if hi == cap {
// or simply a bad transaction. Since this type transaction will always fail. if !executable(hi) {
if !estimate(hi) { return nil, fmt.Errorf("gas required exceeds allowance or always failing transaction")
return nil, fmt.Errorf("gas required exceeds block limit or always failing transaction")
} }
} }
return (*hexutil.Big)(new(big.Int).SetUint64(hi)), nil return (*hexutil.Big)(new(big.Int).SetUint64(hi)), nil