From 3a6e260c0cc9d9b70eab97839ff3fdd2fb3e8b49 Mon Sep 17 00:00:00 2001 From: Mojtaba Date: Fri, 13 Mar 2026 13:45:10 +0000 Subject: [PATCH] chore (evmrpc): fix eth_getBlockTransactionCountByHash hash lookup consistency --- evmrpc/block.go | 58 ++++++++++++++++++------------ evmrpc/height_availability_test.go | 13 +++++++ 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/evmrpc/block.go b/evmrpc/block.go index e896bbe622..870a564b42 100644 --- a/evmrpc/block.go +++ b/evmrpc/block.go @@ -34,6 +34,35 @@ const ( Sei2Namespace = "sei2" ) +// GenesisBlockHash is the block hash returned by GetBlockByNumber("0x0"). Hash-based lookups +// must recognize this so that count/block-by-hash stay consistent with block-by-number. +var GenesisBlockHash = common.HexToHash("0xF9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E") + +func encodeGenesisBlock() map[string]interface{} { + return map[string]interface{}{ + "number": (*hexutil.Big)(big.NewInt(0)), + "hash": "0xF9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E", + "parentHash": common.Hash{}, + "nonce": ethtypes.BlockNonce{}, // inapplicable to Sei + "mixHash": common.Hash{}, // inapplicable to Sei + "sha3Uncles": ethtypes.EmptyUncleHash, // inapplicable to Sei + "logsBloom": ethtypes.Bloom{}, + "stateRoot": common.Hash{}, + "miner": common.Address{}, + "difficulty": (*hexutil.Big)(big.NewInt(0)), // inapplicable to Sei + "extraData": hexutil.Bytes{}, // inapplicable to Sei + "gasLimit": hexutil.Uint64(0), + "gasUsed": hexutil.Uint64(0), + "timestamp": hexutil.Uint64(0), + "transactionsRoot": common.Hash{}, + "receiptsRoot": common.Hash{}, + "size": hexutil.Uint64(0), + "uncles": []common.Hash{}, // inapplicable to Sei + "transactions": []interface{}{}, + "baseFeePerGas": (*hexutil.Big)(big.NewInt(0)), + } +} + type BlockAPI struct { tmClient rpcclient.Client keeper *keeper.Keeper @@ -143,6 +172,9 @@ func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash common.Hash) (result *hexutil.Uint, returnErr error) { startTime := time.Now() defer recordMetricsWithError(fmt.Sprintf("%s_getBlockTransactionCountByHash", a.namespace), a.connectionType, startTime, returnErr) + if blockHash == GenesisBlockHash { + return a.getEvmTxCount(nil, 0), nil + } block, err := blockByHashRespectingWatermarks(ctx, a.tmClient, a.watermarks, blockHash[:], 1) if err != nil { return nil, err @@ -158,6 +190,9 @@ func (a *BlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fu 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) { startTime := time.Now() defer recordMetricsWithError(fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr) + if blockHash == GenesisBlockHash { + return encodeGenesisBlock(), nil + } block, err := blockByHashRespectingWatermarks(ctx, a.tmClient, a.watermarks, blockHash[:], 1) if err != nil { return nil, err @@ -181,28 +216,7 @@ func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, defer recordMetricsWithError(fmt.Sprintf("%s_getBlockByNumber", a.namespace), a.connectionType, startTime, returnErr) if number == 0 { // for compatibility with the graph, always return genesis block - return map[string]interface{}{ - "number": (*hexutil.Big)(big.NewInt(0)), - "hash": "0xF9D3845DF25B43B1C6926F3CEDA6845C17F5624E12212FD8847D0BA01DA1AB9E", - "parentHash": common.Hash{}, - "nonce": ethtypes.BlockNonce{}, // inapplicable to Sei - "mixHash": common.Hash{}, // inapplicable to Sei - "sha3Uncles": ethtypes.EmptyUncleHash, // inapplicable to Sei - "logsBloom": ethtypes.Bloom{}, - "stateRoot": common.Hash{}, - "miner": common.Address{}, - "difficulty": (*hexutil.Big)(big.NewInt(0)), // inapplicable to Sei - "extraData": hexutil.Bytes{}, // inapplicable to Sei - "gasLimit": hexutil.Uint64(0), - "gasUsed": hexutil.Uint64(0), - "timestamp": hexutil.Uint64(0), - "transactionsRoot": common.Hash{}, - "receiptsRoot": common.Hash{}, - "size": hexutil.Uint64(0), - "uncles": []common.Hash{}, // inapplicable to Sei - "transactions": []interface{}{}, - "baseFeePerGas": (*hexutil.Big)(big.NewInt(0)), - }, nil + return encodeGenesisBlock(), nil } return a.getBlockByNumber(ctx, number, fullTx, a.includeShellReceipts, nil) } diff --git a/evmrpc/height_availability_test.go b/evmrpc/height_availability_test.go index e8f2d2ec2f..e6a5fc5a2e 100644 --- a/evmrpc/height_availability_test.go +++ b/evmrpc/height_availability_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/eth/filters" "github.com/ethereum/go-ethereum/rpc" "github.com/sei-protocol/sei-chain/sei-cosmos/client" @@ -99,6 +100,18 @@ func TestBlockAPIEnsureHeightUnavailable(t *testing.T) { require.Contains(t, err.Error(), "requested height") } +// TestGetBlockTransactionCountByHashGenesis verifies that the genesis block hash returned by +// eth_getBlockByNumber("0x0") is accepted by eth_getBlockTransactionCountByHash (consistency). +func TestGetBlockTransactionCountByHashGenesis(t *testing.T) { + t.Parallel() + + api := NewBlockAPI(nil, nil, testCtxProvider, testTxConfigProvider, ConnectionTypeHTTP, nil, nil, nil) + count, err := api.GetBlockTransactionCountByHash(context.Background(), GenesisBlockHash) + require.NoError(t, err) + require.NotNil(t, count) + require.Equal(t, hexutil.Uint(0), *count) +} + func TestLogFetcherSkipsUnavailableCachedBlock(t *testing.T) { t.Parallel()