Skip to content
Open
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
45 changes: 27 additions & 18 deletions evmrpc/simulate.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,11 @@ func NewBackend(
}

func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (vm.StateDB, *ethtypes.Header, error) {
height, isLatestBlock, err := b.getBlockHeight(ctx, blockNrOrHash)
tmBlock, isLatestBlock, err := b.getBlockByNumberOrHash(ctx, blockNrOrHash)
if err != nil {
return nil, nil, err
}
height := tmBlock.Block.Height
isWasmdCall, ok := ctx.Value(CtxIsWasmdPrecompileCallKey).(bool)
sdkCtx := b.ctxProvider(height).WithIsEVM(true).WithEVMEntryViaWasmdPrecompile(ok && isWasmdCall)
if !isLatestBlock {
Expand All @@ -273,7 +274,7 @@ func (b *Backend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHas
return nil, nil, err
}
}
header := b.getHeader(big.NewInt(height))
header := b.getHeader(ctx, big.NewInt(height), tmBlock)
header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt()
return state.NewDBImpl(sdkCtx, b.keeper, true), header, nil
}
Expand Down Expand Up @@ -388,7 +389,7 @@ func (b Backend) BlockByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtyp
})
}
}
header := b.getHeader(big.NewInt(blockNum))
header := b.getHeader(ctx, big.NewInt(blockNum), tmBlock)
block := &ethtypes.Block{
Header_: header,
Txs: txs,
Expand Down Expand Up @@ -433,11 +434,12 @@ func (b *Backend) Engine() consensus.Engine {
}

func (b *Backend) HeaderByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtypes.Header, error) {
height, _, err := b.getBlockHeight(ctx, rpc.BlockNumberOrHashWithNumber(bn))
tmBlock, _, err := b.getBlockByNumberOrHash(ctx, rpc.BlockNumberOrHashWithNumber(bn))
if err != nil {
return nil, err
}
return b.getHeader(big.NewInt(height)), nil
height := tmBlock.Block.Height
return b.getHeader(ctx, big.NewInt(height), tmBlock), nil
}

