accounts, internal: fail if no suitable estimated gas found (#15477)

* accounts, internal: return an error if no suitable estimated gas found

* accounts, internal: minor polishes on the gas estimator
This commit is contained in:
gary rong 2017-11-14 10:26:31 -06:00 committed by Péter Szilágyi
parent a3128f9099
commit 984c25ac40
2 changed files with 54 additions and 26 deletions

View File

@ -41,6 +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 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.
@ -203,33 +204,47 @@ 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 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()
} }
for lo+1 < hi { cap = hi
// Take a guess at the gas, and check transaction validity
mid := (hi + lo) / 2 // Create a helper to check if a gas allowance results in an executable transaction
call.Gas = new(big.Int).SetUint64(mid) executable := func(gas uint64) bool {
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 the transaction became invalid or execution failed, raise the gas limit
if err != nil || failed { if err != nil || failed {
lo = mid return false
continue
} }
// Otherwise assume the transaction succeeded, lower the gas limit return true
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
if !executable(mid) {
lo = mid
} else {
hi = mid hi = mid
} }
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
if !executable(hi) {
return nil, errGasEstimationFailed
}
}
return new(big.Int).SetUint64(hi), nil return new(big.Int).SetUint64(hi), nil
} }

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 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,21 +668,32 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs) (*
} }
hi = block.GasLimit().Uint64() hi = block.GasLimit().Uint64()
} }
for lo+1 < hi { cap = hi
// Take a guess at the gas, and check transaction validity
mid := (hi + lo) / 2
(*big.Int)(&args.Gas).SetUint64(mid)
// 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{}) _, _, failed, err := s.doCall(ctx, args, rpc.PendingBlockNumber, vm.Config{})
// If the transaction became invalid or execution failed, raise the gas limit
if err != nil || failed { if err != nil || failed {
lo = mid return false
continue
} }
// Otherwise assume the transaction succeeded, lower the gas limit return true
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
if !executable(mid) {
lo = mid
} else {
hi = mid hi = mid
} }
}
// 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 return (*hexutil.Big)(new(big.Int).SetUint64(hi)), nil
} }