From d4fc8cd921cff8bdf4195d72026a7cd6d61085a1 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Thu, 2 Oct 2025 15:23:28 -0500 Subject: [PATCH 1/4] add availability checks to getLogs --- evmrpc/filter.go | 17 ++++++++++ evmrpc/filter_test.go | 64 ++++++++++++++++++++++++++++++++++++ evmrpc/setup_test.go | 25 ++++++++++++++ go.work.sum | 3 ++ x/evm/keeper/receipt.go | 4 +++ x/evm/keeper/receipt_test.go | 14 ++++++++ 6 files changed, 127 insertions(+) diff --git a/evmrpc/filter.go b/evmrpc/filter.go index 12edd897d3..d7bad92edb 100644 --- a/evmrpc/filter.go +++ b/evmrpc/filter.go @@ -516,6 +516,11 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r defer recordMetricsWithError(fmt.Sprintf("%s_getLogs", a.namespace), a.connectionType, time.Now(), err) // Calculate block range latest := a.logFetcher.ctxProvider(LatestCtxHeight).BlockHeight() + // get block number from hash and compare to latest + latestReceiptVersion, err := a.logFetcher.k.GetLatestReceiptVersion(a.logFetcher.ctxProvider(LatestCtxHeight)) + if err != nil { + return nil, err + } begin, end := latest, latest if crit.FromBlock != nil { begin = getHeightFromBigIntBlockNumber(latest, crit.FromBlock) @@ -526,6 +531,18 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r begin = end } } + if begin > latestReceiptVersion || end > latestReceiptVersion { + return nil, fmt.Errorf("block range includes unavailable block(s)") + } + if crit.BlockHash != nil { + header, err := a.tmClient.HeaderByHash(ctx, crit.BlockHash[:]) + if err != nil { + return nil, err + } + if header.Header.Height > latestReceiptVersion { + return nil, fmt.Errorf("block hash %s isn't available yet", crit.BlockHash.Hex()) + } + } blockRange := end - begin + 1 diff --git a/evmrpc/filter_test.go b/evmrpc/filter_test.go index 3b8c6b140f..b412ed61bd 100644 --- a/evmrpc/filter_test.go +++ b/evmrpc/filter_test.go @@ -446,6 +446,70 @@ func TestGetLogsBlockHashIsNotZero(t *testing.T) { } } +func TestFilterGetLogsBlockHashNotYetAvailable(t *testing.T) { + t.Parallel() + // Query for a block hash that corresponds to a block height (200) + // that is greater than the latest receipt version (103) + // This should return an error saying the block hash isn't available yet + + filterCriteria := map[string]interface{}{ + "blockHash": FutureBlockHash, + } + resObj := sendRequestGood(t, "getLogs", filterCriteria) + + // Should return an error + errorObj, hasError := resObj["error"] + require.True(t, hasError, "Expected an error when querying for block hash with height > latest receipt version") + + errorMap := errorObj.(map[string]interface{}) + errorMessage := errorMap["message"].(string) + require.Contains(t, errorMessage, "isn't available yet", "Error message should indicate block hash isn't available yet") +} + +func TestFilterGetLogsBlockRangeIncludesUnavailableBlock(t *testing.T) { + t.Parallel() + // Query for a block range where toBlock (200) is greater than the latest receipt version (103) + // This tests that querying a range with unavailable blocks doesn't cause issues + // and returns logs only for available blocks or returns an appropriate error + + filterCriteria := map[string]interface{}{ + "fromBlock": "0x64", // 100 in hex - this block exists + "toBlock": "0xc8", // 200 in hex - this block height > latest receipt version + } + resObj := sendRequestGood(t, "getLogs", filterCriteria) + + // The behavior could be either: + // 1. Return an error indicating the range includes unavailable blocks + // 2. Return empty results (no logs found in the unavailable range) + // We check for both possibilities + + errorObj, hasError := resObj["error"] + require.True(t, hasError, "Expected an error when querying for block range with unavailable block") + // If there's an error, it should mention the block range or unavailability + errorMap := errorObj.(map[string]interface{}) + errorMessage := errorMap["message"].(string) + // The error could be about block range, system overload, or unavailability + require.Contains(t, errorMessage, "unavailable block(s)", "Error message should indicate block range includes unavailable block(s)") +} + +func TestFilterGetLogsBlockRangePartiallyAvailable(t *testing.T) { + t.Parallel() + // Query for a block range that spans both available and unavailable blocks + // fromBlock: 100 (available), toBlock: 105 (should be available since it's < 200) + + filterCriteria := map[string]interface{}{ + "fromBlock": "0x64", // 100 in hex + "toBlock": "0x69", // 105 in hex - still within available range + } + resObj := sendRequestGood(t, "getLogs", filterCriteria) + + // Success case - should return a valid array + result, ok := resObj["result"] + require.True(t, ok, "Result should exist") + _, isArray := result.([]interface{}) + require.True(t, isArray, "Result should be an array") +} + func TestGetLogsTransactionIndexConsistency(t *testing.T) { t.Parallel() diff --git a/evmrpc/setup_test.go b/evmrpc/setup_test.go index 6245594b67..fb60c55ca9 100644 --- a/evmrpc/setup_test.go +++ b/evmrpc/setup_test.go @@ -55,11 +55,13 @@ const MockHeight2 = 2 const MockHeight103 = 103 const MockHeight101 = 101 const MockHeight100 = 100 +const MockHeight200 = 200 var DebugTraceHashHex = "0x1234567890123456789023456789012345678901234567890123456789000004" var DebugTraceBlockHash = "0xBE17E0261E539CB7E9A91E123A6D794E0163D656FCF9B8EAC07823F7ED28512B" var DebugTracePanicBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000003" var MultiTxBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000002" +var FutureBlockHash = "0x00000000000000000000000000000000000000000000000000000000000000C8" var TestCosmosTxHash = "690D39ADF56D4C811B766DFCD729A415C36C4BFFE80D63E305373B9518EBFB14" var TestEvmTxHash = "0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be20a872e" @@ -333,9 +335,22 @@ func (c *MockClient) BlockByHash(_ context.Context, hash bytes.HexBytes) (*coret if hash.String() == MultiTxBlockHash[2:] { return c.mockBlock(MockHeight2), nil } + if hash.String() == FutureBlockHash[2:] { + return c.mockBlock(MockHeight200), nil + } return c.mockBlock(MockHeight8), nil } +func (c *MockClient) HeaderByHash(_ context.Context, hash bytes.HexBytes) (*coretypes.ResultHeader, error) { + block, err := c.BlockByHash(context.Background(), hash) + if err != nil { + return nil, err + } + return &coretypes.ResultHeader{ + Header: &block.Block.Header, + }, nil +} + func (c *MockClient) BlockResults(_ context.Context, height *int64) (*coretypes.ResultBlockResults, error) { if *height == GenesisBlockHeight { return &coretypes.ResultBlockResults{ @@ -1042,6 +1057,16 @@ func setupLogs() { EffectiveGasPrice: 100, }) + // write mock receipt for height 199 + CtxHeight199 := Ctx.WithBlockHeight(199) + EVMKeeper.MockReceipt(CtxHeight199, common.HexToHash("0x1234567890123456789012345678901234567890123456789012345678901999"), &types.Receipt{ + BlockNumber: 199, + TransactionIndex: 0, + TxHashHex: "0x1234567890123456789012345678901234567890123456789012345678901999", + GasUsed: 21000, + EffectiveGasPrice: 100, + }) + // block 2 EVMKeeper.SetBlockBloom(MultiTxCtx, []ethtypes.Bloom{bloom1, bloom2, bloom3}) EVMKeeper.SetEvmOnlyBlockBloom(MultiTxCtx, []ethtypes.Bloom{bloom1, bloom2, bloom3}) diff --git a/go.work.sum b/go.work.sum index 5f0750db39..4c82cba03e 100644 --- a/go.work.sum +++ b/go.work.sum @@ -401,6 +401,7 @@ github.com/cloudflare/cloudflare-go v0.114.0 h1:ucoti4/7Exo0XQ+rzpn1H+IfVVe++zgi github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= @@ -413,6 +414,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbp github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= +github.com/creachadair/command v0.0.0-20220426235536-a748effdf6a1/go.mod h1:bAM+qFQb/KwWyCc9MLC4U1jvn3XyakqP5QRkds5T6cY= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c h1:/ovYnF02fwL0kvspmy9AuyKg1JhdTRUgPw4nUxd9oZM= github.com/decred/dcrd/lru v1.0.0 h1:Kbsb1SFDsIlaupWPwsPp+dkxiBY1frcS07PCPgotKz8= @@ -662,6 +664,7 @@ github.com/neilotoole/errgroup v0.1.5 h1:DxEGoIfFm5ooGicidR+okiHjoOaGRKFaSxDPVZu github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid/v2 v2.0.2/go.mod h1:mtBL0Qe/0HAx6/a4Z30qxVIAL1eQDweXq5lxOEiwQ68= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= diff --git a/x/evm/keeper/receipt.go b/x/evm/keeper/receipt.go index 23ea64b380..260243754c 100644 --- a/x/evm/keeper/receipt.go +++ b/x/evm/keeper/receipt.go @@ -54,6 +54,10 @@ func (k *Keeper) DeleteTransientReceipt(ctx sdk.Context, txHash common.Hash, txI store.Delete(types.NewTransientReceiptKey(txIndex, txHash)) } +func (k *Keeper) GetLatestReceiptVersion(ctx sdk.Context) (int64, error) { + return k.receiptStore.GetLatestVersion() +} + // GetReceipt returns a data structure that stores EVM specific transaction metadata. // Many EVM applications (e.g. MetaMask) relies on being on able to query receipt // by EVM transaction hash (not Sei transaction hash) to function properly. diff --git a/x/evm/keeper/receipt_test.go b/x/evm/keeper/receipt_test.go index 191ea91459..8fe75b88b5 100644 --- a/x/evm/keeper/receipt_test.go +++ b/x/evm/keeper/receipt_test.go @@ -52,6 +52,7 @@ func TestGetReceiptWithRetry(t *testing.T) { func TestFlushTransientReceiptsSync(t *testing.T) { k := &testkeeper.EVMTestApp.EvmKeeper ctx := testkeeper.EVMTestApp.GetContextForDeliverTx([]byte{}) + ctx = ctx.WithBlockHeight(10) txHash := common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef") receipt := &types.Receipt{TxHashHex: txHash.Hex(), Status: 1} @@ -72,6 +73,10 @@ func TestFlushTransientReceiptsSync(t *testing.T) { err = k.FlushTransientReceiptsSync(ctx) require.NoError(t, err) + version, err := k.GetLatestReceiptVersion(ctx) + require.NoError(t, err) + require.Equal(t, int64(10), version) + // Now should be retrievable from persistent store pr, err := k.GetReceipt(ctx, txHash) require.NoError(t, err) @@ -81,9 +86,18 @@ func TestFlushTransientReceiptsSync(t *testing.T) { _, _ = k.GetTransientReceipt(ctx, txHash, 0) // Could be not found or still present depending on flush logic, so we don't assert error here + ctx = ctx.WithBlockHeight(11) + version, err = k.GetLatestReceiptVersion(ctx) + require.NoError(t, err) + require.Equal(t, int64(10), version) // should still be 10 because we haven't flushed yet + // Flushing with no receipts should not error err = k.FlushTransientReceiptsSync(ctx) require.NoError(t, err) + + version, err = k.GetLatestReceiptVersion(ctx) + require.NoError(t, err) + require.Equal(t, int64(11), version) // now should be 11 because we flushed } func TestDeleteTransientReceipt(t *testing.T) { From 5575b54cc0a2f66b1cd18fe4e3319daebbaffc0e Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Thu, 2 Oct 2025 16:48:15 -0500 Subject: [PATCH 2/4] Serve existing errors first --- evmrpc/filter.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/evmrpc/filter.go b/evmrpc/filter.go index d7bad92edb..086c008e05 100644 --- a/evmrpc/filter.go +++ b/evmrpc/filter.go @@ -531,6 +531,18 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r begin = end } } + blockRange := end - begin + 1 + + // Use config value instead of hardcoded constant + if blockRange > a.filterConfig.maxBlock { + return nil, fmt.Errorf("block range too large (%d), maximum allowed is %d blocks", blockRange, a.filterConfig.maxBlock) + } + + // Only apply rate limiting for large queries (> RPSLimitThreshold blocks) + if blockRange > RPSLimitThreshold && !a.globalRPSLimiter.Allow() { + return nil, fmt.Errorf("log query rate limit exceeded for large queries, please try again later") + } + if begin > latestReceiptVersion || end > latestReceiptVersion { return nil, fmt.Errorf("block range includes unavailable block(s)") } @@ -544,18 +556,6 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r } } - blockRange := end - begin + 1 - - // Use config value instead of hardcoded constant - if blockRange > a.filterConfig.maxBlock { - return nil, fmt.Errorf("block range too large (%d), maximum allowed is %d blocks", blockRange, a.filterConfig.maxBlock) - } - - // Only apply rate limiting for large queries (> RPSLimitThreshold blocks) - if blockRange > RPSLimitThreshold && !a.globalRPSLimiter.Allow() { - return nil, fmt.Errorf("log query rate limit exceeded for large queries, please try again later") - } - logs, _, err := a.logFetcher.GetLogsByFilters(ctx, crit, 0) if err != nil { return nil, err From c8f294337d4ebec5af8a2f37f2111008aa110cf7 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Thu, 2 Oct 2025 16:55:16 -0500 Subject: [PATCH 3/4] fix test --- evmrpc/filter_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/evmrpc/filter_test.go b/evmrpc/filter_test.go index b412ed61bd..4c6b147be3 100644 --- a/evmrpc/filter_test.go +++ b/evmrpc/filter_test.go @@ -447,7 +447,6 @@ func TestGetLogsBlockHashIsNotZero(t *testing.T) { } func TestFilterGetLogsBlockHashNotYetAvailable(t *testing.T) { - t.Parallel() // Query for a block hash that corresponds to a block height (200) // that is greater than the latest receipt version (103) // This should return an error saying the block hash isn't available yet @@ -467,7 +466,6 @@ func TestFilterGetLogsBlockHashNotYetAvailable(t *testing.T) { } func TestFilterGetLogsBlockRangeIncludesUnavailableBlock(t *testing.T) { - t.Parallel() // Query for a block range where toBlock (200) is greater than the latest receipt version (103) // This tests that querying a range with unavailable blocks doesn't cause issues // and returns logs only for available blocks or returns an appropriate error @@ -493,7 +491,6 @@ func TestFilterGetLogsBlockRangeIncludesUnavailableBlock(t *testing.T) { } func TestFilterGetLogsBlockRangePartiallyAvailable(t *testing.T) { - t.Parallel() // Query for a block range that spans both available and unavailable blocks // fromBlock: 100 (available), toBlock: 105 (should be available since it's < 200) From a9ab9929d8ded96fda334258df62389ddf9571eb Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Thu, 2 Oct 2025 16:57:02 -0500 Subject: [PATCH 4/4] Add nil check --- evmrpc/filter.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evmrpc/filter.go b/evmrpc/filter.go index 086c008e05..89df7d0f20 100644 --- a/evmrpc/filter.go +++ b/evmrpc/filter.go @@ -551,6 +551,9 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r if err != nil { return nil, err } + if header == nil || header.Header == nil { + return nil, fmt.Errorf("block hash %s not found", crit.BlockHash.Hex()) + } if header.Header.Height > latestReceiptVersion { return nil, fmt.Errorf("block hash %s isn't available yet", crit.BlockHash.Hex()) }