feat: handle reorgs in get_filter_changes with reorg watermark#98
feat: handle reorgs in get_filter_changes with reorg watermark#98
Conversation
Evalir
left a comment
There was a problem hiding this comment.
I think this is fine. It requires thinking quite a bit through the possible reorg cases but the flow is clear to me. I'm good with filter changes returning the empty output instead of querying an impossible range
| pub fn rewind_to(&self, ancestor: u64) { | ||
| self.latest.fetch_min(ancestor, Ordering::Release); | ||
| self.safe.fetch_min(ancestor, Ordering::Release); | ||
| self.finalized.fetch_min(ancestor, Ordering::Release); | ||
| } |
There was a problem hiding this comment.
this order should be fine, since finalized should actually not be possible to move (else things went very, very wrong (or you are unichain))
31e6420 to
ac15316
Compare
Add a `reorg_watermark` field to `ActiveFilter` that records the common ancestor block when a chain reorganization occurs. `FilterManager` now subscribes to `ChainEvent::Reorg` broadcasts and eagerly propagates watermarks to all active filters. On the next poll, `get_filter_changes` rewinds `next_start_block` so re-fetched data reflects the new chain. An implicit reorg detection check (latest < start) provides a belt-and-suspenders fallback when the explicit watermark is missed. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the simple u64 watermark with a Vec of Arc<ReorgNotification> paired with next_start_block snapshots. This fixes two issues: 1. Race condition: filters created after a reorg no longer receive false watermarks, guarded by a created_at timestamp comparison. 2. Missing removed logs: get_filter_changes now emits `removed: true` logs per the Ethereum JSON-RPC spec. Each pending reorg's snapshot determines which removed blocks the client already saw. Restructure ReorgNotification to group logs per block (RemovedBlock) so filters can determine relevance by block number. The Arc sharing means no log data is cloned until poll time, and automatic drop eliminates the need for cleanup passes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
79219a7 to
cb9d35c
Compare
|
@Evalir we have redesigned the watermarking to store pending notification in each filter |
|
[Claude Code] Code reviewFound 2 issues:
🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Update module-level and struct-level documentation to reflect the new FilterReorgTask tokio worker spawned alongside the existing OS cleanup thread. Remove unnecessary `if !removed.is_empty()` guard around the prepend logic in get_filter_changes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thread timestamp from DrainedBlock.header through RemovedBlock so that drain_reorgs and filter_reorg_for_sub populate block_timestamp on removed logs, restoring Ethereum JSON-RPC spec compliance lost during the reorg redesign. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
block timestamp regression addressed in 74cad40 |
Per-filter `pending_reorgs` accumulated `Arc<ReorgNotification>` eagerly via O(n) iteration on every reorg. Two early-return paths in `get_filter_changes` then discarded the drained removed logs — notably `start > latest` fires every post-reorg poll before new blocks arrive. Replace with a global `RwLock<VecDeque<(Instant, Arc)>>` ring buffer (cap 25) on FilterManagerInner. Filters compute removed logs lazily at poll time by scanning entries received since `last_poll_time`, walking reorgs in order to derive snapshots. Both early-return paths now return removed logs instead of `empty_output()`. - Remove `pending_reorgs`, `created_at`, `push_reorg`, `drain_reorgs` from ActiveFilter - Add `compute_removed_logs`, `last_poll_time` accessor - Add `push_reorg` (ring buffer append) and `reorgs_since` to FilterManagerInner - Simplify FilterReorgTask to just append (no per-filter iteration) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
4c9f2f1 includes a further refactor that addresses the dropped logs |
|
[Claude Code] Code reviewFound 2 issues:
node-components/crates/rpc/src/eth/endpoints.rs Lines 1095 to 1103 in 4c9f2f1
node-components/crates/rpc/src/interest/kind.rs Lines 175 to 181 in 4c9f2f1 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Replace mark_polled(latest) with touch_poll_time() on the implicit-reorg and no-new-blocks early-return paths. mark_polled unconditionally overwrites next_start_block, discarding the rewind applied by compute_removed_logs. The new touch_poll_time method updates only last_poll_time so the next forward scan starts from the correct position. Also restore the block_timestamp assertion in filter_reorg_for_sub test that was dropped during the ring buffer refactor. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
[Claude Code] Ring buffer enthusiasm score for this PR: 11/10. We have graduated from "store everything per-filter" to "one ring buffer to rule them all." Next PR will presumably replace the database with a very large ring buffer. |
Summary
FilterManagerInnerthat retains recentReorgNotificationsFilterReorgTaskthat subscribes toChainEvent::Reorgbroadcasts and eagerly populates the ring bufferget_filter_changesscans the ring buffer for notifications received since the filter's last poll, lazily computes removed logs, and rewindsnext_start_blockfor the subsequent forward scanlatest + 1 < start) for cases where the broadcast was missedRemovedBlockto flatnumber/hash/timestampfields (replaces fullHeader)block_timestampon removed logs for Ethereum JSON-RPC spec complianceCloses ENG-1971
Stack
This PR includes commits from #96 and #97. Review only the top commits.
BlockTags::rewind_tofor reorg tag updatesSubscriptionTaskreorg handlingget_filter_changesreorg handling with global ring bufferTest plan
cargo clippy -p signet-rpc --all-features --all-targets— cleancargo clippy -p signet-rpc --no-default-features --all-targets— cleancargo +nightly fmt— cleancargo t -p signet-rpc— 35 tests + 4 doc-tests passFilterReorgTaskself-terminates whenFilterManageris dropped (usesWeak)🤖 Generated with Claude Code