Skip to content
Merged
53 changes: 36 additions & 17 deletions evmrpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ type BlockAPI struct {

type SeiBlockAPI struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: seems like BlockAPI can be used directly now

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.

From data point of view you are correct. But README said everything with Sei* are Sei-specific interface, so removing this can be a bit confusing.

Anyways, I suggest we leave this cleanup for later when we are done with Autobahn integration tests, we will need to touch RPC next, and at that time we may be able to clean up a few Cosmos only RPC endpoints.

*BlockAPI
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error)
}

func NewBlockAPI(tmClient client.LocalClient, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, txConfigProvider func(int64) client.TxConfig, connectionType ConnectionType, watermarks *WatermarkManager, globalBlockCache BlockCache, cacheCreationMutex *sync.Mutex) *BlockAPI {
Expand All @@ -108,7 +107,6 @@ func NewSeiBlockAPI(
ctxProvider func(int64) sdk.Context,
txConfigProvider func(int64) client.TxConfig,
connectionType ConnectionType,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
watermarks *WatermarkManager,
globalBlockCache BlockCache,
cacheCreationMutex *sync.Mutex,
Expand All @@ -127,8 +125,7 @@ func NewSeiBlockAPI(
cacheCreationMutex: cacheCreationMutex,
}
return &SeiBlockAPI{
BlockAPI: blockAPI,
isPanicTx: isPanicTx,
BlockAPI: blockAPI,
}
}

Expand All @@ -138,12 +135,11 @@ func NewSei2BlockAPI(
ctxProvider func(int64) sdk.Context,
txConfigProvider func(int64) client.TxConfig,
connectionType ConnectionType,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
watermarks *WatermarkManager,
globalBlockCache BlockCache,
cacheCreationMutex *sync.Mutex,
) *SeiBlockAPI {
blockAPI := NewSeiBlockAPI(tmClient, k, ctxProvider, txConfigProvider, connectionType, isPanicTx, watermarks, globalBlockCache, cacheCreationMutex)
blockAPI := NewSeiBlockAPI(tmClient, k, ctxProvider, txConfigProvider, connectionType, watermarks, globalBlockCache, cacheCreationMutex)
blockAPI.namespace = Sei2Namespace
blockAPI.includeBankTransfers = true
return blockAPI
Expand All @@ -154,13 +150,14 @@ func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, numb
if number == 0 {
return encodeGenesisBlock(), nil
}
// exclude synthetic txs
return a.getBlockByNumber(ctx, number, fullTx, false, a.isPanicTx)
// Exclude synthetic txs (filterTransactions drops them) and ante-failure
// stub receipts (EncodeTmBlock drops them via excludeUntraceable).
return a.getBlockByNumber(ctx, number, fullTx, false, true)
}

func (a *SeiBlockAPI) GetBlockByHashExcludeTraceFail(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
// exclude synthetic txs
return a.getBlockByHash(ctx, blockHash, fullTx, false, a.isPanicTx)
// See note on GetBlockByNumberExcludeTraceFail.
return a.getBlockByHash(ctx, blockHash, fullTx, false, true)
}

func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number rpc.BlockNumber) (result *hexutil.Uint, returnErr error) {
Expand Down Expand Up @@ -199,10 +196,10 @@ func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash

func (a *BlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
// used for both: eth_ and sei_ namespaces
return a.getBlockByHash(ctx, blockHash, fullTx, a.includeShellReceipts, nil)
return a.getBlockByHash(ctx, blockHash, fullTx, a.includeShellReceipts, false)
}

func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, includeSyntheticTxs bool, isPanicTx func(ctx context.Context, hash common.Hash) (bool, error)) (result map[string]interface{}, returnErr error) {
func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, includeSyntheticTxs bool, excludeUntraceable bool) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
defer func() {
recordMetricsWithError(ctx, fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr, recover())
Expand Down Expand Up @@ -233,7 +230,7 @@ func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fu
if err != nil {
return nil, err
}
return EncodeTmBlock(a.ctxProvider, a.txConfigProvider, block, blockRes, a.keeper, fullTx, a.includeBankTransfers, includeSyntheticTxs, isPanicTx, a.globalBlockCache, a.cacheCreationMutex)
return EncodeTmBlock(a.ctxProvider, a.txConfigProvider, block, blockRes, a.keeper, fullTx, a.includeBankTransfers, includeSyntheticTxs, excludeUntraceable, a.globalBlockCache, a.cacheCreationMutex)
}

func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
Expand All @@ -245,15 +242,15 @@ func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber,
// for compatibility with the graph, always return genesis block
return encodeGenesisBlock(), nil
}
return a.getBlockByNumber(ctx, number, fullTx, a.includeShellReceipts, nil)
return a.getBlockByNumber(ctx, number, fullTx, a.includeShellReceipts, false)
}

