Skip to content

Conversation

@jewei1997
Copy link
Contributor

@jewei1997 jewei1997 commented Feb 2, 2026

Summary

This PR introduces an in-memory cache layer on top of the pebbledb receipt store backend, improving GetReceipt performance for recently accessed receipts.

  • Add cachedReceiptStore wrapper that caches receipts in rotating chunks
  • Simplify FilterLogs API: change from per-block signature to range-based (fromBlock, toBlock, crit)
  • Pebble backend's FilterLogs returns ErrRangeQueryNotSupported, signaling callers to fetch receipts individually
  • Update evmrpc/filter.go with fallback logic for backends that don't support range queries
  • Simplify ReceiptStoreConfig by removing unused pebble options

The cache rotates every 500 blocks (configurable) and keeps 3 chunks, providing a sliding window of recent receipts for fast lookups.

Test plan

  • Receipt store unit tests pass
  • Cached receipt store tests verify cache hits
  • FilterLogs returns ErrRangeQueryNotSupported for pebble backend

@github-actions
Copy link

github-actions bot commented Feb 2, 2026

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedFeb 4, 2026, 3:15 PM

@codecov
Copy link

codecov bot commented Feb 2, 2026

Codecov Report

❌ Patch coverage is 66.57143% with 117 lines in your changes missing coverage. Please review.
✅ Project coverage is 46.91%. Comparing base (fe44b5d) to head (6aa9c6b).

Files with missing lines Patch % Lines
sei-db/ledger_db/receipt/receipt_log_filters.go 0.00% 38 Missing ⚠️
evmrpc/filter.go 55.22% 23 Missing and 7 partials ⚠️
sei-db/ledger_db/receipt/receipt_cache.go 74.78% 22 Missing and 7 partials ⚠️
sei-db/ledger_db/receipt/cached_receipt_store.go 85.22% 7 Missing and 6 partials ⚠️
sei-db/ledger_db/receipt/receipt_store.go 86.84% 3 Missing and 2 partials ⚠️
sei-db/ledger_db/receipt/parquet_support.go 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2788      +/-   ##
==========================================
- Coverage   46.94%   46.91%   -0.03%     
==========================================
  Files        1965     1969       +4     
  Lines      160525   160784     +259     
==========================================
+ Hits        75363    75438      +75     
- Misses      78637    78801     +164     
- Partials     6525     6545      +20     
Flag Coverage Δ
sei-chain 41.49% <66.57%> (-0.03%) ⬇️
sei-cosmos 48.12% <ø> (ø)
sei-db 68.72% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
sei-db/db_engine/pebbledb/mvcc/db.go 64.14% <100.00%> (+0.06%) ⬆️
sei-db/ledger_db/receipt/parquet_support.go 0.00% <0.00%> (ø)
sei-db/ledger_db/receipt/receipt_store.go 59.41% <86.84%> (-24.82%) ⬇️
sei-db/ledger_db/receipt/cached_receipt_store.go 85.22% <85.22%> (ø)
sei-db/ledger_db/receipt/receipt_cache.go 74.78% <74.78%> (ø)
evmrpc/filter.go 68.81% <55.22%> (-1.74%) ⬇️
sei-db/ledger_db/receipt/receipt_log_filters.go 0.00% <0.00%> (ø)

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

}

type cacheWarmupProvider interface {
warmupReceipts() []ReceiptRecord
Copy link
Contributor

Choose a reason for hiding this comment

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

Does warm up means we will load some receipts into cache during initialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes it is used for parquet to replay from WAL

s.cacheNextRotate = blockNumber + s.cacheRotateInterval
return
}
for blockNumber >= s.cacheNextRotate {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use blockNumber % interval == 0?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we could miss the rotation if the blockNumber % interval == 0 block does not have EVM receipts for some reason.

receipt, found := blockReceipts[txHash]
if found {
// Callers (e.g. RPC response formatting) may normalize TransactionIndex in-place.
// Clone to avoid mutating the cached receipt and corrupting future lookups.
Copy link
Contributor

Choose a reason for hiding this comment

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

For perf reason, do we want to default to zero copy? As long as we make sure the codebase doesn't modify the receipt after calling get, we should be able to enable zeroCopy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think there are other places we do modify the receipt actually for example:

there are multiple mutations in evmrpc/tx.go:

  Lines 154-165 (for failed txs that used 0 gas):
  receipt.From = from.Hex()
  receipt.To = etx.To().Hex()
  receipt.ContractAddress = ""
  receipt.TxType = uint32(etx.Type())
  receipt.Status = uint32(ethtypes.ReceiptStatusFailed)
  receipt.GasUsed = 0

  Line 456 (tx index normalization):
  receipt.TransactionIndex = uint32(evmTxIndex)

Copy link
Contributor

@yzang2019 yzang2019 left a comment

Choose a reason for hiding this comment

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

Overall LGTM


type receiptGetter func(ctx sdk.Context, txHash common.Hash) (*types.Receipt, error)

func filterLogsFromReceipts(ctx sdk.Context, blockHeight int64, blockHash common.Hash, txHashes []common.Hash, crit filters.FilterCriteria, applyExactMatch bool, getReceipt receiptGetter) ([]*ethtypes.Log, error) {
Copy link
Contributor

@blindchaser blindchaser Feb 9, 2026

Choose a reason for hiding this comment

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

is filterLogsFromReceipts being used?

blockHash := common.BytesToHash(block.BlockID.Hash)

// Fetch receipts individually and filter logs locally
var logIndex uint
Copy link
Contributor

Choose a reason for hiding this comment

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

it looks we are fetches every receipt and iterates every log with matchesCriteria skipping bloom filter checks. for blocks with many transactions and few matching logs, could this introduce some performance regression?

log.TxIndex = uint(evmTxIndex) //nolint:gosec
log.BlockNumber = uint64(blockHeight) //nolint:gosec
log.BlockHash = blockHash
if isLogExactMatch(log, crit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what's the difference between matchesCriteria() and isLogExactMatch()

require.NoError(t, err)
// FilterLogs now takes fromBlock, toBlock range
collectedLogs, err := store.FilterLogs(ctx, 2, 2, filters.FilterCriteria{})
// Note: this test was using pebble backend which now returns ErrRangeQueryNotSupported
Copy link
Contributor

Choose a reason for hiding this comment

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

do we have testings to cover original test's behavior like a test verified that TransactionIndex was set correctly during log collection?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants