Skip to content

feat(cold): add eth_getLogs support with block-level log indexing#24

Merged
prestwich merged 15 commits intomainfrom
feat/cold-get-logs
Feb 13, 2026
Merged

feat(cold): add eth_getLogs support with block-level log indexing#24
prestwich merged 15 commits intomainfrom
feat/cold-get-logs

Conversation

@prestwich
Copy link
Copy Markdown
Member

@prestwich prestwich commented Feb 12, 2026

Summary

  • Adds ColdStorage::get_logs(Filter) with eth_getLogs semantics — filter by block range, address, and topics (OR within position, AND between positions)
  • Replaces custom RichLog/LogFilter types with alloy::rpc::types::Log (aliased as RpcLog) and alloy::rpc::types::Filter — eliminates redundant types in favor of standard Ethereum RPC types
  • Adds gas_used to IndexedReceipt, precomputed at append time from cumulative gas sequence — avoids prior receipt lookups at query time
  • Moves Confirmed<T> to signet-storage-types — pure data type with no cold-storage-specific deps, re-exported from signet_cold for backward compatibility
  • Replaces verbose turbofish trait calls in cold-mdbx with traverse()/traverse_dual() method syntax; zips receipt and transaction iterators in get_logs instead of per-receipt point lookups
  • Replaces single-column idx_logs_address with composite idx_logs_address_block(address, block_number) for efficient filtered range scans
  • Factors out row_to_log_row helper in the SQL backend
  • Implements for all three backends: in-memory, SQLite, PostgreSQL
  • SQL backend uses a correlated subquery on the PK index to compute block_log_index
  • Returns SealedHeader from all header queries, preserving cached block hash and eliminating redundant seal_slow() calls in backends
  • Replaces three overlapping receipt return types (Confirmed<Receipt>, IndexedReceipt, ReceiptContext) with a single ColdReceipt type containing a consensus receipt with RPC-enriched logs and full block/transaction metadata
  • BlockData now accepts SealedHeader directly, removing redundant re-sealing in both MDBX and in-memory backends

Test plan

  • In-memory conformance passes (cargo t -p signet-cold)
  • SQLite conformance passes (cargo t -p signet-cold-sql --features test-utils)
  • PostgreSQL conformance passes (./scripts/test-postgres.sh)
  • MDBX conformance passes (cargo t -p signet-cold-mdbx --all-features)
  • Unified storage passes (cargo t -p signet-storage)
  • Clippy clean with --all-features and --no-default-features for all cold/hot/storage crates
  • Conformance covers: empty ranges, block range filtering, single/multi address, single/multi topic0, multi-position AND, wildcard positions, combined filters, ordering, log_index correctness, metadata correctness, gas_used on IndexedReceipt
  • ColdReceipt metadata verified: block_number, block_hash, transaction_index, tx_hash, gas_used, log_index

Closes #23

🤖 Generated with Claude Code

prestwich and others added 3 commits February 12, 2026 10:08
Adds the index of a receipt's first log among all logs in its block,
enabling callers to compute per-log logIndex for RPC responses without
refetching prior receipts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…text

Add log filtering to cold storage following eth_getLogs semantics, and
include block-level log indexing for RPC response construction.

- Add `LogFilter` type with block range, address, and topic filters
- Add `RichLog` type with full block/tx context and block_log_index
- Add `ColdStorage::get_logs` with implementations for in-memory, SQLite,
  and PostgreSQL backends
