Skip to content

Commit

Permalink
internal/ethapi: add eth_batchCall method
Browse files Browse the repository at this point in the history
Co-authored-by: lightclient <14004106+lightclient@users.noreply.github.com>
  • Loading branch information
s1na and lightclient committed Sep 27, 2022
1 parent 13e6985 commit 47a90f7
Show file tree
Hide file tree
Showing 12 changed files with 777 additions and 36 deletions.
5 changes: 4 additions & 1 deletion eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,12 +213,15 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
return nil
}

func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) (*vm.EVM, func() error, error) {
if vmConfig == nil {
vmConfig = b.eth.blockchain.GetVMConfig()
}
txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
if blockContext != nil {
context = *blockContext
}
return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error, nil
}

Expand Down
26 changes: 1 addition & 25 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,34 +93,10 @@ func NewAPI(backend Backend) *API {
return &API{backend: backend}
}

type chainContext struct {
api *API
ctx context.Context
}

func (context *chainContext) Engine() consensus.Engine {
return context.api.backend.Engine()
}

func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
if err != nil {
return nil
}
if header.Hash() == hash {
return header
}
header, err = context.api.backend.HeaderByHash(context.ctx, hash)
if err != nil {
return nil
}
return header
}

// chainContext constructs the context reader which is used by the evm for reading
// the necessary chain context.
func (api *API) chainContext(ctx context.Context) core.ChainContext {
return &chainContext{api: api, ctx: ctx}
return ethapi.NewChainContext(ctx, api.backend)
}

// blockByNumber is the wrapper of the chain access function offered by the backend.
Expand Down
1 change: 0 additions & 1 deletion eth/tracers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,6 @@ func TestTracingWithOverrides(t *testing.T) {
From: &accounts[0].addr,
// BLOCKNUMBER PUSH1 MSTORE
Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")),
//&hexutil.Bytes{0x43}, // blocknumber
},
config: &TraceCallConfig{
BlockOverrides: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},
Expand Down
2 changes: 1 addition & 1 deletion ethclient/ethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
t.Fatalf("can't create new node: %v", err)
}
// Create Ethereum Service
config := &ethconfig.Config{Genesis: genesis}
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 50000000}
config.Ethash.PowMode = ethash.ModeFake
ethservice, err := eth.New(n, config)
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion ethclient/gethclient/gethclient_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
t.Fatalf("can't create new node: %v", err)
}
// Create Ethereum Service
config := &ethconfig.Config{Genesis: genesis}
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 50000000}
config.Ethash.PowMode = ethash.ModeFake
ethservice, err := eth.New(n, config)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions graphql/graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock
TrieDirtyCache: 5,
TrieTimeout: 60 * time.Minute,
SnapshotCache: 5,
RPCGasCap: 50000000,
}
ethBackend, err := eth.New(stack, ethConf)
if err != nil {
Expand Down
117 changes: 113 additions & 4 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core"
Expand Down Expand Up @@ -946,6 +947,38 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
}
}

// ChainContextBackend provides methods required to implement ChainContext.
type ChainContextBackend interface {
Engine() consensus.Engine
HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error)
}

// ChainContext is an implementation of core.ChainContext. It's main use-case
// is instantiating a vm.BlockContext without having access to the BlockChain object.
type ChainContext struct {
b ChainContextBackend
ctx context.Context
}

// NewChainContext creates a new ChainContext object.
func NewChainContext(ctx context.Context, backend ChainContextBackend) *ChainContext {
return &ChainContext{ctx: ctx, b: backend}
}

func (context *ChainContext) Engine() consensus.Engine {
return context.b.Engine()
}

func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
// This method is called to get the hash for a block number when executing the BLOCKHASH
// opcode. Hence no need to search for non-canonical blocks.
header, err := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
if err != nil || header.Hash() != hash {
return nil
}
return header
}

func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, 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())

Expand All @@ -967,13 +1000,16 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
return doCall(ctx, b, args, state, header, timeout, new(core.GasPool).AddGas(globalGasCap), nil)
}

func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.StateDB, header *types.Header, timeout time.Duration, gp *core.GasPool, blockContext *vm.BlockContext) (*core.ExecutionResult, error) {
// Get a new instance of the EVM.
msg, err := args.ToMessage(globalGasCap, header.BaseFee)
msg, err := args.ToMessage(gp.Gas(), header.BaseFee)
if err != nil {
return nil, err
}
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, blockContext)
if err != nil {
return nil, err
}
Expand All @@ -985,7 +1021,6 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
}()

// Execute the message.
gp := new(core.GasPool).AddGas(math.MaxUint64)
result, err := core.ApplyMessage(evm, msg, gp)
if err := vmError(); err != nil {
return nil, err
Expand Down Expand Up @@ -1049,6 +1084,80 @@ func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrO
return result.Return(), result.Err
}

// BatchCallConfig is the config object to be passed to eth_batchCall.
type BatchCallConfig struct {
Block rpc.BlockNumberOrHash
StateOverrides *StateOverride
Calls []BatchCallArgs
}

// BatchCallArgs is the object specifying each call within eth_batchCall. It
// extends TransactionArgs with the list of block metadata overrides.
type BatchCallArgs struct {
TransactionArgs
BlockOverrides *BlockOverrides
}

// CallResult is the result of one call.
type CallResult struct {
Return hexutil.Bytes
Error error
}

// BatchCall executes a series of transactions on the state of a given block as base.
// The base state can be overridden once before transactions are executed.
//
// Additionally, each call can override block context fields such as number.
//
// Note, this function doesn't make any changes in the state/blockchain and is
// useful to execute and retrieve values.
func (s *BlockChainAPI) BatchCall(ctx context.Context, config BatchCallConfig) ([]CallResult, error) {
state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, config.Block)
if state == nil || err != nil {
return nil, err
}
// State overrides are applied once before all calls
if err := config.StateOverrides.Apply(state); err != nil {
return nil, err
}
// Setup context so it may be cancelled before the calls completed
// or, in case of unmetered gas, setup a context with a timeout.
var (
cancel context.CancelFunc
timeout = s.b.RPCEVMTimeout()
)
if timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, timeout)
} else {
ctx, cancel = context.WithCancel(ctx)
}
// Make sure the context is cancelled when the call has completed
// this makes sure resources are cleaned up.
defer cancel()
var (
results []CallResult
// Each tx and all the series of txes shouldn't consume more gas than cap
globalGasCap = s.b.RPCGasCap()
gp = new(core.GasPool).AddGas(globalGasCap)
)
for _, call := range config.Calls {
blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil)
if call.BlockOverrides != nil {
call.BlockOverrides.Apply(&blockContext)
}
result, err := doCall(ctx, s.b, call.TransactionArgs, state, header, timeout, gp, &blockContext)
if err != nil {
return nil, err
}
// If the result contains a revert reason, try to unpack and return it.
if len(result.Revert()) > 0 {
return nil, newRevertError(result)
}
results = append(results, CallResult{Return: result.Return(), Error: result.Err})
}
return results, nil
}

func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
// Binary search the gas requirement, as it may be higher than the amount used
var (
Expand Down Expand Up @@ -1459,7 +1568,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
// Apply the transaction with the access list tracer
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true}
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config)
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config, nil)
if err != nil {
return nil, 0, nil, err
}
Expand Down
Loading

0 comments on commit 47a90f7

Please sign in to comment.