Skip to content

Commit 1727f6d

Browse files
authored
Merge pull request #691 from onflow/mpeter/eth-call-block-overrides
Support block overrides parameter for `eth_call` & `debug_traceCall`
2 parents a9c1289 + adee2f3 commit 1727f6d

File tree

9 files changed

+368
-35
lines changed

9 files changed

+368
-35
lines changed

api/api.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,7 +539,7 @@ func (b *BlockChainAPI) Call(
539539
args ethTypes.TransactionArgs,
540540
blockNumberOrHash *rpc.BlockNumberOrHash,
541541
stateOverrides *ethTypes.StateOverride,
542-
_ *ethTypes.BlockOverrides,
542+
blockOverrides *ethTypes.BlockOverrides,
543543
) (hexutil.Bytes, error) {
544544
l := b.logger.With().
545545
Str("endpoint", "call").
@@ -576,7 +576,7 @@ func (b *BlockChainAPI) Call(
576576
from = *args.From
577577
}
578578

579-
res, err := b.evm.Call(tx, from, height, stateOverrides)
579+
res, err := b.evm.Call(tx, from, height, stateOverrides, blockOverrides)
580580
if err != nil {
581581
return handleError[hexutil.Bytes](err, l, b.collector)
582582
}

api/debug.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,20 @@ func (d *DebugAPI) TraceCall(
175175
return nil, err
176176
}
177177

178-
blocksProvider := replayer.NewBlocksProvider(
178+
blocksProvider := requester.NewOverridableBlocksProvider(
179179
d.blocks,
180180
d.config.FlowNetworkID,
181181
tracer,
182182
)
183+
184+
if config.BlockOverrides != nil {
185+
blocksProvider = blocksProvider.WithBlockOverrides(&ethTypes.BlockOverrides{
186+
Number: config.BlockOverrides.Number,
187+
Time: config.BlockOverrides.Time,
188+
Coinbase: config.BlockOverrides.Coinbase,
189+
Random: config.BlockOverrides.Random,
190+
})
191+
}
183192
viewProvider := query.NewViewProvider(
184193
d.config.FlowNetworkID,
185194
flowEVM.StorageAccountAddress(d.config.FlowNetworkID),

bootstrap/bootstrap.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -208,12 +208,6 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
208208
b.config,
209209
)
210210

211-
blocksProvider := replayer.NewBlocksProvider(
212-
b.storages.Blocks,
213-
b.config.FlowNetworkID,
214-
nil,
215-
)
216-
217211
accountKeys := make([]*requester.AccountKey, 0)
218212
if !b.config.IndexOnly {
219213
account, err := b.client.GetAccount(ctx, b.config.COAAddress)
@@ -246,7 +240,6 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
246240

247241
evm, err := requester.NewEVM(
248242
b.storages.Registers,
249-
blocksProvider,
250243
b.client,
251244
b.config,
252245
b.logger,

bootstrap/bootstrap_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ func TestRetryInterceptor(t *testing.T) {
5656
}
5757

5858
for _, tc := range testCases {
59-
tc := tc
6059
t.Run(tc.name, func(t *testing.T) {
6160
t.Parallel()
6261

services/replayer/blocks_provider.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ func (bs *blockSnapshot) BlockContext() (evmTypes.BlockContext, error) {
4141
)
4242
}
4343

44+
// This BlocksProvider implementation is used in the EVM events ingestion pipeline.
45+
// The ingestion module notifies the BlocksProvider of incoming EVM blocks, by
46+
// calling the `OnBlockReceived` method. This method guarantees that blocks are
47+
// processed sequentially, and keeps track of the latest block, which is used
48+
// for generating the proper `BlockContext`. This is necessary for replaying
49+
// EVM blocks/transactions locally, and verifying that there are no state
50+
// mismatches.
4451
type BlocksProvider struct {
4552
blocks storage.BlockIndexer
4653
chainID flowGo.ChainID
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package requester
2+
3+
import (
4+
ethTypes "github.com/onflow/flow-evm-gateway/eth/types"
5+
"github.com/onflow/flow-evm-gateway/models"
6+
"github.com/onflow/flow-evm-gateway/storage"
7+
"github.com/onflow/flow-go/fvm/evm/offchain/blocks"
8+
evmTypes "github.com/onflow/flow-go/fvm/evm/types"
9+
flowGo "github.com/onflow/flow-go/model/flow"
10+
gethCommon "github.com/onflow/go-ethereum/common"
11+
"github.com/onflow/go-ethereum/eth/tracers"
12+
)
13+
14+
type blockSnapshot struct {
15+
*OverridableBlocksProvider
16+
block models.Block
17+
}
18+
19+
var _ evmTypes.BlockSnapshot = (*blockSnapshot)(nil)
20+
21+
func (bs *blockSnapshot) BlockContext() (evmTypes.BlockContext, error) {
22+
blockContext, err := blocks.NewBlockContext(
23+
bs.chainID,
24+
bs.block.Height,
25+
bs.block.Timestamp,
26+
func(n uint64) gethCommon.Hash {
27+
block, err := bs.blocks.GetByHeight(n)
28+
if err != nil {
29+
return gethCommon.Hash{}
30+
}
31+
blockHash, err := block.Hash()
32+
if err != nil {
33+
return gethCommon.Hash{}
34+
}
35+
36+
return blockHash
37+
},
38+
bs.block.PrevRandao,
39+
bs.tracer,
40+
)
41+
if err != nil {
42+
return evmTypes.BlockContext{}, err
43+
}
44+
45+
if bs.blockOverrides == nil {
46+
return blockContext, nil
47+
}
48+
49+
if bs.blockOverrides.Number != nil {
50+
blockContext.BlockNumber = bs.blockOverrides.Number.ToInt().Uint64()
51+
}
52+
53+
if bs.blockOverrides.Time != nil {
54+
blockContext.BlockTimestamp = uint64(*bs.blockOverrides.Time)
55+
}
56+
57+
if bs.blockOverrides.Random != nil {
58+
blockContext.Random = *bs.blockOverrides.Random
59+
}
60+
61+
if bs.blockOverrides.Coinbase != nil {
62+
blockContext.GasFeeCollector = evmTypes.NewAddress(*bs.blockOverrides.Coinbase)
63+
}
64+
65+
return blockContext, nil
66+
}
67+
68+
// This OverridableBlocksProvider implementation is only used for the `eth_call` &
69+
// `debug_traceCall` JSON-RPC endpoints. It accepts optional `Tracer` &
70+
// `BlockOverrides` objects, which are used when constructing the
71+
// `BlockContext` object.
72+
type OverridableBlocksProvider struct {
73+
blocks storage.BlockIndexer
74+
chainID flowGo.ChainID
75+
tracer *tracers.Tracer
76+
blockOverrides *ethTypes.BlockOverrides
77+
}
78+
79+
var _ evmTypes.BlockSnapshotProvider = (*OverridableBlocksProvider)(nil)
80+
81+
func NewOverridableBlocksProvider(
82+
blocks storage.BlockIndexer,
83+
chainID flowGo.ChainID,
84+
tracer *tracers.Tracer,
85+
) *OverridableBlocksProvider {
86+
return &OverridableBlocksProvider{
87+
blocks: blocks,
88+
chainID: chainID,
89+
tracer: tracer,
90+
}
91+
}
92+
93+
func (bp *OverridableBlocksProvider) WithBlockOverrides(
94+
blockOverrides *ethTypes.BlockOverrides,
95+
) *OverridableBlocksProvider {
96+
return &OverridableBlocksProvider{
97+
blocks: bp.blocks,
98+
chainID: bp.chainID,
99+
tracer: bp.tracer,
100+
blockOverrides: blockOverrides,
101+
}
102+
}
103+
104+
func (bp *OverridableBlocksProvider) GetSnapshotAt(height uint64) (
105+
evmTypes.BlockSnapshot,
106+
error,
107+
) {
108+
block, err := bp.blocks.GetByHeight(height)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
return &blockSnapshot{
114+
OverridableBlocksProvider: bp,
115+
block: *block,
116+
}, nil
117+
}

services/requester/requester.go

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import (
2727
"github.com/onflow/flow-evm-gateway/metrics"
2828
"github.com/onflow/flow-evm-gateway/models"
2929
errs "github.com/onflow/flow-evm-gateway/models/errors"
30-
"github.com/onflow/flow-evm-gateway/services/replayer"
3130
"github.com/onflow/flow-evm-gateway/storage"
3231
"github.com/onflow/flow-evm-gateway/storage/pebble"
3332

@@ -66,6 +65,7 @@ type Requester interface {
6665
from common.Address,
6766
height uint64,
6867
stateOverrides *ethTypes.StateOverride,
68+
blockOverrides *ethTypes.BlockOverrides,
6969
) ([]byte, error)
7070

7171
// EstimateGas executes the given signed transaction data on the state for the given EVM block height.
@@ -95,15 +95,15 @@ type Requester interface {
9595
var _ Requester = &EVM{}
9696

9797
type EVM struct {
98-
registerStore *pebble.RegisterStorage
99-
blocksProvider *replayer.BlocksProvider
100-
client *CrossSporkClient
101-
config config.Config
102-
txPool *TxPool
103-
logger zerolog.Logger
104-
blocks storage.BlockIndexer
105-
mux sync.Mutex
106-
keystore *KeyStore
98+
registerStore *pebble.RegisterStorage
99+
client *CrossSporkClient
100+
config config.Config
101+
txPool *TxPool
102+
logger zerolog.Logger
103+
blocks storage.BlockIndexer
104+
mux sync.Mutex
105+
keystore *KeyStore
106+
107107
head *types.Header
108108
evmSigner types.Signer
109109
validationOptions *txpool.ValidationOptions
@@ -112,7 +112,6 @@ type EVM struct {
112112

113113
func NewEVM(
114114
registerStore *pebble.RegisterStorage,
115-
blocksProvider *replayer.BlocksProvider,
116115
client *CrossSporkClient,
117116
config config.Config,
118117
logger zerolog.Logger,
@@ -167,7 +166,6 @@ func NewEVM(
167166

168167
evm := &EVM{
169168
registerStore: registerStore,
170-
blocksProvider: blocksProvider,
171169
client: client,
172170
config: config,
173171
logger: logger,
@@ -250,7 +248,7 @@ func (e *EVM) GetBalance(
250248
address common.Address,
251249
height uint64,
252250
) (*big.Int, error) {
253-
view, err := e.getBlockView(height)
251+
view, err := e.getBlockView(height, nil)
254252
if err != nil {
255253
return nil, err
256254
}
@@ -262,7 +260,7 @@ func (e *EVM) GetNonce(
262260
address common.Address,
263261
height uint64,
264262
) (uint64, error) {
265-
view, err := e.getBlockView(height)
263+
view, err := e.getBlockView(height, nil)
266264
if err != nil {
267265
return 0, err
268266
}
@@ -275,7 +273,7 @@ func (e *EVM) GetStorageAt(
275273
hash common.Hash,
276274
height uint64,
277275
) (common.Hash, error) {
278-
view, err := e.getBlockView(height)
276+
view, err := e.getBlockView(height, nil)
279277
if err != nil {
280278
return common.Hash{}, err
281279
}
@@ -288,8 +286,9 @@ func (e *EVM) Call(
288286
from common.Address,
289287
height uint64,
290288
stateOverrides *ethTypes.StateOverride,
289+
blockOverrides *ethTypes.BlockOverrides,
291290
) ([]byte, error) {
292-
result, err := e.dryRunTx(tx, from, height, stateOverrides)
291+
result, err := e.dryRunTx(tx, from, height, stateOverrides, blockOverrides)
293292
if err != nil {
294293
return nil, err
295294
}
@@ -327,7 +326,7 @@ func (e *EVM) EstimateGas(
327326
tx.Gas = passingGasLimit
328327
// We first execute the transaction at the highest allowable gas limit,
329328
// since if this fails we can return the error immediately.
330-
result, err := e.dryRunTx(tx, from, height, stateOverrides)
329+
result, err := e.dryRunTx(tx, from, height, stateOverrides, nil)
331330
if err != nil {
332331
return 0, err
333332
}
@@ -352,7 +351,7 @@ func (e *EVM) EstimateGas(
352351
optimisticGasLimit := (result.GasConsumed + result.GasRefund + gethParams.CallStipend) * 64 / 63
353352
if optimisticGasLimit < passingGasLimit {
354353
tx.Gas = optimisticGasLimit
355-
result, err = e.dryRunTx(tx, from, height, stateOverrides)
354+
result, err = e.dryRunTx(tx, from, height, stateOverrides, nil)
356355
if err != nil {
357356
// This should not happen under normal conditions since if we make it this far the
358357
// transaction had run without error at least once before.
@@ -382,7 +381,7 @@ func (e *EVM) EstimateGas(
382381
mid = failingGasLimit * 2
383382
}
384383
tx.Gas = mid
385-
result, err = e.dryRunTx(tx, from, height, stateOverrides)
384+
result, err = e.dryRunTx(tx, from, height, stateOverrides, nil)
386385
if err != nil {
387386
return 0, err
388387
}
@@ -405,7 +404,7 @@ func (e *EVM) GetCode(
405404
address common.Address,
406405
height uint64,
407406
) ([]byte, error) {
408-
view, err := e.getBlockView(height)
407+
view, err := e.getBlockView(height, nil)
409408
if err != nil {
410409
return nil, err
411410
}
@@ -437,12 +436,25 @@ func (e *EVM) GetLatestEVMHeight(ctx context.Context) (uint64, error) {
437436
return height, nil
438437
}
439438

440-
func (e *EVM) getBlockView(height uint64) (*query.View, error) {
439+
func (e *EVM) getBlockView(
440+
height uint64,
441+
blockOverrides *ethTypes.BlockOverrides,
442+
) (*query.View, error) {
443+
blocksProvider := NewOverridableBlocksProvider(
444+
e.blocks,
445+
e.config.FlowNetworkID,
446+
nil,
447+
)
448+
449+
if blockOverrides != nil {
450+
blocksProvider = blocksProvider.WithBlockOverrides(blockOverrides)
451+
}
452+
441453
viewProvider := query.NewViewProvider(
442454
e.config.FlowNetworkID,
443455
evm.StorageAccountAddress(e.config.FlowNetworkID),
444456
e.registerStore,
445-
e.blocksProvider,
457+
blocksProvider,
446458
blockGasLimit,
447459
)
448460

@@ -467,8 +479,9 @@ func (e *EVM) dryRunTx(
467479
from common.Address,
468480
height uint64,
469481
stateOverrides *ethTypes.StateOverride,
482+
blockOverrides *ethTypes.BlockOverrides,
470483
) (*evmTypes.Result, error) {
471-
view, err := e.getBlockView(height)
484+
view, err := e.getBlockView(height, blockOverrides)
472485
if err != nil {
473486
return nil, err
474487
}
@@ -592,7 +605,7 @@ func (e *EVM) validateTransactionWithState(
592605
if err != nil {
593606
return err
594607
}
595-
view, err := e.getBlockView(height)
608+
view, err := e.getBlockView(height, nil)
596609
if err != nil {
597610
return err
598611
}

tests/e2e_web3js_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ func TestWeb3_E2E(t *testing.T) {
4040
runWeb3Test(t, "debug_util_test")
4141
})
4242

43+
t.Run("test contract call overrides", func(t *testing.T) {
44+
runWeb3Test(t, "contract_call_overrides_test")
45+
})
46+
4347
t.Run("test setup sanity check", func(t *testing.T) {
4448
runWeb3Test(t, "setup_test")
4549
})

0 commit comments

Comments
 (0)