From 1c7a984a8e8a73a8fafabace771a1f9f066e9eef Mon Sep 17 00:00:00 2001 From: Wen Date: Sat, 16 May 2026 09:36:10 -0700 Subject: [PATCH 1/5] evmrpc: iterate Block.Txs in block trace backend (CON-296) Co-Authored-By: Claude Opus 4.7 (1M context) --- evmrpc/simulate.go | 9 ++------- .../testdata/debug_traceBlockByHash/traceBlockByHash.iox | 8 +++++--- .../debug_traceBlockByNumber/traceBlockByNumber.iox | 8 +++++--- .../traceBlockByHashExcludeTraceFail.iox | 8 +++++--- .../traceBlockByNumberExcludeTraceFail.iox | 8 +++++--- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index 4de9806574..a578116739 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -364,11 +364,6 @@ func (b Backend) BlockByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtyp if err != nil { return nil, nil, err } - blockRes, err := b.tmClient.BlockResults(ctx, &tmBlock.Block.Height) - if err != nil { - return nil, nil, err - } - TraceTendermintIfApplicable(ctx, "BlockResults", []string{stringifyInt64Ptr(&tmBlock.Block.Height)}, blockRes) sdkCtx := b.ctxProvider(LatestCtxHeight) var txs []*ethtypes.Transaction var metadata []tracersutils.TraceBlockMetadata @@ -377,8 +372,8 @@ func (b Backend) BlockByNumber(ctx context.Context, bn rpc.BlockNumber) (*ethtyp for _, msg := range msgs { idxToMsgs[msg.index] = msg.msg } - for i := range blockRes.TxsResults { - decoded, err := b.txConfigProvider(blockRes.Height).TxDecoder()(tmBlock.Block.Txs[i]) + for i := range tmBlock.Block.Txs { + decoded, err := b.txConfigProvider(tmBlock.Block.Height).TxDecoder()(tmBlock.Block.Txs[i]) if err != nil { return nil, nil, err } diff --git a/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByHash/traceBlockByHash.iox b/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByHash/traceBlockByHash.iox index 8186a4e9b6..cde49c6443 100644 --- a/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByHash/traceBlockByHash.iox +++ b/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByHash/traceBlockByHash.iox @@ -1,6 +1,8 @@ -// Data-dependent: bind block hash from latest, then debug_traceBlockByHash(blockHash). Run evm_rpc_tests.sh when local. ->> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]} +// Resolve the deploy tx's block hash, then trace that block. The deploy is a known EVM tx, +// so the result must be non-empty. expect_body_contains "txHash" rejects an empty result (`[]`). +>> {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["${deployTxHash}"]} << {"jsonrpc":"2.0","id":1,"result":{}} -@ bind blockHash = result.hash +@ bind blockHash = result.blockHash >> {"jsonrpc":"2.0","id":1,"method":"debug_traceBlockByHash","params":["${blockHash}",{"tracer":"callTracer"}]} << {"jsonrpc":"2.0","id":1,"result":[]} +@ expect_body_contains "txHash" diff --git a/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByNumber/traceBlockByNumber.iox b/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByNumber/traceBlockByNumber.iox index bf9f3cde27..5b0fa09f65 100644 --- a/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByNumber/traceBlockByNumber.iox +++ b/integration_test/evm_module/rpc_io_test/testdata/debug_traceBlockByNumber/traceBlockByNumber.iox @@ -1,6 +1,8 @@ -// Data-dependent: bind block number from latest, then debug_traceBlockByNumber(blockNum). Run evm_rpc_tests.sh when local. ->> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]} +// Resolve the deploy tx's block number, then trace that block. The deploy is a known EVM tx, +// so the result must be non-empty. expect_body_contains "txHash" rejects an empty result (`[]`). +>> {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["${deployTxHash}"]} << {"jsonrpc":"2.0","id":1,"result":{}} -@ bind blockNum = result.number +@ bind blockNum = result.blockNumber >> {"jsonrpc":"2.0","id":1,"method":"debug_traceBlockByNumber","params":["${blockNum}",{"tracer":"callTracer"}]} << {"jsonrpc":"2.0","id":1,"result":[]} +@ expect_body_contains "txHash" diff --git a/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByHashExcludeTraceFail/traceBlockByHashExcludeTraceFail.iox b/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByHashExcludeTraceFail/traceBlockByHashExcludeTraceFail.iox index 81162e8d84..0de7dab878 100644 --- a/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByHashExcludeTraceFail/traceBlockByHashExcludeTraceFail.iox +++ b/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByHashExcludeTraceFail/traceBlockByHashExcludeTraceFail.iox @@ -1,6 +1,8 @@ -// Data-dependent: bind block hash from latest, then sei_traceBlockByHashExcludeTraceFail(blockHash). Run evm_rpc_tests.sh when local. ->> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]} +// Resolve the deploy tx's block hash, then trace that block. The deploy is a known-successful EVM tx, +// so the result must be non-empty. expect_body_contains "txHash" rejects an empty result (`[]`). +>> {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["${deployTxHash}"]} << {"jsonrpc":"2.0","id":1,"result":{}} -@ bind blockHash = result.hash +@ bind blockHash = result.blockHash >> {"jsonrpc":"2.0","id":1,"method":"sei_traceBlockByHashExcludeTraceFail","params":["${blockHash}",{"tracer":"callTracer"}]} << {"jsonrpc":"2.0","id":1,"result":[]} +@ expect_body_contains "txHash" diff --git a/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByNumberExcludeTraceFail/traceBlockByNumberExcludeTraceFail.iox b/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByNumberExcludeTraceFail/traceBlockByNumberExcludeTraceFail.iox index d18ca757a3..2242b8f4d6 100644 --- a/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByNumberExcludeTraceFail/traceBlockByNumberExcludeTraceFail.iox +++ b/integration_test/evm_module/rpc_io_test/testdata/sei_traceBlockByNumberExcludeTraceFail/traceBlockByNumberExcludeTraceFail.iox @@ -1,6 +1,8 @@ -// Data-dependent: bind block number from latest, then sei_traceBlockByNumberExcludeTraceFail(blockNum). Run evm_rpc_tests.sh when local. ->> {"jsonrpc":"2.0","id":1,"method":"eth_getBlockByNumber","params":["latest",false]} +// Resolve the deploy tx's block number, then trace that block. The deploy is a known-successful EVM tx, +// so the result must be non-empty. expect_body_contains "txHash" rejects an empty result (`[]`). +>> {"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["${deployTxHash}"]} << {"jsonrpc":"2.0","id":1,"result":{}} -@ bind blockNum = result.number +@ bind blockNum = result.blockNumber >> {"jsonrpc":"2.0","id":1,"method":"sei_traceBlockByNumberExcludeTraceFail","params":["${blockNum}",{"tracer":"callTracer"}]} << {"jsonrpc":"2.0","id":1,"result":[]} +@ expect_body_contains "txHash" From 4c6335a0680c6226aedac42cedffd740373e6711 Mon Sep 17 00:00:00 2001 From: Wen Date: Sat, 16 May 2026 14:12:44 -0700 Subject: [PATCH 2/5] evmrpc: resolve sei_getCosmosTx via receipt.TransactionIndex Co-Authored-By: Claude Opus 4.7 (1M context) --- evmrpc/association.go | 32 +++---------------- .../sei_getCosmosTx/getCosmosTx-deploy.iox | 4 +++ 2 files changed, 8 insertions(+), 28 deletions(-) create mode 100644 integration_test/evm_module/rpc_io_test/testdata/sei_getCosmosTx/getCosmosTx-deploy.iox diff --git a/evmrpc/association.go b/evmrpc/association.go index dd9fe909ea..bb3b1a5e3c 100644 --- a/evmrpc/association.go +++ b/evmrpc/association.go @@ -10,7 +10,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/rpc" "github.com/sei-protocol/sei-chain/sei-cosmos/client" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" sdkerrors "github.com/sei-protocol/sei-chain/sei-cosmos/types/errors" @@ -157,37 +156,14 @@ func (t *AssociationAPI) GetCosmosTx(ctx context.Context, ethHash common.Hash) ( return "", fmt.Errorf("invalid block number: %d", receipt.BlockNumber) } height := int64(receipt.BlockNumber) //nolint:gosec - number := rpc.BlockNumber(height) - numberPtr, err := getBlockNumber(ctx, t.tmClient, number) + block, err := blockByNumberRespectingWatermarks(ctx, t.tmClient, t.watermarks, &height, 1) if err != nil { return "", err } - block, err := blockByNumberRespectingWatermarks(ctx, t.tmClient, t.watermarks, numberPtr, 1) - if err != nil { - return "", err - } - blockRes, err := blockResultsWithRetry(ctx, t.tmClient, &height) - if err != nil { - return "", err + if int(receipt.TransactionIndex) >= len(block.Block.Txs) { + return "", fmt.Errorf("transaction index out of range") } - for i := range blockRes.TxsResults { - tmTx := block.Block.Txs[i] - decoded, err := t.txConfigProvider(block.Block.Height).TxDecoder()(block.Block.Txs[i]) - if err != nil { - return "", err - } - for _, msg := range decoded.GetMsgs() { - switch m := msg.(type) { - case *types.MsgEVMTransaction: - ethtx, _ := m.AsTransaction() - hash := ethtx.Hash() - if hash == ethHash { - return fmt.Sprintf("%X", tmTx.Hash()), nil - } - } - } - } - return "", fmt.Errorf("transaction not found") + return fmt.Sprintf("%X", block.Block.Txs[receipt.TransactionIndex].Hash()), nil } func (t *AssociationAPI) GetEvmTx(ctx context.Context, cosmosHash string) (result string, returnErr error) { diff --git a/integration_test/evm_module/rpc_io_test/testdata/sei_getCosmosTx/getCosmosTx-deploy.iox b/integration_test/evm_module/rpc_io_test/testdata/sei_getCosmosTx/getCosmosTx-deploy.iox new file mode 100644 index 0000000000..1e1fd7b5a0 --- /dev/null +++ b/integration_test/evm_module/rpc_io_test/testdata/sei_getCosmosTx/getCosmosTx-deploy.iox @@ -0,0 +1,4 @@ +// Positive case: look up the cosmos tx hash for the seeded deploy tx. +// Spec-only matcher: a regression in the lookup returns `error` (kind mismatch) and fails this test. +>> {"jsonrpc":"2.0","id":1,"method":"sei_getCosmosTx","params":["${deployTxHash}"]} +<< {"jsonrpc":"2.0","id":1,"result":""} From f33e827e269e325a1a378d494edd7dcc8b828775 Mon Sep 17 00:00:00 2001 From: Wen Date: Sat, 16 May 2026 14:41:48 -0700 Subject: [PATCH 3/5] evmrpc: source getHeader gasLimit from ctx.ConsensusParams Co-Authored-By: Claude Opus 4.7 (1M context) --- evmrpc/simulate.go | 6 ++---- evmrpc/simulate_test.go | 35 ++++++++++++++++------------------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/evmrpc/simulate.go b/evmrpc/simulate.go index a578116739..de45e662b6 100644 --- a/evmrpc/simulate.go +++ b/evmrpc/simulate.go @@ -682,11 +682,9 @@ func (b *Backend) getHeader(ctx context.Context, tmBlock *coretypes.ResultBlock) baseFee = nil } var gasLimit uint64 - blockRes, blockResErr := blockResultsWithRetry(ctx, b.tmClient, &height) - if blockResErr == nil && blockRes.ConsensusParamUpdates != nil && blockRes.ConsensusParamUpdates.Block != nil { - gasLimit = uint64(blockRes.ConsensusParamUpdates.Block.MaxGas) //nolint:gosec + if cp := sdkCtx.ConsensusParams(); cp != nil && cp.Block != nil { + gasLimit = uint64(cp.Block.MaxGas) //nolint:gosec } else { - // Fallback to default if block results unavailable gasLimit = keeper.DefaultBlockGasLimit } diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index d7674b103e..62d32e3ee4 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -23,6 +23,7 @@ import ( sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" + types2 "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" @@ -32,13 +33,6 @@ import ( "github.com/stretchr/testify/require" ) -// 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") -} - func primeReceiptStore(t *testing.T, store receipt.ReceiptStore, latest int64) { t.Helper() if store == nil { @@ -410,10 +404,11 @@ func TestPreV620UpgradeUsesBaseFeeNil(t *testing.T) { require.NotNil(t, headerDifferentChain.BaseFee, "Base fee should not be nil for non-pacific-1 chains") } -// Concise gas-limit sanity test +// Header gasLimit comes from the active SDK ConsensusParams at the block's height. func TestGasLimitUsesConsensusOrConfig(t *testing.T) { testApp := app.Setup(t, false, false, false) - baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1) + baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1). + WithConsensusParams(&types2.ConsensusParams{Block: &types2.BlockParams{MaxGas: 200_000_000}}) ctxProvider := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } cfg := &evmrpc.SimulateConfig{GasCap: 10_000_000, EVMTimeout: time.Second} @@ -434,25 +429,27 @@ func TestGasLimitUsesConsensusOrConfig(t *testing.T) { require.Equal(t, uint64(200_000_000), header2.GasLimit) } -// Gas‐limit fallback tests +// Gas-limit fallback tests func TestGasLimitFallbackToDefault(t *testing.T) { testApp := app.Setup(t, false, false, false) - baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1) - ctxProvider := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } cfg := &evmrpc.SimulateConfig{GasCap: 20_000_000, EVMTimeout: time.Second} - // Case 1: BlockResults fails - brClient := &brFailClient{MockClient: &MockClient{}} - watermarks1 := evmrpc.NewWatermarkManager(brClient, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) - backend1 := evmrpc.NewBackend(ctxProvider, &testApp.EvmKeeper, legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig }, brClient, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks1) + // Case 1: ConsensusParams is nil → DefaultBlockGasLimit. + nilParamsCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1).WithConsensusParams(nil) + ctxProvider1 := func(h int64) sdk.Context { return nilParamsCtx.WithBlockHeight(h) } + tmClient1 := &MockClient{} + watermarks1 := evmrpc.NewWatermarkManager(tmClient1, ctxProvider1, nil, testApp.EvmKeeper.ReceiptStore()) + backend1 := evmrpc.NewBackend(ctxProvider1, &testApp.EvmKeeper, legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig }, tmClient1, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks1) h1, err := backend1.HeaderByNumber(context.Background(), 1) require.NoError(t, err) require.Equal(t, uint64(10_000_000), h1.GasLimit) // DefaultBlockGasLimit - // Case 2: Block fails — with one RPC path for the block, resolution errors out entirely. + // Case 2: Block fails — resolution errors out entirely. + baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1) + ctxProvider2 := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } 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) + watermarks2 := evmrpc.NewWatermarkManager(bcClient, ctxProvider2, nil, testApp.EvmKeeper.ReceiptStore()) + backend2 := evmrpc.NewBackend(ctxProvider2, &testApp.EvmKeeper, legacyabci.BeginBlockKeepers{}, func(int64) client.TxConfig { return TxConfig }, bcClient, cfg, testApp.BaseApp, testApp.TracerAnteHandler, evmrpc.NewBlockCache(3000), &sync.Mutex{}, watermarks2) _, err = backend2.HeaderByNumber(context.Background(), 1) require.Error(t, err) } From 50f1fb873f7e03086b499424c091adb72ce3d419 Mon Sep 17 00:00:00 2001 From: Wen Date: Tue, 19 May 2026 13:54:34 -0700 Subject: [PATCH 4/5] evmrpc: include index and length in sei_getCosmosTx OOB error The bounds check at association.go now produces a self-describing error rather than the opaque "transaction index out of range". Co-Authored-By: Claude Opus 4.7 (1M context) --- evmrpc/association.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evmrpc/association.go b/evmrpc/association.go index bb3b1a5e3c..a3a7d4bac4 100644 --- a/evmrpc/association.go +++ b/evmrpc/association.go @@ -161,7 +161,7 @@ func (t *AssociationAPI) GetCosmosTx(ctx context.Context, ethHash common.Hash) ( return "", err } if int(receipt.TransactionIndex) >= len(block.Block.Txs) { - return "", fmt.Errorf("transaction index out of range") + return "", fmt.Errorf("transaction index %d out of range (block has %d txs)", receipt.TransactionIndex, len(block.Block.Txs)) } return fmt.Sprintf("%X", block.Block.Txs[receipt.TransactionIndex].Hash()), nil } From 0d2b1cb3db0a9ed85f600c2d075010bdcca2d0ed Mon Sep 17 00:00:00 2001 From: Wen Date: Tue, 19 May 2026 15:00:43 -0700 Subject: [PATCH 5/5] evmrpc: simplify simulate_test.go import aliases Drop the redundant `receipt` alias (default name matches the path) and rename `types2` to `tenderminttypes` for readability. Co-Authored-By: Claude Opus 4.7 (1M context) --- evmrpc/simulate_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evmrpc/simulate_test.go b/evmrpc/simulate_test.go index f8f052d9aa..673f5d062e 100644 --- a/evmrpc/simulate_test.go +++ b/evmrpc/simulate_test.go @@ -22,9 +22,9 @@ import ( "github.com/sei-protocol/sei-chain/example/contracts/simplestorage" "github.com/sei-protocol/sei-chain/sei-cosmos/client" sdk "github.com/sei-protocol/sei-chain/sei-cosmos/types" - receipt "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" + "github.com/sei-protocol/sei-chain/sei-db/ledger_db/receipt" "github.com/sei-protocol/sei-chain/sei-tendermint/libs/bytes" - types2 "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" + tenderminttypes "github.com/sei-protocol/sei-chain/sei-tendermint/proto/tendermint/types" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/client/mock" "github.com/sei-protocol/sei-chain/sei-tendermint/rpc/coretypes" tmtypes "github.com/sei-protocol/sei-chain/sei-tendermint/types" @@ -409,7 +409,7 @@ func TestPreV620UpgradeUsesBaseFeeNil(t *testing.T) { func TestGasLimitUsesConsensusOrConfig(t *testing.T) { testApp := app.Setup(t, false, false, false) baseCtx := testApp.GetContextForDeliverTx([]byte{}).WithBlockHeight(1). - WithConsensusParams(&types2.ConsensusParams{Block: &types2.BlockParams{MaxGas: 200_000_000}}) + WithConsensusParams(&tenderminttypes.ConsensusParams{Block: &tenderminttypes.BlockParams{MaxGas: 200_000_000}}) ctxProvider := func(h int64) sdk.Context { return baseCtx.WithBlockHeight(h) } cfg := &evmrpc.SimulateConfig{GasCap: 10_000_000, EVMTimeout: time.Second}