Skip to content

Commit 47a90f7

Browse files
s1nalightclient
andcommitted
internal/ethapi: add eth_batchCall method
Co-authored-by: lightclient <[email protected]>
1 parent 13e6985 commit 47a90f7

File tree

12 files changed

+777
-36
lines changed

12 files changed

+777
-36
lines changed

eth/api_backend.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,15 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
213213
return nil
214214
}
215215

216-
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
216+
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) {
217217
if vmConfig == nil {
218218
vmConfig = b.eth.blockchain.GetVMConfig()
219219
}
220220
txContext := core.NewEVMTxContext(msg)
221221
context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
222+
if blockContext != nil {
223+
context = *blockContext
224+
}
222225
return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error, nil
223226
}
224227

eth/tracers/api.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,34 +93,10 @@ func NewAPI(backend Backend) *API {
9393
return &API{backend: backend}
9494
}
9595

96-
type chainContext struct {
97-
api *API
98-
ctx context.Context
99-
}
100-
101-
func (context *chainContext) Engine() consensus.Engine {
102-
return context.api.backend.Engine()
103-
}
104-
105-
func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
106-
header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
107-
if err != nil {
108-
return nil
109-
}
110-
if header.Hash() == hash {
111-
return header
112-
}
113-
header, err = context.api.backend.HeaderByHash(context.ctx, hash)
114-
if err != nil {
115-
return nil
116-
}
117-
return header
118-
}
119-
12096
// chainContext constructs the context reader which is used by the evm for reading
12197
// the necessary chain context.
12298
func (api *API) chainContext(ctx context.Context) core.ChainContext {
123-
return &chainContext{api: api, ctx: ctx}
99+
return ethapi.NewChainContext(ctx, api.backend)
124100
}
125101

126102
// blockByNumber is the wrapper of the chain access function offered by the backend.

eth/tracers/api_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,6 @@ func TestTracingWithOverrides(t *testing.T) {
535535
From: &accounts[0].addr,
536536
// BLOCKNUMBER PUSH1 MSTORE
537537
Input: newRPCBytes(common.Hex2Bytes("4360005260206000f3")),
538-
//&hexutil.Bytes{0x43}, // blocknumber
539538
},
540539
config: &TraceCallConfig{
541540
BlockOverrides: &ethapi.BlockOverrides{Number: (*hexutil.Big)(big.NewInt(0x1337))},

ethclient/ethclient_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
220220
t.Fatalf("can't create new node: %v", err)
221221
}
222222
// Create Ethereum Service
223-
config := &ethconfig.Config{Genesis: genesis}
223+
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 50000000}
224224
config.Ethash.PowMode = ethash.ModeFake
225225
ethservice, err := eth.New(n, config)
226226
if err != nil {

ethclient/gethclient/gethclient_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
5454
t.Fatalf("can't create new node: %v", err)
5555
}
5656
// Create Ethereum Service
57-
config := &ethconfig.Config{Genesis: genesis}
57+
config := &ethconfig.Config{Genesis: genesis, RPCGasCap: 50000000}
5858
config.Ethash.PowMode = ethash.ModeFake
5959
ethservice, err := eth.New(n, config)
6060
if err != nil {

graphql/graphql_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ func newGQLService(t *testing.T, stack *node.Node, gspec *core.Genesis, genBlock
345345
TrieDirtyCache: 5,
346346
TrieTimeout: 60 * time.Minute,
347347
SnapshotCache: 5,
348+
RPCGasCap: 50000000,
348349
}
349350
ethBackend, err := eth.New(stack, ethConf)
350351
if err != nil {

internal/ethapi/api.go

Lines changed: 113 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/ethereum/go-ethereum/common"
3434
"github.com/ethereum/go-ethereum/common/hexutil"
3535
"github.com/ethereum/go-ethereum/common/math"
36+
"github.com/ethereum/go-ethereum/consensus"
3637
"github.com/ethereum/go-ethereum/consensus/ethash"
3738
"github.com/ethereum/go-ethereum/consensus/misc"
3839
"github.com/ethereum/go-ethereum/core"
@@ -946,6 +947,38 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
946947
}
947948
}
948949