func (a *BlockAPI) getBlockByNumber(
ctx context.Context,
number rpc.BlockNumber,
fullTx bool,
includeSyntheticTxs bool,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
excludeUntraceable bool,
) (result map[string]interface{}, returnErr error) {
numberPtr, err := getBlockNumber(ctx, a.tmClient, number)
if err != nil {
Expand All @@ -280,7 +277,7 @@ func (a *BlockAPI) getBlockByNumber(
if err != nil {
return nil, err
}
return EncodeTmBlock(a.ctxProvider, a.txConfigProvider, block, blockRes, a.keeper, fullTx, a.includeBankTransfers, includeSyntheticTxs, isPanicTx, a.globalBlockCache, a.cacheCreationMutex)
return EncodeTmBlock(a.ctxProvider, a.txConfigProvider, block, blockRes, a.keeper, fullTx, a.includeBankTransfers, includeSyntheticTxs, excludeUntraceable, a.globalBlockCache, a.cacheCreationMutex)
}

func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]interface{}, returnErr error) {
Expand Down Expand Up @@ -362,6 +359,17 @@ func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.Block
return compactReceipts, nil
}

// EncodeTmBlock renders a tendermint block as an eth_getBlockBy* response.
//
// excludeUntraceable, when true, drops EVM txs whose receipt is an
// ante-deferred stub (EffectiveGasPrice==0 && GasUsed==0). x/evm/keeper/abci.go
// writes such stubs for txs that passed the nonce check but failed a later
// ante step (insufficient funds, insufficient fee, etc.); they never reached
// the VM and have no meaningful trace. Used by the *ExcludeTraceFail block
// endpoints to satisfy evmrpc/README.md's "included in blocks but not
// executed" filter; the regular eth_getBlockBy* endpoints pass false so
// these txs still surface in normal block responses (per PR #2343's
// TestAnteFailureOthers — users want to see them).
func EncodeTmBlock(
ctxProvider func(int64) sdk.Context,
txConfigProvider func(int64) client.TxConfig,
Expand All @@ -371,7 +379,7 @@ func EncodeTmBlock(
fullTx bool,
includeBankTransfers bool,
includeSyntheticTxs bool,
isPanicOrSynthetic func(ctx context.Context, hash common.Hash) (bool, error),
excludeUntraceable bool,
globalBlockCache BlockCache,
cacheCreationMutex *sync.Mutex,
) (map[string]interface{}, error) {
Expand Down Expand Up @@ -407,6 +415,17 @@ func EncodeTmBlock(
if !found {
continue
}
// Untraceable receipt — tx never reached the VM (ante-deferred
// stub) or is chain-generated synthetic. filterTransactions's
// isReceiptFromAnteError only catches the nonce-error subset
// post-v5.8.0 (per PR #2343, which keeps insufficient-funds
// receipts visible to the regular eth_getBlockBy* endpoints);
// *ExcludeTraceFail needs the broader discriminator. See
// isReceiptUntraceable for the shared definition used at every
// *ExcludeTraceFail site.
if excludeUntraceable && isReceiptUntraceable(receipt) {
continue
}
if !fullTx {
transactions = append(transactions, hash.Hex())
} else {
Expand Down
105 changes: 101 additions & 4 deletions evmrpc/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package evmrpc_test
import (
"crypto/sha256"
"math/big"
"strings"
"sync"
"testing"
"time"
Expand All @@ -13,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/export"
"github.com/sei-protocol/sei-chain/evmrpc"
"github.com/sei-protocol/sei-chain/sei-cosmos/client"
Expand All @@ -22,7 +24,9 @@ import (
"github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes"
tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types"
testkeeper "github.com/sei-protocol/sei-chain/testutil/keeper"
"github.com/sei-protocol/sei-chain/x/evm/config"
"github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/sei-protocol/sei-chain/x/evm/types/ethtx"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -50,7 +54,7 @@ func TestEncodeTmBlock_EmptyTransactions(t *testing.T) {
}

// Call EncodeTmBlock with empty transactions
result, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, block, blockRes, k, true, false, false, nil, evmrpc.NewBlockCache(3000), &sync.Mutex{})
result, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, block, blockRes, k, true, false, false, false, evmrpc.NewBlockCache(3000), &sync.Mutex{})
require.Nil(t, err)

// Assert txHash is equal to ethtypes.EmptyTxsHash
Expand Down Expand Up @@ -96,7 +100,7 @@ func TestEncodeBankMsg(t *testing.T) {
},
},
}
res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, &resBlockRes, k, true, false, false, nil, evmrpc.NewBlockCache(3000), &sync.Mutex{})
res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, &resBlockRes, k, true, false, false, false, evmrpc.NewBlockCache(3000), &sync.Mutex{})
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 0, len(txs))
Expand Down Expand Up @@ -149,7 +153,7 @@ func TestEncodeWasmExecuteMsg(t *testing.T) {
},
},
}
res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, &resBlockRes, k, true, false, true, nil, evmrpc.NewBlockCache(3000), &sync.Mutex{})
res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, &resBlockRes, k, true, false, true, false, evmrpc.NewBlockCache(3000), &sync.Mutex{})
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 1, len(txs))
Expand Down Expand Up @@ -210,7 +214,7 @@ func TestEncodeBankTransferMsg(t *testing.T) {
},
},
}
res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, &resBlockRes, k, true, true, false, nil, evmrpc.NewBlockCache(3000), &sync.Mutex{})
res, err := evmrpc.EncodeTmBlock(func(i int64) sdk.Context { return ctx }, func(i int64) client.TxConfig { return TxConfig }, &resBlock, &resBlockRes, k, true, true, false, false, evmrpc.NewBlockCache(3000), &sync.Mutex{})
require.Nil(t, err)
txs := res["transactions"].([]interface{})
require.Equal(t, 1, len(txs))
Expand All @@ -230,6 +234,99 @@ func TestEncodeBankTransferMsg(t *testing.T) {
}, txs[0].(*export.RPCTransaction))
}

