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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
13 changes: 10 additions & 3 deletions api/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,18 @@ func (d *DebugAPI) TraceCall(
return nil, err
}

blocksProvider := replayer.NewBlocksProvider(
blocksProvider := requester.NewBlocksProvider(
d.blocks,
d.config.FlowNetworkID,
tracer,
)
).WithTracer(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 @@ -193,12 +193,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 @@ -231,7 +225,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
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
124 changes: 124 additions & 0 deletions services/requester/blocks_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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 {
*BlocksProvider
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 BlocksProvider 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 BlocksProvider struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add a comment when to use this one and when to use replayer.BlocksProvider

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea 👍 I added descriptions on both implementations in da79cd3 .

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename it to OverridableBlocksProvider to be different from the replayer.BlockProvider, so that it is obvious when OverridableBlocksProvider is being used in a wrong place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea 👍 Renamed the type in 6566fba .

blocks storage.BlockIndexer
chainID flowGo.ChainID
tracer *tracers.Tracer
blockOverrides *ethTypes.BlockOverrides
}

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

func NewBlocksProvider(
blocks storage.BlockIndexer,
chainID flowGo.ChainID,
) *BlocksProvider {
return &BlocksProvider{
blocks: blocks,
chainID: chainID,
}
}

func (bp *BlocksProvider) WithTracer(tracer *tracers.Tracer) *BlocksProvider {
return &BlocksProvider{
blocks: bp.blocks,
chainID: bp.chainID,
tracer: tracer,
blockOverrides: bp.blockOverrides,
}
}

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

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

return &blockSnapshot{
BlocksProvider: bp,
block: *block,
}, nil
}
57 changes: 33 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,21 @@ 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 := NewBlocksProvider(e.blocks, e.config.FlowNetworkID)

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 +475,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 {
m-Peter marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
}
Expand Down Expand Up @@ -592,7 +601,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