Skip to content

Commit 0438283

Browse files
s1naholiman
authored andcommitted
internal/ethapi: add block overrides to eth_call (ethereum#26414)
Adds an optional config parameter to eth_call which allows users to override block context fields (same functionality that was added to traceCall in ethereum#24871) --------- Co-authored-by: Martin Holst Swende <[email protected]>
1 parent f9f799b commit 0438283

File tree

11 files changed

+674
-40
lines changed

11 files changed

+674
-40
lines changed

eth/api_backend.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -246,12 +246,17 @@ func (b *EthAPIBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int {
246246
return nil
247247
}
248248

249-
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error) {
249+
func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockCtx *vm.BlockContext) (*vm.EVM, func() error, error) {
250250
if vmConfig == nil {
251251
vmConfig = b.eth.blockchain.GetVMConfig()
252252
}
253253
txContext := core.NewEVMTxContext(msg)
254-
context := core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
254+
var context vm.BlockContext
255+
if blockCtx != nil {
256+
context = *blockCtx
257+
} else {
258+
context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil)
259+
}
255260
return vm.NewEVM(context, txContext, state, b.eth.blockchain.Config(), *vmConfig), state.Error, nil
256261
}
257262

eth/tracers/api.go

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -100,34 +100,10 @@ func NewAPI(backend Backend) *API {
100100
return &API{backend: backend}
101101
}
102102

103-
type chainContext struct {
104-
api *API
105-
ctx context.Context
106-
}
107-
108-
func (context *chainContext) Engine() consensus.Engine {
109-
return context.api.backend.Engine()
110-
}
111-
112-
func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header {
113-
header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number))
114-
if err != nil {
115-
return nil
116-
}
117-
if header.Hash() == hash {
118-
return header
119-
}
120-
header, err = context.api.backend.HeaderByHash(context.ctx, hash)
121-
if err != nil {
122-
return nil
123-
}
124-
return header
125-
}
126-
127103
// chainContext constructs the context reader which is used by the evm for reading
128104
// the necessary chain context.
129105
func (api *API) chainContext(ctx context.Context) core.ChainContext {
130-
return &chainContext{api: api, ctx: ctx}
106+
return ethapi.NewChainContext(ctx, api.backend)
131107
}
132108

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

ethclient/gethclient/gethclient.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,28 @@ func (ec *Client) CallContract(ctx context.Context, msg ethereum.CallMsg, blockN
143143
return hex, err
144144
}
145145