950+
// ChainContextBackend provides methods required to implement ChainContext.
951+
type ChainContextBackend interface {
952+
Engine() consensus.Engine
953+
HeaderByNumber(context.Context, rpc.BlockNumber) (*types.Header, error)
954+
}
955+
956+
// ChainContext is an implementation of core.ChainContext. It's main use-case
957+
// is instantiating a vm.BlockContext without having access to the BlockChain object.
958+
type ChainContext struct {
959+
b ChainContextBackend
960+
ctx context.Context
961+
}
962+
963+
// NewChainContext creates a new ChainContext object.
964+
func NewChainContext(ctx context.Context, backend ChainContextBackend) *ChainContext {
965+
return &ChainContext{ctx: ctx, b: backend}
966+
}
967+
968+
func (context *ChainContext) Engine() consensus.Engine {
969+
return context.b.Engine()
970+
}
971+
972+
func (context *ChainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
973+
// This method is called to get the hash for a block number when executing the BLOCKHASH
974+
// opcode. Hence no need to search for non-canonical blocks.
975+
header, err := context.b.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
976+
if err != nil || header.Hash() != hash {
977+
return nil
978+
}
979+
return header
980+
}
981+
949982
func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) {
950983
defer func(start time.Time) { log.Debug("Executing EVM call finished", "runtime", time.Since(start)) }(time.Now())
951984

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

1006+
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) {
9711007
// Get a new instance of the EVM.
972-
msg, err := args.ToMessage(globalGasCap, header.BaseFee)
1008+
msg, err := args.ToMessage(gp.Gas(), header.BaseFee)
9731009
if err != nil {
9741010
return nil, err
9751011
}
976-
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
1012+
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, blockContext)
9771013
if err != nil {
9781014
return nil, err
9791015
}
@@ -985,7 +1021,6 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
9851021
}()
9861022

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

1087+
// BatchCallConfig is the config object to be passed to eth_batchCall.
1088+
type BatchCallConfig struct {
1089+
Block rpc.BlockNumberOrHash
1090+
StateOverrides *StateOverride
1091+
Calls []BatchCallArgs
1092+
}
1093+
1094+
// BatchCallArgs is the object specifying each call within eth_batchCall. It
1095+
// extends TransactionArgs with the list of block metadata overrides.
1096+
type BatchCallArgs struct {
1097+
TransactionArgs
1098+
BlockOverrides *BlockOverrides
1099+
}
1100+
1101+
// CallResult is the result of one call.
1102+
type CallResult struct {
1103+
Return hexutil.Bytes
1104+
Error error
1105+
}
1106+
1107+
// BatchCall executes a series of transactions on the state of a given block as base.
1108+
// The base state can be overridden once before transactions are executed.
1109+
//
1110+
// Additionally, each call can override block context fields such as number.
1111+
//
1112+
// Note, this function doesn't make any changes in the state/blockchain and is
1113+
// useful to execute and retrieve values.
1114+
func (s *BlockChainAPI) BatchCall(ctx context.Context, config BatchCallConfig) ([]CallResult, error) {
1115+
state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, config.Block)
1116+
if state == nil || err != nil {
1117+
return nil, err
1118+
}
1119+
// State overrides are applied once before all calls
1120+
if err := config.StateOverrides.Apply(state); err != nil {
1121+
return nil, err
1122+
}
1123+
// Setup context so it may be cancelled before the calls completed
1124+
// or, in case of unmetered gas, setup a context with a timeout.
1125+
var (
1126+
cancel context.CancelFunc
1127+
timeout = s.b.RPCEVMTimeout()
1128+
)
1129+
if timeout > 0 {
1130+
ctx, cancel = context.WithTimeout(ctx, timeout)
1131+
} else {
1132+
ctx, cancel = context.WithCancel(ctx)
1133+
}
1134+
// Make sure the context is cancelled when the call has completed
1135+
// this makes sure resources are cleaned up.
1136+
defer cancel()
1137+
var (
1138+
results []CallResult
1139+
// Each tx and all the series of txes shouldn't consume more gas than cap
1140+
globalGasCap = s.b.RPCGasCap()
1141+
gp = new(core.GasPool).AddGas(globalGasCap)
1142+
)
1143+
for _, call := range config.Calls {
1144+
blockContext := core.NewEVMBlockContext(header, NewChainContext(ctx, s.b), nil)
1145+
if call.BlockOverrides != nil {
1146+
call.BlockOverrides.Apply(&blockContext)
1147+
}
1148+
result, err := doCall(ctx, s.b, call.TransactionArgs, state, header, timeout, gp, &blockContext)
1149+
if err != nil {
1150+
return nil, err
1151+
}
1152+
// If the result contains a revert reason, try to unpack and return it.
1153+
if len(result.Revert()) > 0 {
1154+
return nil, newRevertError(result)
1155+
}
1156+
results = append(results, CallResult{Return: result.Return(), Error: result.Err})
1157+
}
1158+
return results, nil
1159+
}
1160+
10521161
func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, gasCap uint64) (hexutil.Uint64, error) {
10531162
// Binary search the gas requirement, as it may be higher than the amount used
10541163
var (
@@ -1459,7 +1568,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
14591568
// Apply the transaction with the access list tracer
14601569
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
14611570
config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true}
1462-
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config)
1571+
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config, nil)
14631572
if err != nil {
14641573
return nil, 0, nil, err
14651574
}

0 commit comments

Comments
 (0)