func (b *Backend) StateAtTransaction(ctx context.Context, block *ethtypes.Block, txIndex int, reexec uint64) (*ethtypes.Transaction, vm.BlockContext, vm.StateDB, tracers.StateReleaseFunc, error) {
Expand Down Expand Up @@ -552,7 +554,7 @@ func (b *Backend) GetEVM(_ context.Context, msg *core.Message, stateDB vm.StateD
}

func (b *Backend) CurrentHeader() *ethtypes.Header {
header := b.getHeader(big.NewInt(b.ctxProvider(LatestCtxHeight).BlockHeight()))
header := b.getHeader(context.Background(), big.NewInt(b.ctxProvider(LatestCtxHeight).BlockHeight()), nil)
header.BaseFee = b.keeper.GetNextBaseFeePerGas(b.ctxProvider(LatestCtxHeight)).TruncateInt().BigInt()
return header
}
Expand All @@ -561,7 +563,9 @@ func (b *Backend) SuggestGasTipCap(context.Context) (*big.Int, error) {
return utils.Big0, nil
}

func (b *Backend) getBlockHeight(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (int64, bool, error) {
// getBlockByNumberOrHash resolves blockNrOrHash to a Tendermint ResultBlock in one RPC path
// (by hash or by number, including latest). Callers can pass tmBlock to getHeader to avoid a second block fetch.
func (b *Backend) getBlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*coretypes.ResultBlock, bool, error) {
var (
block *coretypes.ResultBlock
err error
Expand All @@ -571,16 +575,16 @@ func (b *Backend) getBlockHeight(ctx context.Context, blockNrOrHash rpc.BlockNum
if blockNrOrHash.BlockHash != nil {
block, err = blockByHashRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNrOrHash.BlockHash[:], 1)
if err != nil {
return 0, false, err
return nil, false, err
}
return block.Block.Height, false, nil
return block, false, nil
}

var blockNumberPtr *int64
if blockNrOrHash.BlockNumber != nil {
blockNumberPtr, err = getBlockNumber(ctx, b.tmClient, *blockNrOrHash.BlockNumber)
if err != nil {
return 0, false, err
return nil, false, err
}
if blockNumberPtr == nil {
isLatestBlock = true
Expand All @@ -590,25 +594,30 @@ func (b *Backend) getBlockHeight(ctx context.Context, blockNrOrHash rpc.BlockNum
}
block, err = blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, blockNumberPtr, 1)
if err != nil {
return 0, false, err
return nil, false, err
}
return block.Block.Height, isLatestBlock, nil
return block, isLatestBlock, nil
}

func (b *Backend) getHeader(blockNumber *big.Int) *ethtypes.Header {
func (b *Backend) getHeader(ctx context.Context, blockNumber *big.Int, tmBlock *coretypes.ResultBlock) *ethtypes.Header {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Similarly, if this function takes block there is no need to also pass in the hight, right? Since it can get what it needs from the block itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe the reason to have both blockNumber and tmBlock is in some methods that use the getHeader, the tmBlock is not available and only we have the blockNumber. For example in method CurrentHeader().

zeroExcessBlobGas := uint64(0)
baseFee := b.keeper.GetNextBaseFeePerGas(b.ctxProvider(blockNumber.Int64() - 1)).TruncateInt().BigInt()
ctx := b.ctxProvider(blockNumber.Int64())
if ctx.ChainID() == "pacific-1" && ctx.BlockHeight() < b.keeper.UpgradeKeeper().GetDoneHeight(ctx.WithGasMeter(sdk.NewInfiniteGasMeter(1, 1)), "6.2.0") {
sdkCtx := b.ctxProvider(blockNumber.Int64())
if sdkCtx.ChainID() == "pacific-1" && sdkCtx.BlockHeight() < b.keeper.UpgradeKeeper().GetDoneHeight(sdkCtx.WithGasMeter(sdk.NewInfiniteGasMeter(1, 1)), "6.2.0") {
baseFee = nil
}
// Get block results to access consensus parameters
number := blockNumber.Int64()
block, blockErr := blockByNumberRespectingWatermarks(context.Background(), b.tmClient, b.watermarks, &number, 1)
var block *coretypes.ResultBlock
var blockErr error
if tmBlock != nil {
block = tmBlock
} else {
block, blockErr = blockByNumberRespectingWatermarks(ctx, b.tmClient, b.watermarks, &number, 1)
}
var gasLimit uint64
if blockErr == nil {
// Try to get consensus parameters from block results
blockRes, blockResErr := blockResultsWithRetry(context.Background(), b.tmClient, &number)
blockRes, blockResErr := blockResultsWithRetry(ctx, b.tmClient, &number)
if blockResErr == nil && blockRes.ConsensusParamUpdates != nil && blockRes.ConsensusParamUpdates.Block != nil {
gasLimit = uint64(blockRes.ConsensusParamUpdates.Block.MaxGas) //nolint:gosec
} else {
Expand Down
66 changes: 50 additions & 16 deletions evmrpc/simulate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/export"
Expand All @@ -28,18 +29,13 @@ import (
"github.com/stretchr/testify/require"
)

// brFailClient fails BlockResults; bcFailClient fails Block
// brFailClient fails BlockResults
type brFailClient struct{ *MockClient }

func (br brFailClient) BlockResults(ctx context.Context, h *int64) (*coretypes.ResultBlockResults, error) {
return nil, fmt.Errorf("fail br")
}

type bcFailClient struct {
*MockClient
first bool
}

func primeReceiptStore(t *testing.T, store receipt.ReceiptStore, latest int64) {
t.Helper()
if store == nil {
Expand All @@ -52,11 +48,10 @@ func primeReceiptStore(t *testing.T, store receipt.ReceiptStore, latest int64) {
require.NoError(t, store.SetEarliestVersion(1))
}

func (bc *bcFailClient) Block(ctx context.Context, h *int64) (*coretypes.ResultBlock, error) {
if !bc.first {
bc.first = true
return bc.MockClient.Block(ctx, h)
}
// bcAlwaysFailClient fails every Block call (header resolution uses a single block fetch).
type bcAlwaysFailClient struct{ *MockClient }

func (bc bcAlwaysFailClient) Block(ctx context.Context, h *int64) (*coretypes.ResultBlock, error) {
return nil, fmt.Errorf("fail bc")
}

Expand Down Expand Up @@ -451,13 +446,52 @@ func TestGasLimitFallbackToDefault(t *testing.T) {
require.NoError(t, err)
require.Equal(t, uint64(10_000_000), h1.GasLimit) // DefaultBlockGasLimit

// Case 2: Block fails
bcClient := &bcFailClient{MockClient: &MockClient{}}
// Case 2: Block fails — with one RPC path for the block, resolution errors out entirely.
bcClient := &bcAlwaysFailClient{MockClient: &MockClient{}}
watermarks2 := evmrpc.NewWatermarkManager(bcClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore())
backend2 := evmrpc.NewBackend(ctxProvider, &testApp.EvmKeeper, legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig }, bcClient, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks2)
h2, err := backend2.HeaderByNumber(context.Background(), 1)
require.NoError(t, err)
require.Equal(t, uint64(10_000_000), h2.GasLimit) // DefaultBlockGasLimit
_, err = backend2.HeaderByNumber(context.Background(), 1)
require.Error(t, err)
}

// Exercises getHeader when tmBlock is nil (CurrentHeader): successful block fetch vs gas-limit
// fallback when Block RPC fails. HeaderByNumber / BlockByNumber already cover the tmBlock != nil path.
func TestSimulateBackendBlockResolutionCoverage(t *testing.T) {
testApp := app.Setup(t, false, false, false)
baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1)
ctxProvider := func(h int64) sdk.Context {
if h == evmrpc.LatestCtxHeight {
return baseCtx
}
return baseCtx.WithBlockHeight(h)
}
cfg := &evmrpc.SimulateConfig{GasCap: 10_000_000, EVMTimeout: time.Second}
primeReceiptStore(t, testApp.EvmKeeper.ReceiptStore(), 1)
tmClient := &MockClient{}
watermarks := evmrpc.NewWatermarkManager(tmClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore())
backend := evmrpc.NewBackend(ctxProvider, &testApp.EvmKeeper,
legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig },
tmClient, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks)

t.Run("CurrentHeader_fetches_block_when_tmBlock_nil", func(t *testing.T) {
h := backend.CurrentHeader()
require.NotNil(t, h)
require.Equal(t, int64(1), h.Number.Int64())
require.Equal(t, common.BytesToHash(MockBlockID.Hash), h.ParentHash)
})

t.Run("CurrentHeader_fallback_gas_limit_when_block_unavailable", func(t *testing.T) {
bcClient := &bcAlwaysFailClient{MockClient: &MockClient{}}
wm := evmrpc.NewWatermarkManager(bcClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore())
b2 := evmrpc.NewBackend(ctxProvider, &testApp.EvmKeeper,
legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig },
bcClient, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, wm)
h := b2.CurrentHeader()
require.NotNil(t, h)
require.Equal(t, int64(1), h.Number.Int64())
require.Equal(t, uint64(10_000_000), h.GasLimit)
require.Equal(t, common.Hash{}, h.ParentHash)
})
}

func TestSimulationAPIRequestLimiter(t *testing.T) {
Expand Down
Loading