Skip to content

feat: add ledger cache layer for receipt store#2788

Merged
jewei1997 merged 16 commits intomainfrom
ledger-cache-layer
Feb 11, 2026
Merged

feat: add ledger cache layer for receipt store#2788
jewei1997 merged 16 commits intomainfrom
ledger-cache-layer

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 11, 2026, 5:16 PM

@codecov
Copy link

codecov bot commented Feb 2, 2026

Codecov Report

❌ Patch coverage is 77.47036% with 57 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.09%. Comparing base (e31ab0c) to head (fbf9b69).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
evmrpc/filter.go 56.00% 25 Missing and 8 partials ⚠️
sei-db/ledger_db/receipt/receipt_cache.go 83.92% 6 Missing and 3 partials ⚠️
sei-db/ledger_db/receipt/cached_receipt_store.go 89.04% 4 Missing and 4 partials ⚠️
sei-db/ledger_db/receipt/receipt_store.go 86.84% 3 Missing and 2 partials ⚠️
evmrpc/tx.go 77.77% 1 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2788      +/-   ##
==========================================
- Coverage   57.12%   57.09%   -0.03%     
==========================================
  Files        2088     2090       +2     
  Lines      171079   171169      +90     
==========================================
+ Hits        97726    97734       +8     
- Misses      64680    64735      +55     
- Partials     8673     8700      +27     
Flag Coverage Δ
sei-chain 52.55% <77.47%> (-0.03%) ⬇️
sei-cosmos 48.12% <ø> (-0.01%) ⬇️
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%) ⬆️
evmrpc/tx.go 84.53% <77.77%> (-0.33%) ⬇️
sei-db/ledger_db/receipt/receipt_store.go 69.59% <86.84%> (-14.64%) ⬇️
sei-db/ledger_db/receipt/cached_receipt_store.go 89.04% <89.04%> (ø)
sei-db/ledger_db/receipt/receipt_cache.go 83.92% <83.92%> (ø)
evmrpc/filter.go 68.63% <56.00%> (-1.57%) ⬇️

... and 28 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

Choose a reason for hiding this comment

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

Hmm that's bad, is this the only place we modify receipts? Can we actually fix those places where we modify receipts directly and make that a cloned copy before we modify?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed. moved the clone receipt functionality out of ledger_db and to the modification locations

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

@jewei1997 jewei1997 enabled auto-merge (squash) February 11, 2026 17:19
@jewei1997 jewei1997 merged commit 7db2f0a into main Feb 11, 2026
40 checks passed
@jewei1997 jewei1997 deleted the ledger-cache-layer branch February 11, 2026 17:31
jewei1997 added a commit that referenced this pull request Feb 17, 2026
## Summary

This PR adds a parquet-based receipt storage backend with DuckDB for
efficient range queries on logs, enabling fast `eth_getLogs` queries
across block ranges.

- Add parquet backend option (`Backend: "parquet"` in config)
- Parquet files rotate every 500 blocks
- DuckDB queries across closed parquet files for efficient log filtering
- WAL for crash recovery of in-progress parquet files
- Pruning of old parquet files based on `KeepRecent` config
- Build tag support: use `-tags duckdb` to enable parquet backend

The parquet backend supports the new `FilterLogs` range query API
introduced in #2788, enabling efficient cross-block log queries without
falling back to per-receipt fetching.

## Dependencies

- Depends on #2788 (ledger cache layer)

## Test plan

- Receipt store unit tests pass (without duckdb tag)
- Parquet store tests pass with `-tags duckdb`
- Integration testing with full node using parquet backend
jewei1997 added a commit that referenced this pull request Feb 24, 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

- [x] Receipt store unit tests pass
- [x] Cached receipt store tests verify cache hits
- [x] FilterLogs returns `ErrRangeQueryNotSupported` for pebble backend
jewei1997 added a commit that referenced this pull request Feb 24, 2026
## Summary

This PR adds a parquet-based receipt storage backend with DuckDB for
efficient range queries on logs, enabling fast `eth_getLogs` queries
across block ranges.

- Add parquet backend option (`Backend: "parquet"` in config)
- Parquet files rotate every 500 blocks
- DuckDB queries across closed parquet files for efficient log filtering
- WAL for crash recovery of in-progress parquet files
- Pruning of old parquet files based on `KeepRecent` config
- Build tag support: use `-tags duckdb` to enable parquet backend

The parquet backend supports the new `FilterLogs` range query API
introduced in #2788, enabling efficient cross-block log queries without
falling back to per-receipt fetching.

## Dependencies

- Depends on #2788 (ledger cache layer)

## Test plan

- Receipt store unit tests pass (without duckdb tag)
- Parquet store tests pass with `-tags duckdb`
- Integration testing with full node using parquet backend
yzang2019 pushed a commit that referenced this pull request Feb 25, 2026
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.

- [x] Receipt store unit tests pass
- [x] Cached receipt store tests verify cache hits
- [x] FilterLogs returns `ErrRangeQueryNotSupported` for pebble backend
yzang2019 pushed a commit that referenced this pull request Feb 25, 2026
This PR adds a parquet-based receipt storage backend with DuckDB for
efficient range queries on logs, enabling fast `eth_getLogs` queries
across block ranges.

- Add parquet backend option (`Backend: "parquet"` in config)
- Parquet files rotate every 500 blocks
- DuckDB queries across closed parquet files for efficient log filtering
- WAL for crash recovery of in-progress parquet files
- Pruning of old parquet files based on `KeepRecent` config
- Build tag support: use `-tags duckdb` to enable parquet backend

The parquet backend supports the new `FilterLogs` range query API
introduced in #2788, enabling efficient cross-block log queries without
falling back to per-receipt fetching.

- Depends on #2788 (ledger cache layer)

- Receipt store unit tests pass (without duckdb tag)
- Parquet store tests pass with `-tags duckdb`
- Integration testing with full node using parquet backend
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.

4 participants