// TestEncodeTmBlock_ExcludeUntraceable pins the block-side counterpart of the
// tx-side discriminator: a correct-nonce ante failure (e.g. insufficient
// funds) lands a stub receipt in the receipt store. filterTransactions's
// isReceiptFromAnteError post-v5.8.0 only matches nonce-error VmError
// content, so the stub flows through to EncodeTmBlock — where
// excludeUntraceable=true catches it via EffectiveGasPrice==0 && GasUsed==0.
//
// We use the global EVMKeeper (where setup_test.go's fixtures live) and a
// post-v5.8.0 ctx so filterTransactions takes the production branch.
// DebugTracePanicTx isn't suitable here (Nonce=100, gets filtered by
// filterTransactions's nonce-bumping check before reaching EncodeTmBlock);
// we register a correct-nonce ante-stub fixture instead.
func TestEncodeTmBlock_ExcludeUntraceable(t *testing.T) {
k := EVMKeeper

// Build a correct-nonce EVM tx whose receipt mimics an ante-deferred
// stub: EffectiveGasPrice=0, GasUsed=0, VmError set. We need a fresh
// sender so startOfBlockNonce equals the tx's nonce — otherwise
// filterTransactions's nonce-bumping branch filters it out at
// utils.go:253 before EncodeTmBlock sees it.
chainID := big.NewInt(config.DefaultChainID)
privKey, _ := crypto.GenerateKey()
signer := ethtypes.MakeSigner(types.DefaultChainConfig().EthereumConfig(chainID), big.NewInt(Ctx.BlockHeight()), uint64(Ctx.BlockTime().Unix()))
to := common.HexToAddress("0x1234567890123456789012345678901234567890")
rawTx, err := ethtypes.SignTx(ethtypes.NewTx(&ethtypes.DynamicFeeTx{
Nonce: 0, GasFeeCap: big.NewInt(10), Gas: 22000, To: &to, Value: big.NewInt(0), ChainID: chainID,
}), signer, privKey)
require.NoError(t, err)
td, err := ethtx.NewDynamicFeeTx(rawTx)
require.NoError(t, err)
msg, err := types.NewMsgEVMTransaction(td)
require.NoError(t, err)
builder := TxConfig.NewTxBuilder()
require.NoError(t, builder.SetMsgs(msg))
anteStubTx := builder.GetTx()
anteStubBz, _ := Encoder(anteStubTx)
anteStubHash := rawTx.Hash()

// Mock the stub receipt on EVMKeeper so getOrSetCachedReceipt finds it.
stubHeight := int64(MockHeight103)
stubCtx := Ctx.WithBlockHeight(stubHeight)
require.NoError(t, k.MockReceipt(stubCtx, anteStubHash, &types.Receipt{
BlockNumber: uint64(stubHeight),
TransactionIndex: 0,
TxHashHex: anteStubHash.Hex(),
VmError: "insufficient funds",
}))

nonPanicBz, _ := Encoder(DebugTraceNonPanicTx)

block := &coretypes.ResultBlock{
BlockID: MockBlockID,
Block: &tmtypes.Block{
Header: mockBlockHeader(stubHeight),
Data: tmtypes.Data{
Txs: []tmtypes.Tx{anteStubBz, nonPanicBz},
},
LastCommit: &tmtypes.Commit{Height: stubHeight - 1},
},
}
blockRes := &coretypes.ResultBlockResults{
TxsResults: []*abci.ExecTxResult{{Data: anteStubBz}, {Data: nonPanicBz}},
ConsensusParamUpdates: &types2.ConsensusParams{
Block: &types2.BlockParams{MaxBytes: 100000000, MaxGas: 200000000},
},
}

// Post-v5.8.0 ctx so filterTransactions's isReceiptFromAnteError stays
// narrow (nonce-only VmError) — the production path where the stub
// flows through and EncodeTmBlock has to apply its own check.
ctx := Ctx.WithBlockHeight(stubHeight).WithClosestUpgradeName(LatestCtxUpgradeName)
ctxProvider := func(int64) sdk.Context { return ctx }
txConfigProvider := func(int64) client.TxConfig { return TxConfig }

// excludeUntraceable=true: ante stub dropped, revert kept.
res, err := evmrpc.EncodeTmBlock(ctxProvider, txConfigProvider, block, blockRes, k,
false /*fullTx*/, false /*includeBankTransfers*/, false /*includeSyntheticTxs*/, true, /*excludeUntraceable*/
evmrpc.NewBlockCache(3000), &sync.Mutex{})
require.NoError(t, err)
txs := res["transactions"].([]interface{})
require.Len(t, txs, 1, "expected only the revert to survive, got %v", txs)
require.Equal(t, strings.ToLower(TestNonPanicTxHash), strings.ToLower(txs[0].(string)))

// excludeUntraceable=false: ante stub flows through (matches regular
// eth_getBlockBy* behavior per PR #2343's TestAnteFailureOthers).
res, err = evmrpc.EncodeTmBlock(ctxProvider, txConfigProvider, block, blockRes, k,
false /*fullTx*/, false /*includeBankTransfers*/, false /*includeSyntheticTxs*/, false, /*excludeUntraceable*/
evmrpc.NewBlockCache(3000), &sync.Mutex{})
require.NoError(t, err)
txs = res["transactions"].([]interface{})
require.Len(t, txs, 2, "expected revert + ante stub to flow through, got %v", txs)
}