146+
// CallContractWithBlockOverrides executes a message call transaction, which is directly executed
147+
// in the VM of the node, but never mined into the blockchain.
148+
//
149+
// blockNumber selects the block height at which the call runs. It can be nil, in which
150+
// case the code is taken from the latest known block. Note that state from very old
151+
// blocks might not be available.
152+
//
153+
// overrides specifies a map of contract states that should be overwritten before executing
154+
// the message call.
155+
//
156+
// blockOverrides specifies block fields exposed to the EVM that can be overridden for the call.
157+
//
158+
// Please use ethclient.CallContract instead if you don't need the override functionality.
159+
func (ec *Client) CallContractWithBlockOverrides(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount, blockOverrides BlockOverrides) ([]byte, error) {
160+
var hex hexutil.Bytes
161+
err := ec.c.CallContext(
162+
ctx, &hex, "eth_call", toCallArg(msg),
163+
toBlockNumArg(blockNumber), overrides, blockOverrides,
164+
)
165+
return hex, err
166+
}
167+
146168
// GCStats retrieves the current garbage collection stats from a geth node.
147169
func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) {
148170
var result debug.GCStats
@@ -265,3 +287,52 @@ func (a OverrideAccount) MarshalJSON() ([]byte, error) {
265287
}
266288
return json.Marshal(output)
267289
}
290+
291+
// BlockOverrides specifies the set of header fields to override.
292+
type BlockOverrides struct {
293+
// Number overrides the block number.
294+
Number *big.Int
295+
// Difficulty overrides the block difficulty.
296+
Difficulty *big.Int
297+
// Time overrides the block timestamp. Time is applied only when
298+
// it is non-zero.
299+
Time uint64
300+
// GasLimit overrides the block gas limit. GasLimit is applied only when
301+
// it is non-zero.
302+
GasLimit uint64
303+
// Coinbase overrides the block coinbase. Coinbase is applied only when
304+
// it is different from the zero address.
305+
Coinbase common.Address
306+
// Random overrides the block extra data which feeds into the RANDOM opcode.
307+
// Random is applied only when it is a non-zero hash.
308+
Random common.Hash
309+
// BaseFee overrides the block base fee.
310+
BaseFee *big.Int
311+
}
312+
313+
func (o BlockOverrides) MarshalJSON() ([]byte, error) {
314+
type override struct {
315+
Number *hexutil.Big `json:"number,omitempty"`
316+
Difficulty *hexutil.Big `json:"difficulty,omitempty"`
317+
Time hexutil.Uint64 `json:"time,omitempty"`
318+
GasLimit hexutil.Uint64 `json:"gasLimit,omitempty"`
319+
Coinbase *common.Address `json:"coinbase,omitempty"`
320+
Random *common.Hash `json:"random,omitempty"`
321+
BaseFee *hexutil.Big `json:"baseFee,omitempty"`
322+
}
323+
324+
output := override{
325+
Number: (*hexutil.Big)(o.Number),
326+
Difficulty: (*hexutil.Big)(o.Difficulty),
327+
Time: hexutil.Uint64(o.Time),
328+
GasLimit: hexutil.Uint64(o.GasLimit),
329+
BaseFee: (*hexutil.Big)(o.BaseFee),
330+
}
331+
if o.Coinbase != (common.Address{}) {
332+
output.Coinbase = &o.Coinbase
333+
}
334+
if o.Random != (common.Hash{}) {
335+
output.Random = &o.Random
336+
}
337+
return json.Marshal(output)
338+
}

ethclient/gethclient/gethclient_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ func TestGethClient(t *testing.T) {
127127
}, {
128128
"TestCallContract",
129129
func(t *testing.T) { testCallContract(t, client) },
130+
}, {
131+
"TestCallContractWithBlockOverrides",
132+
func(t *testing.T) { testCallContractWithBlockOverrides(t, client) },
130133
},
131134
// The testaccesslist is a bit time-sensitive: the newTestBackend imports
132135
// one block. The `testAcessList` fails if the miner has not yet created a
@@ -413,3 +416,75 @@ func TestOverrideAccountMarshal(t *testing.T) {
413416
t.Error("want:", expected)
414417
}
415418
}
419+
420+
func TestBlockOverridesMarshal(t *testing.T) {
421+
for i, tt := range []struct {
422+
bo BlockOverrides
423+
want string
424+
}{
425+
{
426+
bo: BlockOverrides{},
427+
want: `{}`,
428+
},
429+
{
430+
bo: BlockOverrides{
431+
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
432+
},
433+
want: `{"coinbase":"0x1111111111111111111111111111111111111111"}`,
434+
},
435+
{
436+
bo: BlockOverrides{
437+
Number: big.NewInt(1),
438+
Difficulty: big.NewInt(2),
439+
Time: 3,
440+
GasLimit: 4,
441+
BaseFee: big.NewInt(5),
442+
},
443+
want: `{"number":"0x1","difficulty":"0x2","time":"0x3","gasLimit":"0x4","baseFee":"0x5"}`,
444+
},
445+
} {
446+
marshalled, err := json.Marshal(&tt.bo)
447+
if err != nil {
448+
t.Fatalf("unexpected error: %v", err)
449+
}
450+
if string(marshalled) != tt.want {
451+
t.Errorf("Testcase #%d failed. expected\n%s\ngot\n%s", i, tt.want, string(marshalled))
452+
}
453+
}
454+
}
455+
456+
func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) {
457+
ec := New(client)
458+
msg := ethereum.CallMsg{
459+
From: testAddr,
460+
To: &common.Address{},
461+
Gas: 50000,
462+
GasPrice: big.NewInt(1000000000),
463+
Value: big.NewInt(1),
464+
}
465+
override := OverrideAccount{
466+
// Returns coinbase address.
467+
Code: common.FromHex("0x41806000526014600cf3"),
468+
}
469+
mapAcc := make(map[common.Address]OverrideAccount)
470+
mapAcc[common.Address{}] = override
471+
res, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc)
472+
if err != nil {
473+
t.Fatalf("unexpected error: %v", err)
474+
}
475+
if !bytes.Equal(res, common.FromHex("0x0000000000000000000000000000000000000000")) {
476+
t.Fatalf("unexpected result: %x", res)
477+
}
478+
479+
// Now test with block overrides
480+
bo := BlockOverrides{
481+
Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"),
482+
}
483+
res, err = ec.CallContractWithBlockOverrides(context.Background(), msg, big.NewInt(0), &mapAcc, bo)
484+
if err != nil {
485+
t.Fatalf("unexpected error: %v", err)
486+
}
487+
if !bytes.Equal(res, common.FromHex("0x1111111111111111111111111111111111111111")) {
488+
t.Fatalf("unexpected result: %x", res)
489+
}
490+
}

