Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support block overrides parameter for eth_call & debug_traceCall #691

Merged
merged 5 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ func (b *BlockChainAPI) Call(
args ethTypes.TransactionArgs,
blockNumberOrHash *rpc.BlockNumberOrHash,
stateOverrides *ethTypes.StateOverride,
_ *ethTypes.BlockOverrides,
blockOverrides *ethTypes.BlockOverrides,
) (hexutil.Bytes, error) {
l := b.logger.With().
Str("endpoint", "call").
Expand Down Expand Up @@ -576,7 +576,7 @@ func (b *BlockChainAPI) Call(
from = *args.From
}

res, err := b.evm.Call(tx, from, height, stateOverrides)
res, err := b.evm.Call(tx, from, height, stateOverrides, blockOverrides)
if err != nil {
return handleError[hexutil.Bytes](err, l, b.collector)
}
Expand Down
11 changes: 10 additions & 1 deletion api/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,20 @@ func (d *DebugAPI) TraceCall(
return nil, err
}

blocksProvider := replayer.NewBlocksProvider(
blocksProvider := requester.NewOverridableBlocksProvider(
d.blocks,
d.config.FlowNetworkID,
tracer,
)

if config.BlockOverrides != nil {
blocksProvider = blocksProvider.WithBlockOverrides(&ethTypes.BlockOverrides{
Number: config.BlockOverrides.Number,
Time: config.BlockOverrides.Time,
Coinbase: config.BlockOverrides.Coinbase,
Random: config.BlockOverrides.Random,
})
}
viewProvider := query.NewViewProvider(
d.config.FlowNetworkID,
flowEVM.StorageAccountAddress(d.config.FlowNetworkID),
Expand Down
7 changes: 0 additions & 7 deletions bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,6 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {
b.config,
)

blocksProvider := replayer.NewBlocksProvider(
b.storages.Blocks,
b.config.FlowNetworkID,
nil,
)

accountKeys := make([]*requester.AccountKey, 0)
if !b.config.IndexOnly {
account, err := b.client.GetAccount(ctx, b.config.COAAddress)
Expand Down Expand Up @@ -246,7 +240,6 @@ func (b *Bootstrap) StartAPIServer(ctx context.Context) error {

evm, err := requester.NewEVM(
b.storages.Registers,
blocksProvider,
b.client,
b.config,
b.logger,
Expand Down
1 change: 0 additions & 1 deletion bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@ func TestRetryInterceptor(t *testing.T) {
}

for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

Expand Down
7 changes: 7 additions & 0 deletions services/replayer/blocks_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ func (bs *blockSnapshot) BlockContext() (evmTypes.BlockContext, error) {
)
}

// This BlocksProvider implementation is used in the EVM events ingestion pipeline.
// The ingestion module notifies the BlocksProvider of incoming EVM blocks, by
// calling the `OnBlockReceived` method. This method guarantees that blocks are
// processed sequentially, and keeps track of the latest block, which is used
// for generating the proper `BlockContext`. This is necessary for replaying
// EVM blocks/transactions locally, and verifying that there are no state
// mismatches.
type BlocksProvider struct {
blocks storage.BlockIndexer
chainID flowGo.ChainID
Expand Down
117 changes: 117 additions & 0 deletions services/requester/overridable_blocks_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package requester

import (
ethTypes "github.com/onflow/flow-evm-gateway/eth/types"
"github.com/onflow/flow-evm-gateway/models"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-go/fvm/evm/offchain/blocks"
evmTypes "github.com/onflow/flow-go/fvm/evm/types"
flowGo "github.com/onflow/flow-go/model/flow"
gethCommon "github.com/onflow/go-ethereum/common"
"github.com/onflow/go-ethereum/eth/tracers"
)

type blockSnapshot struct {
*OverridableBlocksProvider
block models.Block
}

var _ evmTypes.BlockSnapshot = (*blockSnapshot)(nil)

func (bs *blockSnapshot) BlockContext() (evmTypes.BlockContext, error) {
blockContext, err := blocks.NewBlockContext(
bs.chainID,
bs.block.Height,
bs.block.Timestamp,
func(n uint64) gethCommon.Hash {
block, err := bs.blocks.GetByHeight(n)
if err != nil {
return gethCommon.Hash{}
}
blockHash, err := block.Hash()
if err != nil {
return gethCommon.Hash{}
}

return blockHash
},
bs.block.PrevRandao,
bs.tracer,
)
if err != nil {
return evmTypes.BlockContext{}, err
}

if bs.blockOverrides == nil {
return blockContext, nil
}

if bs.blockOverrides.Number != nil {
blockContext.BlockNumber = bs.blockOverrides.Number.ToInt().Uint64()
}

if bs.blockOverrides.Time != nil {
blockContext.BlockTimestamp = uint64(*bs.blockOverrides.Time)
}

if bs.blockOverrides.Random != nil {
blockContext.Random = *bs.blockOverrides.Random
}

if bs.blockOverrides.Coinbase != nil {
blockContext.GasFeeCollector = evmTypes.NewAddress(*bs.blockOverrides.Coinbase)
}

return blockContext, nil
}

// This OverridableBlocksProvider implementation is only used for the `eth_call` &
// `debug_traceCall` JSON-RPC endpoints. It accepts optional `Tracer` &
// `BlockOverrides` objects, which are used when constructing the
// `BlockContext` object.
type OverridableBlocksProvider struct {
blocks storage.BlockIndexer
chainID flowGo.ChainID
tracer *tracers.Tracer
blockOverrides *ethTypes.BlockOverrides
}

var _ evmTypes.BlockSnapshotProvider = (*OverridableBlocksProvider)(nil)

func NewOverridableBlocksProvider(
blocks storage.BlockIndexer,
chainID flowGo.ChainID,
tracer *tracers.Tracer,
) *OverridableBlocksProvider {
return &OverridableBlocksProvider{
blocks: blocks,
chainID: chainID,
tracer: tracer,
}
}

func (bp *OverridableBlocksProvider) WithBlockOverrides(
blockOverrides *ethTypes.BlockOverrides,
) *OverridableBlocksProvider {
return &OverridableBlocksProvider{
blocks: bp.blocks,
chainID: bp.chainID,
tracer: bp.tracer,
blockOverrides: blockOverrides,
}
}

func (bp *OverridableBlocksProvider) GetSnapshotAt(height uint64) (
evmTypes.BlockSnapshot,
error,
) {
block, err := bp.blocks.GetByHeight(height)
if err != nil {
return nil, err
}

return &blockSnapshot{
OverridableBlocksProvider: bp,
block: *block,
}, nil
}
61 changes: 37 additions & 24 deletions services/requester/requester.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
"github.com/onflow/flow-evm-gateway/metrics"
"github.com/onflow/flow-evm-gateway/models"
errs "github.com/onflow/flow-evm-gateway/models/errors"
"github.com/onflow/flow-evm-gateway/services/replayer"
"github.com/onflow/flow-evm-gateway/storage"
"github.com/onflow/flow-evm-gateway/storage/pebble"

Expand Down Expand Up @@ -66,6 +65,7 @@ type Requester interface {
from common.Address,
height uint64,
stateOverrides *ethTypes.StateOverride,
blockOverrides *ethTypes.BlockOverrides,
) ([]byte, error)

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

type EVM struct {
registerStore *pebble.RegisterStorage
blocksProvider *replayer.BlocksProvider
client *CrossSporkClient
config config.Config
txPool *TxPool
logger zerolog.Logger
blocks storage.BlockIndexer
mux sync.Mutex
keystore *KeyStore
registerStore *pebble.RegisterStorage
client *CrossSporkClient
config config.Config
txPool *TxPool
logger zerolog.Logger
blocks storage.BlockIndexer
mux sync.Mutex
keystore *KeyStore

head *types.Header
evmSigner types.Signer
validationOptions *txpool.ValidationOptions
Expand All @@ -112,7 +112,6 @@ type EVM struct {

func NewEVM(
registerStore *pebble.RegisterStorage,
blocksProvider *replayer.BlocksProvider,
client *CrossSporkClient,
config config.Config,
logger zerolog.Logger,
Expand Down Expand Up @@ -167,7 +166,6 @@ func NewEVM(

evm := &EVM{
registerStore: registerStore,
blocksProvider: blocksProvider,
client: client,
config: config,
logger: logger,
Expand Down Expand Up @@ -250,7 +248,7 @@ func (e *EVM) GetBalance(
address common.Address,
height uint64,
) (*big.Int, error) {
view, err := e.getBlockView(height)
view, err := e.getBlockView(height, nil)
if err != nil {
return nil, err
}
Expand All @@ -262,7 +260,7 @@ func (e *EVM) GetNonce(
address common.Address,
height uint64,
) (uint64, error) {
view, err := e.getBlockView(height)
view, err := e.getBlockView(height, nil)
if err != nil {
return 0, err
}
Expand All @@ -275,7 +273,7 @@ func (e *EVM) GetStorageAt(
hash common.Hash,
height uint64,
) (common.Hash, error) {
view, err := e.getBlockView(height)
view, err := e.getBlockView(height, nil)
if err != nil {
return common.Hash{}, err
}
Expand All @@ -288,8 +286,9 @@ func (e *EVM) Call(
from common.Address,
height uint64,
stateOverrides *ethTypes.StateOverride,
blockOverrides *ethTypes.BlockOverrides,
) ([]byte, error) {
result, err := e.dryRunTx(tx, from, height, stateOverrides)
result, err := e.dryRunTx(tx, from, height, stateOverrides, blockOverrides)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -327,7 +326,7 @@ func (e *EVM) EstimateGas(
tx.Gas = passingGasLimit
// We first execute the transaction at the highest allowable gas limit,
// since if this fails we can return the error immediately.
result, err := e.dryRunTx(tx, from, height, stateOverrides)
result, err := e.dryRunTx(tx, from, height, stateOverrides, nil)
if err != nil {
return 0, err
}
Expand All @@ -352,7 +351,7 @@ func (e *EVM) EstimateGas(
optimisticGasLimit := (result.GasConsumed + result.GasRefund + gethParams.CallStipend) * 64 / 63
if optimisticGasLimit < passingGasLimit {
tx.Gas = optimisticGasLimit
result, err = e.dryRunTx(tx, from, height, stateOverrides)
result, err = e.dryRunTx(tx, from, height, stateOverrides, nil)
if err != nil {
// This should not happen under normal conditions since if we make it this far the
// transaction had run without error at least once before.
Expand Down Expand Up @@ -382,7 +381,7 @@ func (e *EVM) EstimateGas(
mid = failingGasLimit * 2
}
tx.Gas = mid
result, err = e.dryRunTx(tx, from, height, stateOverrides)
result, err = e.dryRunTx(tx, from, height, stateOverrides, nil)
if err != nil {
return 0, err
}
Expand All @@ -405,7 +404,7 @@ func (e *EVM) GetCode(
address common.Address,
height uint64,
) ([]byte, error) {
view, err := e.getBlockView(height)
view, err := e.getBlockView(height, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -437,12 +436,25 @@ func (e *EVM) GetLatestEVMHeight(ctx context.Context) (uint64, error) {
return height, nil
}

func (e *EVM) getBlockView(height uint64) (*query.View, error) {
func (e *EVM) getBlockView(
height uint64,
blockOverrides *ethTypes.BlockOverrides,
) (*query.View, error) {
blocksProvider := NewOverridableBlocksProvider(
e.blocks,
e.config.FlowNetworkID,
nil,
)

if blockOverrides != nil {
blocksProvider = blocksProvider.WithBlockOverrides(blockOverrides)
}

viewProvider := query.NewViewProvider(
e.config.FlowNetworkID,
evm.StorageAccountAddress(e.config.FlowNetworkID),
e.registerStore,
e.blocksProvider,
blocksProvider,
blockGasLimit,
)

Expand All @@ -467,8 +479,9 @@ func (e *EVM) dryRunTx(
from common.Address,
height uint64,
stateOverrides *ethTypes.StateOverride,
blockOverrides *ethTypes.BlockOverrides,
) (*evmTypes.Result, error) {
view, err := e.getBlockView(height)
view, err := e.getBlockView(height, blockOverrides)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -592,7 +605,7 @@ func (e *EVM) validateTransactionWithState(
if err != nil {
return err
}
view, err := e.getBlockView(height)
view, err := e.getBlockView(height, nil)
if err != nil {
return err
}
Expand Down
4 changes: 4 additions & 0 deletions tests/e2e_web3js_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func TestWeb3_E2E(t *testing.T) {
runWeb3Test(t, "debug_util_test")
})

t.Run("test contract call overrides", func(t *testing.T) {
runWeb3Test(t, "contract_call_overrides_test")
})

t.Run("test setup sanity check", func(t *testing.T) {
runWeb3Test(t, "setup_test")
})
Expand Down
Loading
Loading