Skip to content

Commit

Permalink
add support for sequential gas estimation
Browse files Browse the repository at this point in the history
add multiple gas estimation

merge

fix test errors ethereum#21062

fix previous state error

lint

set empty previousState if not set

cleanup and create state copy during DoCall

create eth_estimateGasList method

add comments for eth_estimateGasList

enfore gasCap on eth_estimateGasList
  • Loading branch information
kvhnuke authored and rjl493456442 committed Sep 29, 2020
1 parent fdd42d4 commit a701ffa
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 39 deletions.
9 changes: 5 additions & 4 deletions graphql/graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ func (b *Block) Call(ctx context.Context, args struct {
return nil, err
}
}
result, err := ethapi.DoCall(ctx, b.backend, args.Data, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
result, _, err := ethapi.DoCall(ctx, b.backend, args.Data, nil, *b.numberOrHash, nil, vm.Config{}, 5*time.Second, b.backend.RPCGasCap())
if err != nil {
return nil, err
}
Expand All @@ -828,7 +828,7 @@ func (b *Block) EstimateGas(ctx context.Context, args struct {
return hexutil.Uint64(0), err
}
}
gas, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, *b.numberOrHash, b.backend.RPCGasCap())
gas, _, err := ethapi.DoEstimateGas(ctx, b.backend, args.Data, nil, *b.numberOrHash, b.backend.RPCGasCap())
return gas, err
}

Expand Down Expand Up @@ -873,7 +873,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
Data ethapi.CallArgs
}) (*CallResult, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
result, err := ethapi.DoCall(ctx, p.backend, args.Data, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
result, _, err := ethapi.DoCall(ctx, p.backend, args.Data, nil, pendingBlockNr, nil, vm.Config{}, 5*time.Second, p.backend.RPCGasCap())
if err != nil {
return nil, err
}
Expand All @@ -893,7 +893,8 @@ func (p *Pending) EstimateGas(ctx context.Context, args struct {
Data ethapi.CallArgs
}) (hexutil.Uint64, error) {
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
return ethapi.DoEstimateGas(ctx, p.backend, args.Data, pendingBlockNr, p.backend.RPCGasCap())
gas, _, err := ethapi.DoEstimateGas(ctx, p.backend, args.Data, nil, pendingBlockNr, p.backend.RPCGasCap())
return gas, err
}

// Resolver is the top-level object in the GraphQL hierarchy.
Expand Down
110 changes: 75 additions & 35 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -814,38 +815,51 @@ type account struct {
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
}

func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
type PreviousState struct {
state *state.StateDB
header *types.Header
}

state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, *PreviousState, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
if prevState == nil {
prevState = &PreviousState{}
}
if (prevState.header != nil && prevState.header == nil) || (prevState.header == nil && prevState.header != nil) {
return nil, nil, fmt.Errorf("both header and state must be set to use previous staate")
}
if prevState.header == nil && prevState.state == nil {
var err error
prevState.state, prevState.header, err = b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if prevState.state == nil || err != nil {
return nil, nil, err
}
}
// Override the fields of specified contracts before execution.
for addr, account := range overrides {
// Override account nonce.
if account.Nonce != nil {
state.SetNonce(addr, uint64(*account.Nonce))
prevState.state.SetNonce(addr, uint64(*account.Nonce))
}
// Override account(contract) code.
if account.Code != nil {
state.SetCode(addr, *account.Code)
prevState.state.SetCode(addr, *account.Code)
}
// Override account balance.
if account.Balance != nil {
state.SetBalance(addr, (*big.Int)(*account.Balance))
prevState.state.SetBalance(addr, (*big.Int)(*account.Balance))
}
if account.State != nil && account.StateDiff != nil {
return nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
return nil, nil, fmt.Errorf("account %s has both 'state' and 'stateDiff'", addr.Hex())
}
// Replace entire state if caller requires.
if account.State != nil {
state.SetStorage(addr, *account.State)
prevState.state.SetStorage(addr, *account.State)
}
// Apply state diff into specified accounts.
if account.StateDiff != nil {
for key, value := range *account.StateDiff {
state.SetState(addr, key, value)
prevState.state.SetState(addr, key, value)
}
}
}
Expand All @@ -863,9 +877,9 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo

// Get a new instance of the EVM.
msg := args.ToMessage(globalGasCap)
evm, vmError, err := b.GetEVM(ctx, msg, state, header)
evm, vmError, err := b.GetEVM(ctx, msg, prevState.state, prevState.header)
if err != nil {
return nil, err
return nil, nil, err
}
// Wait for the context to be done and cancel the evm. Even if the
// EVM has finished, cancelling may be done (repeatedly)
Expand All @@ -879,16 +893,17 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
return nil, nil, err
}
// If the timer caused an abort, return an appropriate error message
if evm.Cancelled() {
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
return nil, nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
}
if err != nil {
return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
return result, nil, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
}
return result, nil
prevState.header.Root = prevState.state.IntermediateRoot(b.ChainConfig().IsEIP158(prevState.header.Number))
return result, prevState, err
}