graphql/graphql.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ func (c *CallResult) Status() hexutil.Uint64 {
10681068
func (b *Block) Call(ctx context.Context, args struct {
10691069
Data ethapi.TransactionArgs
10701070
}) (*CallResult, error) {
1071-
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
1071+
result, err := ethapi.DoCall(ctx, b.r.backend, args.Data, *b.numberOrHash, nil, nil, b.r.backend.RPCEVMTimeout(), b.r.backend.RPCGasCap())
10721072
if err != nil {
10731073
return nil, err
10741074
}
@@ -1131,7 +1131,7 @@ func (p *Pending) Call(ctx context.Context, args struct {
11311131
Data ethapi.TransactionArgs
11321132
}) (*CallResult, error) {
11331133
pendingBlockNr := rpc.BlockNumberOrHashWithNumber(rpc.PendingBlockNumber)
1134-
result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap())
1134+
result, err := ethapi.DoCall(ctx, p.r.backend, args.Data, pendingBlockNr, nil, nil, p.r.backend.RPCEVMTimeout(), p.r.backend.RPCGasCap())
11351135
if err != nil {
11361136
return nil, err
11371137
}

internal/ethapi/api.go

Lines changed: 43 additions & 6 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"
@@ -955,7 +956,39 @@ func (diff *BlockOverrides) Apply(blockCtx *vm.BlockContext) {
955956
}
956957
}
957958

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

961994
state, header, err := b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
@@ -982,7 +1015,11 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
9821015
if err != nil {
9831016
return nil, err
9841017
}
985-
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
1018+
blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil)
1019+
if blockOverrides != nil {
1020+
blockOverrides.Apply(&blockCtx)
1021+
}
1022+
evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}, &blockCtx)
9861023
if err != nil {
9871024
return nil, err
9881025
}
@@ -1046,8 +1083,8 @@ func (e *revertError) ErrorData() interface{} {
10461083
//
10471084
// Note, this function doesn't make and changes in the state/blockchain and is
10481085
// useful to execute and retrieve values.
1049-
func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) {
1050-
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
1086+
func (s *BlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, blockOverrides *BlockOverrides) (hexutil.Bytes, error) {
1087+
result, err := DoCall(ctx, s.b, args, blockNrOrHash, overrides, blockOverrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap())
10511088
if err != nil {
10521089
return nil, err
10531090
}
@@ -1132,7 +1169,7 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr
11321169
executable := func(gas uint64) (bool, *core.ExecutionResult, error) {
11331170
args.Gas = (*hexutil.Uint64)(&gas)
11341171

1135-
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, 0, gasCap)
1172+
result, err := DoCall(ctx, b, args, blockNrOrHash, nil, nil, 0, gasCap)
11361173
if err != nil {
11371174
if errors.Is(err, core.ErrIntrinsicGas) {
11381175
return true, nil, nil // Special case, raise gas limit
@@ -1478,7 +1515,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
14781515
// Apply the transaction with the access list tracer
14791516
tracer := logger.NewAccessListTracer(accessList, args.from(), to, precompiles)
14801517
config := vm.Config{Tracer: tracer, NoBaseFee: true}
1481-
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config)
1518+
vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config, nil)
14821519
if err != nil {
14831520
return nil, 0, nil, err
14841521
}

0 commit comments

Comments
 (0)