- Add `first_log_index` to `ReceiptContext` (cherry-picked from #23)
- Replace `idx_logs_address` with composite `idx_logs_address_block`
- Factor out `row_to_log_row` helper in SQL backend
- Wire through task channel plumbing (request, handle, runner)
- Comprehensive conformance tests covering all filter combinations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
prestwich and others added 3 commits February 12, 2026 15:00
…ackend

iter_k2 had two bugs: (1) next_dual_above positioned the cursor at the
first entry and returned it, but the iterator discarded that result and
called next_k2() which advanced past it — so the first entry was never
yielded. Fix: capture the first entry in the iterator struct and yield
it before advancing. (2) The in-memory backend's next_k2 used
next_dual_above(current_k1, current_k2) which is "at or above",
returning the same entry forever. Fix: use read_next() (strictly above)
and verify k1 still matches.

Adds iter_k2 regression tests to the conformance suite.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tches_log

Move log-matching logic from a private function in mem.rs to a public
LogFilter::matches_log method for cross-backend reuse. Implement
get_logs on MdbxColdBackend using per-index exact_dual lookups. Remove
unused SQL helper methods left over from a prior refactor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…aits

The default iter() and iter_from() implementations on KvTraverse and
DualKeyTraverse discarded the entry returned by the initial positioning
call (first()/lower_bound()/next_dual_above()), causing the first entry
to be skipped. The typed iter_from() on TableTraverse and
DualTableTraverse had a second bug: they positioned the cursor then
called iter() which reset it via first().

Fix by capturing the first entry as owned data in the iterator structs
(RawKvIter, RawDualKeyIter) and yielding it before calling read_next().
Also override iter()/iter_from() on the MDBX cursor to use native
libmdbx iterators which handle first-entry capture natively.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
prestwich and others added 9 commits February 13, 2026 10:17
Replace manual exact_dual index probing with iter_k2 in
MdbxColdBackend::get_logs_inner, matching the convention used by all
other dual-table iterations in this file. Replace inner for/if/push
loops with filter/map/extend in both MDBX and in-memory backends.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-types

These are pure data types with no cold-storage-specific dependencies.
Moving them to the shared types crate makes them available to all
storage backends without depending on signet-cold. Re-exported from
signet_cold for backward compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… log iterators

Replace verbose turbofish trait calls (DualTableTraverse::<T, _>::iter_k2,
TableTraverse::<T, _>::exact) with method syntax via tx.traverse() and
tx.traverse_dual(). Zip receipt and transaction iterators in get_logs_inner
instead of doing per-receipt point lookups for transaction hashes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Precompute receipt metadata at write time to eliminate read-path
recomputation. IndexedReceipt wraps Receipt with tx_hash and
first_log_index, removing the need to join with transactions or iterate
prior receipts during queries. MDBX now stores SealedHeader (hash
alongside header bytes) to eliminate all hash_slow() calls on reads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… IndexedReceipt

Replace custom RichLog/LogFilter types with alloy::rpc::types::Log (aliased
as RpcLog) and alloy::rpc::types::Filter. This eliminates redundant types in
favor of the standard Ethereum RPC log type.

Add gas_used field to IndexedReceipt, precomputed at append time from the
cumulative gas sequence. This avoids needing prior receipt lookups at query
time, following the same pattern as first_log_index and tx_hash.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace unnecessary `Trait::method(receiver, ...)` calls with
`receiver.method(...)` where no competing trait in scope defines
the same method name.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…m queries

Return SealedHeader from all header queries to preserve the cached block
hash and eliminate redundant seal_slow() calls in backends. Replace the
three overlapping receipt types (Confirmed<Receipt>, IndexedReceipt,
ReceiptContext) with a single ColdReceipt type containing a consensus
receipt with RPC-enriched logs and full block/transaction metadata.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Accept `Recovered<TransactionSigned>` in `BlockData` so the sender
address is preserved at append time. Transaction queries now return
`Confirmed<RecoveredTx>` and `ColdReceipt` includes a `from` field,
eliminating the need for consumers to run ecrecover themselves.

- Add `sender: Address` to `IndexedReceipt` and `ColdReceipt`
- Add `ColdTxSenders` MDBX table for sender storage
- Add SQL migration for `from_address` column
- Update all backends (mem, MDBX, SQL) and conformance tests
- Add `RecoveredTx` type alias and `Recovered` re-export

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the `from_address` column from a separate migration 002 into the
initial schema (001_initial.sql / 001_initial_pg.sql) and remove the
idempotent ALTER TABLE logic from the backend constructor.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@prestwich prestwich merged commit 44f144b into main Feb 13, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant