Skip to content
Closed
12 changes: 6 additions & 6 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,7 +994,7 @@ func TestLogRebirth(t *testing.T) {
genesis = gspec.MustCommit(db)
signer = types.NewEIP155Signer(gspec.Config.ChainID)
engine = ethash.NewFaker()
blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil, nil)
)

defer blockchain.Stop()
Expand Down Expand Up @@ -1057,7 +1057,7 @@ func TestSideLogRebirth(t *testing.T) {
gspec = &Genesis{Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(10000000000000)}}}
genesis = gspec.MustCommit(db)
signer = types.NewEIP155Signer(gspec.Config.ChainID)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil)
blockchain, _ = NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil, nil)
)

defer blockchain.Stop()
Expand Down Expand Up @@ -2615,7 +2615,7 @@ func TestDeleteRecreateSlots(t *testing.T) {
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
Debug: true,
Tracer: vm.NewJSONLogger(nil, os.Stdout),
}, nil)
}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
Expand Down Expand Up @@ -2695,7 +2695,7 @@ func TestDeleteRecreateAccount(t *testing.T) {
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
Debug: true,
Tracer: vm.NewJSONLogger(nil, os.Stdout),
}, nil)
}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
Expand Down Expand Up @@ -2868,7 +2868,7 @@ func TestDeleteRecreateSlotsAcrossManyBlocks(t *testing.T) {
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
//Debug: true,
//Tracer: vm.NewJSONLogger(nil, os.Stdout),
}, nil)
}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
Expand Down Expand Up @@ -3002,7 +3002,7 @@ func TestInitThenFailCreateContract(t *testing.T) {
chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{
//Debug: true,
//Tracer: vm.NewJSONLogger(nil, os.Stdout),
}, nil)
}, nil, nil)
if err != nil {
t.Fatalf("failed to create tester chain: %v", err)
}
Expand Down
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 @@ -827,7 +827,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 @@ -872,7 +872,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 @@ -891,7 +891,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
100 changes: 66 additions & 34 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package ethapi
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"math/big"
Expand All @@ -37,6 +38,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"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 @@ -790,12 +792,15 @@ 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 *big.Int) (*core.ExecutionResult, error) {
func DoCall(ctx context.Context, b Backend, args CallArgs, prevState *state.StateDB, blockNrOrHash rpc.BlockNumberOrHash, overrides map[common.Address]account, vmCfg vm.Config, timeout time.Duration, globalGasCap *big.Int) (*core.ExecutionResult, *state.StateDB, error) {
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())

state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
return nil, nil, err
}
if prevState != nil {
state = prevState.Copy()
header.Root = state.IntermediateRoot(b.ChainConfig().IsEIP158(header.Number))
}
// Override the fields of specified contracts before execution.
for addr, account := range overrides {
Expand All @@ -812,7 +817,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
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 {
Expand Down Expand Up @@ -841,7 +846,7 @@ func DoCall(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.Blo
msg := args.ToMessage(globalGasCap)
evm, vmError, err := b.GetEVM(ctx, msg, state, 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 @@ -855,13 +860,13 @@ 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)
}
return result, err
return result, state, err
}

// Call executes the given transaction on the state for the given block number.
Expand All @@ -875,7 +880,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 @@ -899,12 +904,13 @@ func (e estimateGasError) Error() string {
return errMsg
}

func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, error) {
func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, prevState *state.StateDB, blockNrOrHash rpc.BlockNumberOrHash, gasCap *big.Int) (hexutil.Uint64, *state.StateDB, 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
validState *state.StateDB
)
// Use zero address if sender unspecified.
if args.From == nil {
Expand All @@ -917,21 +923,21 @@ 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
}
hi = block.GasLimit()
}
// Recap the highest gas limit with account's available balance.
if args.GasPrice != nil && args.GasPrice.ToInt().Uint64() != 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 All @@ -954,40 +960,40 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
cap = hi

// Create a helper to check if a gas allowance results in an executable transaction
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
executable := func(gas uint64) (bool, *core.ExecutionResult, *state.StateDB, 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)
if err != nil {
if err == core.ErrIntrinsicGas {
return true, nil, nil // Special case, raise gas limit
return true, nil, nil, nil // Special case, raise gas limit
}
return true, nil, err // Bail out
return true, nil, nil, err // Bail out
}
return result.Failed(), result, nil
return result.Failed(), result, prevS, nil
}
// Execute the binary search and hone in on an executable gas limit
for lo+1 < hi {
mid := (hi + lo) / 2
failed, _, err := executable(mid)
failed, _, prevS, err := executable(mid)

// If the error is not nil(consensus error), it means the provided message
// call or transaction will never be accepted no matter how much gas it is
// assigened. Return the error directly, don't struggle any more.
if err != nil {
return 0, err
return 0, nil, err
}
if failed {
lo = mid
} else {
validState = prevS
hi = mid
}
}
// Reject the transaction as invalid if it still fails at the highest allowance
if hi == cap {
failed, result, err := executable(hi)
failed, result, prevS, err := executable(hi)
if err != nil {
return 0, err
return 0, nil, err
}
if failed {
if result != nil && result.Err != vm.ErrOutOfGas {
Expand All @@ -1000,24 +1006,50 @@ func DoEstimateGas(ctx context.Context, b Backend, args CallArgs, blockNrOrHash
revert = ret
}
}
return 0, estimateGasError{
return 0, nil, estimateGasError{
error: "always failing transaction",
vmerr: result.Err,
revert: revert,
}
}
// Otherwise, the specified gas cap is too low
return 0, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)}
return 0, nil, estimateGasError{error: fmt.Sprintf("gas required exceeds allowance (%d)", cap)}
}
validState = prevS
}
return hexutil.Uint64(hi), nil
return hexutil.Uint64(hi), validState, nil
}

// 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.Uint64, error) {
func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, argsInterface interface{}) (interface{}, error) {
blockNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
return DoEstimateGas(ctx, s.b, args, blockNrOrHash, s.b.RPCGasCap())
var (
gas hexutil.Uint64
err error
prevState *state.StateDB
)
getCallArgs := func(inter map[string]interface{}) CallArgs {
marshalled, _ := json.Marshal(inter)
callArgs := CallArgs{}
json.Unmarshal(marshalled, &callArgs)
return callArgs
}
switch args := argsInterface.(type) {
case map[string]interface{}:
gas, _, err = DoEstimateGas(ctx, s.b, getCallArgs(args), nil, blockNrOrHash, s.b.RPCGasCap())
return gas, err
case []interface{}:
returnVals := make([]hexutil.Uint64, len(args))
for idx, argData := range args {
gas, prevState, err = DoEstimateGas(ctx, s.b, getCallArgs(argData.(map[string]interface{})), prevState, blockNrOrHash, s.b.RPCGasCap())
if err != nil {
return nil, err
}
returnVals[idx] = gas
}
return returnVals, nil
default:
return nil, estimateGasError{error: "unknown input"}
}
}

// ExecutionResult groups all structured logs emitted by the EVM
Expand Down Expand Up @@ -1488,7 +1520,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