func newRevertError(result *core.ExecutionResult) *revertError {
Expand Down Expand Up @@ -932,7 +947,7 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
if overrides != nil {
accounts = *overrides
}
result, err := DoCall(ctx, s.b, args, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
result, _, err := DoCall(ctx, s.b, args, nil, blockNrOrHash, accounts, vm.Config{}, 5*time.Second, s.b.RPCGasCap())
if err != nil {
return nil, err
}
Expand All @@ -943,12 +958,13 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args CallArgs, blockNrOr
return result.Return(), result.Err
}

func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *PreviousState, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, *PreviousState, error) {
// Binary search the gas requirement, as it may be higher than the amount used
var (
lo uint64 = params.TxGas - 1
hi uint64
cap uint64
lo uint64 = params.TxGas - 1
hi uint64
cap uint64
stateData *PreviousState
)
// Use zero address if sender unspecified.
if args.From == nil {
Expand All @@ -961,24 +977,24 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
// Retrieve the block to act as the gas ceiling
block, err := b.BlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
return 0, nil, err
}
if block == nil {
return 0, errors.New("block not found")
return 0, nil, errors.New("block not found")
}
hi = block.GasLimit()
}
// Recap the highest gas limit with account's available balance.
if args.GasPrice != nil && args.GasPrice.ToInt().BitLen() != 0 {
state, _, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return 0, err
return 0, nil, err
}
balance := state.GetBalance(*args.From) // from can't be nil
available := new(big.Int).Set(balance)
if args.Value != nil {
if args.Value.ToInt().Cmp(available) >= 0 {
return 0, errors.New("insufficient funds for transfer")
return 0, nil, errors.New("insufficient funds for transfer")
}
available.Sub(available, args.Value.ToInt())
}
Expand Down Expand Up @@ -1006,7 +1022,8 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
args.Gas = (*hexutil.Uint64)(&gas)

result, err := DoCall(ctx, b, args, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
result, prevS, err := DoCall(ctx, b, args, prevState, blockNrOrHash, nil, vm.Config{}, 0, gasCap)
stateData = prevS
if err != nil {
if errors.Is(err, core.ErrIntrinsicGas) {
return true, nil, nil // Special case, raise gas limit
Expand All @@ -1024,7 +1041,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
// call or transaction will never be accepted no matter how much gas it is
// assigned. Return the error directly, don't struggle any more.
if err != nil {
return 0, err
return 0, nil, err
}
if failed {
lo = mid
Expand All @@ -1036,20 +1053,20 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
if hi == cap {
failed, result, err := executable(hi)
if err != nil {
return 0, err
return 0, nil, err
}
if failed {
if result != nil && result.Err != vm.ErrOutOfGas {
if len(result.Revert()) > 0 {
return 0, newRevertError(result)
return 0, nil, newRevertError(result)
}
return 0, result.Err
return 0, nil, result.Err
}
// Otherwise, the specified gas cap is too low
return 0, fmt.Errorf("gas required exceeds allowance (%d)", cap)
return 0, nil, fmt.Errorf("gas required exceeds allowance (%d)", cap)
}
}
return hexutil.Uint64(hi), nil
return hexutil.Uint64(hi), stateData, nil
}

// EstimateGas returns an estimate of the amount of gas needed to execute the
Expand All @@ -1059,7 +1076,30 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, bl
if blockNrOrHash != nil {
bNrOrHash = *blockNrOrHash
}
return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap())
gas, _, err := DoEstimateGas(ctx, s.b, args, nil, bNrOrHash, s.b.RPCGasCap())
return gas, err
}

// EstimateGasList returns an estimate of the amount of gas needed to execute list of
// given transactions against the current pending block.
func (s *PublicBlockChainAPI) EstimateGasList(ctx context.Context, argsList []CallArgs) ([]hexutil.Uint64, error) {
blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
var (
gas hexutil.Uint64
err error
stateData *PreviousState
gasCap = s.b.RPCGasCap()
)
returnVals := make([]hexutil.Uint64, len(argsList))
for idx, args := range argsList {
gas, stateData, err = DoEstimateGas(ctx, s.b, args, stateData, blockNrOrHash, gasCap)
if err != nil {
return nil, err
}
gasCap -= uint64(gas)
returnVals[idx] = gas
}
return returnVals, nil
}

// ExecutionResult groups all structured logs emitted by the EVM
Expand Down Expand Up @@ -1530,7 +1570,7 @@ func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
Data: input,
}
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
estimated, err := DoEstimateGas(ctx, b, callArgs, pendingBlockNr, b.RPCGasCap())
estimated, _, err := DoEstimateGas(ctx, b, callArgs, nil, pendingBlockNr, b.RPCGasCap())
if err != nil {
return err
}
Expand Down

0 comments on commit a701ffa

Please sign in to comment.