diff --git a/evmrpc/filter.go b/evmrpc/filter.go index 12edd897d3..89df7d0f20 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,7 +531,6 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r begin = end } } - blockRange := end - begin + 1 // Use config value instead of hardcoded constant @@ -539,6 +543,22 @@ func (a *FilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) (r 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)") + } + if crit.BlockHash != nil { + header, err := a.tmClient.HeaderByHash(ctx, crit.BlockHash[:]) + 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()) + } + } + logs, _, err := a.logFetcher.GetLogsByFilters(ctx, crit, 0) if err != nil { return nil, err diff --git a/evmrpc/filter_test.go b/evmrpc/filter_test.go index 3b8c6b140f..4c6b147be3 100644 --- a/evmrpc/filter_test.go +++ b/evmrpc/filter_test.go @@ -446,6 +446,67 @@ func TestGetLogsBlockHashIsNotZero(t *testing.T) { } } +func TestFilterGetLogsBlockHashNotYetAvailable(t *testing.T) { + // 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) { + // 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) { + // 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) {