func TestEVMLaunchHeightValidation(t *testing.T) {
// Test ValidateEVMBlockHeight function
// Should pass for pacific-1 with valid height
Expand Down
2 changes: 1 addition & 1 deletion evmrpc/block_txcount_parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func TestBlockTransactionCountMatchesGetBlockByNumber(t *testing.T) {
decodeOnly := countDecodeOnlyEvmTxs(block.Block.Txs, TxConfig.TxDecoder())
require.Equal(t, 2, decodeOnly)

encoded, err := evmrpc.EncodeTmBlock(ctxProvider, txConfigProvider, block, blockRes, k, false, false, false, nil, cache, mu)
encoded, err := evmrpc.EncodeTmBlock(ctxProvider, txConfigProvider, block, blockRes, k, false, false, false, false, cache, mu)
require.NoError(t, err)
list := encoded["transactions"].([]interface{})

Expand Down
4 changes: 1 addition & 3 deletions evmrpc/height_availability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,7 @@ func TestGetBlockReceiptsGenesisByNumber(t *testing.T) {
func TestGetBlockByNumberExcludeTraceFailGenesis(t *testing.T) {
t.Parallel()

api := NewSeiBlockAPI(nil, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP,
func(context.Context, common.Hash) (bool, error) { return false, nil },
nil, nil, nil)
api := NewSeiBlockAPI(nil, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, nil, nil, nil)
block, err := api.GetBlockByNumberExcludeTraceFail(context.Background(), 0, false)
require.NoError(t, err)
require.NotNil(t, block)
Expand Down
4 changes: 2 additions & 2 deletions evmrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ func NewEVMHTTPServer(
},
{
Namespace: "sei",
Service: NewSeiBlockAPI(tmClient, k, ctxProvider, txConfigProvider, ConnectionTypeHTTP, isPanicOrSyntheticTxFunc, watermarks, globalBlockCache, cacheCreationMutex),
Service: NewSeiBlockAPI(tmClient, k, ctxProvider, txConfigProvider, ConnectionTypeHTTP, watermarks, globalBlockCache, cacheCreationMutex),
},
{
Namespace: "sei2",
Service: NewSei2BlockAPI(tmClient, k, ctxProvider, txConfigProvider, ConnectionTypeHTTP, isPanicOrSyntheticTxFunc, watermarks, globalBlockCache, cacheCreationMutex),
Service: NewSei2BlockAPI(tmClient, k, ctxProvider, txConfigProvider, ConnectionTypeHTTP, watermarks, globalBlockCache, cacheCreationMutex),
},
{
Namespace: "eth",
Expand Down
Loading
Loading