Skip to content

feat: per-wallet filter scan and runtime wallet catch-up#122

Merged
xdustinface merged 10 commits into
v0.42-devfrom
feat/per-wallet-filter-scan
Apr 27, 2026
Merged

feat: per-wallet filter scan and runtime wallet catch-up#122
xdustinface merged 10 commits into
v0.42-devfrom
feat/per-wallet-filter-scan

Conversation

@xdustinface
Copy link
Copy Markdown
Owner

Rebased onto v0.42-dev after #117 was squash-merged.

Per-wallet filter scan and runtime wallet catch-up implementation.

Part of the v0.42 series.

Filter matching and block processing now operate per wallet, so a wallet added at runtime catches up without disturbing the wallets that are already in sync.

`WalletInterface` is restructured around per-wallet operations:

- `process_block_for_wallets(block, height, wallets)` replaces the global `process_block` and only updates the listed wallets.
- `wallets_behind(height)` returns the wallet ids that still need filter coverage at `height`.
- `monitored_addresses_for(wallet_id)` and `wallet_synced_height(wallet_id)` give per-wallet projections for filter matching.
- `update_wallet_synced_height` and `update_wallet_last_processed_height` advance one wallet at a time and are monotonic.
- `BlockProcessingResult.new_addresses` and `CheckTransactionsResult.new_addresses` carry gap-limit discoveries with wallet attribution.

`FiltersManager.scan_batch` matches each behind wallet's addresses against the batch's filters at heights it hasn't yet covered. The per-block result flows through `BlocksNeeded` to `BlocksManager`, which processes each block only against the wallets whose filters matched it. `FiltersBatch` records the scanned wallet set so commit advances only their `synced_height`.

When a late-added wallet's filter matches a block already in flight, its id is merged into the existing entry. If the block has already been processed, it is re-queued so `BlocksManager` reloads it from local storage and processes it for the late wallet only.

`process_block_for_wallets` refreshes the cached balance even on rescan paths below the wallet's current `last_processed_height`, because UTXOs may change.
When `wallet.synced_height()` drops below `FiltersManager`'s `progress.committed_height()`, a wallet was added or moved behind the current scan position and needs catch-up coverage. Add a check at the top of `FiltersManager::tick()` that detects this regression, clears in-flight pipeline state, lowers `committed_height` to the new aggregate min, and re-enters `start_download()`. The check runs in `Syncing`, `Synced`, and `WaitForEvents` states so idle additions are caught on the next 100ms tick.

Add `test_wallet_added_at_runtime_catches_up` in `dash-spv/tests/dashd_sync/tests_basic.rs`. After initial sync with `W1`, mine a block funding `W2`'s address and add `W2` at runtime with `birth_height` before that block. Assert the rescan picks up `W2`'s funding transaction and `W1`'s state is unchanged. Then add `W3` with `birth_height` beyond the tip and assert no spurious rescan or regression in either existing wallet.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (22s)
    feature · 1128 lines · 4 agents
    review effort: medium · judge effort: high

Review — 25 findings
    ✅ Correctness & Logic — 6 (485s)
    ✅ Security & Safety — 5 (301s)
    ✅ Architecture & Design — 7 (256s)
    ✅ Testing & Coverage — 7 (142s)

Judge — 21 kept · 0 dropped (52s)
    kept: 5 warning · 6 suggestion · 10 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 1128 lines)
  • Team: Correctness & Logic, Security & Safety, Architecture & Design, Testing & Coverage
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "hash_to_wallets keyed by expected hash, but looked up by actual block hash" (nitpick, medium confidence) — "Impact: High (silent dropped wallet processing if hashes ever diverge), Likelihood: Possible (relies on upstream hash validation never being bypassed). Even though the active code path validates hashes upstream, the fragile coupling and unbounded entry leak warrant attention. Findings 1 and 7 merged — same root cause."
  • ✓ Kept: "Rescan for new addresses assumes process_block_for_wallets does not skip already-processed heights" (warning, medium confidence) — "Impact: High (missed transactions to gap-limit addresses), Likelihood: Possible (depends on implementation detail of WalletInterface impls). The contract is documented but unenforced; a future or existing height-guard would silently break address-rescan."
  • ✓ Kept: "Empty interested set silently processes block for no wallets with no diagnostic" (nitpick, medium confidence) — "Impact: Medium (debuggability), Likelihood: Unlikely under normal flow. A diagnostic log would help future debugging but the path is reachable only via bugs upstream; not a behavioral defect on its own. Single reviewer."
  • ✓ Kept: "Unrelated change: wallets.clone() in storage-hit branch is unnecessary given move semantics" (nitpick, high confidence) — "Impact: Low (one extra heap allocation per stored block), Likelihood: Certain on storage-hit path. Findings 4, 10, and 18 merged — same observation. Trivial micro-optimization."
  • ✓ Kept: "wallets_behind(batch_end + 1) over-includes wallets already fully synced to batch_end" (suggestion, medium confidence) — "Impact: Low (unnecessary iteration, behavior correct), Likelihood: Certain when wallets sit at batch_end. Reviewer admits behavior is correct; it's an off-by-one in semantics rather than output."
  • ✓ Kept: "Silent usize→u32 truncation in FFI new-address count" (nitpick, high confidence) — "Impact: Low (truncation at u32::MAX is practically unreachable for HD wallets), Likelihood: Unlikely. Findings 6 and 8 merged. Saturating cast is the conventional idiom but the realistic risk is negligible."
  • ✓ Kept: "Double active_batches lookup with bare unwrap() after a guarded lookup in rescan_batch and scan_batch" (nitpick, medium confidence) — "Impact: Low (cannot panic in single-threaded async path), Likelihood: Unlikely. Encodes a hidden invariant; adding expect() with context improves debuggability."
  • ✓ Kept: "Const-to-mutable pointer cast in FFI test code is technically UB" (nitpick, medium confidence) — "Impact: Low (test-only code), Likelihood: Unlikely (no Miri in test pipeline). The Rust provenance concern is real but limited to test code. Single reviewer."
  • ✓ Kept: "Idempotency of process_block_for_wallets is an unverified assumption enabling the re-queue path" (warning, medium confidence) — "Impact: High (duplicate balance credits if invariant violated), Likelihood: Possible. Closely related to Finding 2 — both highlight the same brittle contract. A test asserting idempotency at the trait boundary is warranted."
  • ✓ Kept: "filters_matched no longer gates deduplication but is still silently maintained" (suggestion, medium confidence) — "Impact: Low (maintainability hazard, minor overhead), Likelihood: Possible (future maintainer may misuse). Renaming/commenting clarifies the new role; not behaviorally broken."
  • ✓ Kept: "set_scanned_wallets assigns instead of merging, making the design fragile" (nitpick, medium confidence) — "Impact: Medium (could stall synced_height permanently if called twice), Likelihood: Unlikely (only one caller today). Defensive monotonicity would harden the API; current usage is correct."
  • ✓ Kept: "scan_batch clones BlockFilter data per wallet, O(wallets × batch_size) allocation" (suggestion, medium confidence) — "Impact: Medium (memory churn at scale), Likelihood: Probable as wallet count grows. Real performance concern but not a correctness issue."
  • ✓ Kept: "WalletInterface is accumulating per-wallet state inspection methods, blurring its boundary" (suggestion, medium confidence) — "Impact: Low (architectural cleanliness), Likelihood: Probable as features grow. Trait split is an open design choice, not a defect."
  • ✓ Kept: "track_block_match name does not convey its re-queue semantics" (nitpick, high confidence) — "Impact: Low (readability), Likelihood: Possible (future maintainer confusion). Doc-comment partly mitigates; rename is optional."
  • ✓ Kept: "Pipeline wallet-set merge path has no test coverage" (warning, high confidence) — "Impact: High (silent wallet drop on regression), Likelihood: Possible. The merge is a load-bearing correctness invariant for late-added wallets; lack of a direct test is a real gap."
  • ✓ Kept: "Per-wallet filter height slicing in scan_batch is not unit tested" (warning, high confidence) — "Impact: High (off-by-one would miss txs for catch-up wallets), Likelihood: Possible. The boundary key.height() > wallet_synced is the heart of catch-up correctness; coverage is warranted."
  • ✓ Kept: "No test for rescan_batch with multiple wallets contributing different addresses" (warning, high confidence) — "Impact: High (cross-wallet attribution bugs), Likelihood: Possible. Per-wallet attribution is the core new contract of rescan_batch; a multi-wallet test is needed to lock it in."
  • ✓ Kept: "test_tick_rescans_when_wallet_falls_behind_committed only tests synced_height = 0" (suggestion, medium confidence) — "Impact: Medium (off-by-one in scan_start would slip), Likelihood: Possible. A second scenario at synced_height = 150 strengthens the test; current coverage is partial but not absent."
  • ✓ Kept: "Silent empty wallet set in take_next_ordered_block causes missed transactions" (nitpick, high confidence) — "Impact: Low (test scope clarification), Likelihood: Certain. A clarifying comment is sufficient; the test still validates the download path it claims to."
  • ✓ Kept: "Advancing synced_height for behind wallets with no monitored addresses is not explicitly tested" (suggestion, medium confidence) — "Impact: Medium (forward-progress guarantee for empty wallets), Likelihood: Possible. Adding the test guards against regression of an important monotonicity property."
  • ✓ Kept: "Misleading log message: new_addresses.len() counts wallets, not addresses" (nitpick, high confidence) — "Impact: Low (log clarity only), Likelihood: Certain when path is hit. The fix in the suggested code is also what the description asks for — both totals should be logged with correct labels."

Timing:

  • Parse: 1.3s
  • Review agents: 509.2s
  • Judge: 52.0s
  • Total: 562.5s

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 27, 2026

Codecov Report

❌ Patch coverage is 97.16886% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.61%. Comparing base (53b25c4) to head (b78ba31).

Files with missing lines Patch % Lines
dash-spv/src/sync/filters/manager.rs 97.55% 15 Missing ⚠️
key-wallet-manager/src/process_block.rs 91.17% 6 Missing ⚠️
key-wallet-manager/src/matching.rs 70.58% 5 Missing ⚠️
key-wallet-manager/src/wallet_interface.rs 60.00% 2 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##           v0.42-dev     #122      +/-   ##
=============================================
+ Coverage      70.50%   70.61%   +0.10%     
=============================================
  Files            319      319              
  Lines          66758    67590     +832     
=============================================
+ Hits           47071    47726     +655     
- Misses         19687    19864     +177     
Flag Coverage Δ
core 75.81% <ø> (ø)
ffi 44.72% <100.00%> (-1.51%) ⬇️
rpc 20.00% <ø> (ø)
spv 87.21% <98.15%> (+0.54%) ⬆️
wallet 68.93% <89.51%> (+0.10%) ⬆️
Files with missing lines Coverage Δ
dash-spv-ffi/src/callbacks.rs 83.70% <100.00%> (+3.09%) ⬆️
dash-spv/src/sync/blocks/manager.rs 96.57% <100.00%> (+1.88%) ⬆️
dash-spv/src/sync/blocks/pipeline.rs 96.67% <100.00%> (+1.09%) ⬆️
dash-spv/src/sync/blocks/sync_manager.rs 82.85% <ø> (ø)
dash-spv/src/sync/events.rs 65.00% <100.00%> (+0.89%) ⬆️
dash-spv/src/sync/filters/batch.rs 97.60% <100.00%> (+0.23%) ⬆️
dash-spv/src/sync/filters/sync_manager.rs 100.00% <100.00%> (ø)
dash-spv/src/sync/mempool/sync_manager.rs 98.72% <100.00%> (ø)
key-wallet-manager/src/lib.rs 63.12% <100.00%> (+2.11%) ⬆️
key-wallet-manager/src/wallet_interface.rs 15.00% <60.00%> (+15.00%) ⬆️
... and 3 more

... and 22 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

manki-review[bot]
manki-review Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

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

Per-wallet filter scan looks well-structured, but two real risks lurk: the pipeline keys wallet sets by expected hash and looks them up by actual block hash, and the rescan path silently relies on process_block_for_wallets not short-circuiting on last_processed_height.

📊 21 findings (5 warning, 6 suggestion, 10 nitpick) · 1128 lines · 563s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 562507,
  "diffLines": 1128,
  "diffAdditions": 858,
  "diffDeletions": 270,
  "filesReviewed": 20,
  "agents": [
    "Correctness & Logic",
    "Security & Safety",
    "Architecture & Design",
    "Testing & Coverage"
  ],
  "findingsRaw": 25,
  "findingsKept": 21,
  "findingsDropped": 4,
  "severity": {
    "blocker": 0,
    "warning": 5,
    "suggestion": 6,
    "nitpick": 10
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 122,
  "commitSha": "bf2f2241df18f0aef560d01f3498792d6792ce55",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 6,
      "findingsKept": 5,
      "responseLength": 7095
    },
    {
      "name": "Security & Safety",
      "findingsRaw": 5,
      "findingsKept": 4,
      "responseLength": 5266
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 7,
      "findingsKept": 7,
      "responseLength": 8574
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 7,
      "findingsKept": 7,
      "responseLength": 8262
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 8,
      "medium": 13,
      "low": 0
    },
    "severityChanges": 21,
    "mergedDuplicates": 4,
    "defensiveHardeningCount": 4,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 20
    },
    "findingsPerFile": {
      "dash-spv/src/sync/blocks/pipeline.rs": 3,
      "dash-spv/src/sync/filters/manager.rs": 12,
      "dash-spv/src/sync/blocks/manager.rs": 2,
      "dash-spv/src/sync/blocks/sync_manager.rs": 1,
      "dash-spv-ffi/src/callbacks.rs": 1,
      "key-wallet-ffi/src/wallet_manager_tests.rs": 1,
      "dash-spv/src/sync/filters/batch.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/blocks/pipeline.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Encapsulate the off-by-one between `wallets_behind` (strict less-than)
and the inclusive height semantics needed by `scan_batch`, so call
sites no longer need to compensate with `saturating_add(1)`.
Single-wallet `MockWallet` cannot exercise paths that hinge on multiple
wallets having distinct `synced_height` values or independent address
sets. `MultiMockWallet` holds per-wallet state keyed by `WalletId`.
Resolves several correctness issues in the per-wallet filter scan and
catch-up paths:

- \`track_block_match\` now distinguishes three states (\`NewlyTracked\`,
  \`InFlight\`, \`AlreadyProcessed\`) instead of a boolean. A block that
  was already processed in a prior round is no longer silently re-queued
  for re-processing, and a block still in flight still re-emits
  \`BlocksNeeded\` so the \`BlocksPipeline\` merges late-arriving wallet
  ids into its pending wallet set.
- \`scan_batch\` only adds wallets that contributed addresses to the
  scan into \`scanned_wallets\`. Empty-address wallets no longer have
  their \`synced_height\` advanced for free.
- \`rescan_batch\` no longer drops new wallet ids when the matching
  block is in flight; it forwards the wallet ids through a fresh
  \`BlocksNeeded\`, which the pipeline merges into its pending set.
- \`tick\` resets \`committed_height\` to the lowest \`synced_height\` of
  the stale wallets only, instead of the global wallet \`synced_height\`,
  so already-synced wallets are not re-scanned from scratch.
- \`scan_batch\` runs the filters once over the union of behind-wallet
  addresses, then attributes each match per-wallet by re-testing the
  matched filter against that wallet's scripts. Cost drops from
  O(N_wallets * batch_size) to O(batch_size + N_wallets * matches),
  and the per-batch borrow of \`active_batches\` is consolidated.
- Field \`filters_matched\` renamed to \`matched_block_hashes\` to
  reflect its actual role as a record (not a deduplication gate).
- \`rescan_batch\` now takes \`addresses_by_wallet\` by reference,
  saving a clone per later-batch rescan in \`try_commit_batches\`.
- Uses \`wallets_not_yet_at(batch_end)\` to express the inclusive
  height intent, hiding the \`+ 1\` off-by-one at the call site.
…eline`

Cover the previously-implicit contract that `queue` and
`add_from_storage` merge wallet sets per block hash, and that
`take_next_ordered_block` returns the merged wallet set.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (29s)
    feature · 1695 lines · 4 agents
    review effort: medium · judge effort: high

Review — 22 findings
    ✅ Security & Safety — 5 (257s)
    ✅ Correctness & Logic — 5 (819s)
    ✅ Architecture & Design — 5 (178s)
    ✅ Testing & Coverage — 7 (159s)

Judge — 16 kept · 3 dropped (48s)
    kept: 6 warning · 5 suggestion · 5 nitpick
    dropped: 2 warning · 1 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 1695 lines)
  • Team: Security & Safety, Correctness & Logic, Architecture & Design, Testing & Coverage
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "filter.match_any errors silently treated as non-match, causing missed transactions" (warning, high confidence) — "Impact: High (silent transaction miss in SPV correctness gate), Likelihood: Possible (requires malformed/corrupt filter data). The unwrap_or(false) hides errors with no operational signal — one reviewer, but the concern is legitimate for an SPV correctness path."
  • ✓ Kept: "AlreadyProcessed blocks silently skipped for runtime-added wallets within current session" (warning, high confidence) — "Impact: High (silent history gap for runtime-added wallets — directly contradicts PR's stated 'runtime wallet catch-up' goal), Likelihood: Probable (any wallet imported mid-session). Two reviewers flagged this independently (findings 2, 7, 11). Keep at warning given the PR explicitly advertises this capability."
  • ✓ Kept: "Panic via .expect() in hot scan_batch attribution loop" (nitpick, high confidence) — "Impact: Medium (defensive hardening of an invariant currently held by upstream call), Likelihood: Unlikely (invariant holds today). Reasonable hardening but not urgent."
  • ✓ Kept: "unwrap_or_default() in take_next_ordered_block silently processes block for zero wallets" (nitpick, high confidence) — "Impact: Medium (silent invariant violation if a future caller bypasses queue/add_from_storage), Likelihood: Unlikely. A warn log in the fallback is a low-cost hardening."
  • ✓ Kept: "usize-to-u32 cast for total_new_addresses can silently truncate on 64-bit hosts" (nitpick, high confidence) — "Impact: Low (truncation only above 4B addresses), Likelihood: Unlikely. Pure cosmetic hardening of an FFI boundary; cheap to apply but not behaviorally relevant."
  • ✓ Kept: "Wallets with no monitored addresses never advance synced_height, causing perpetual redundant scanning" (warning, high confidence) — "Impact: Medium-High (O(batches × empty-wallets) wasted work plus the wallet is structurally stuck behind), Likelihood: Probable (newly created wallets pre-gap-fill). Two reviewers flagged independently (6, 12). Consensus warning is correct."
  • ✓ Kept: "set_scanned_wallets call in scan_batch silently no-ops if the batch was concurrently removed" (nitpick, medium confidence) — "Impact: High if it fired (no wallet heights advance for that batch), Likelihood: Unlikely (single-task async, no concurrent removal). Downgraded from warning since current control flow guarantees the batch is present; an expect/log fallback is cheap defensive coding."
  • ✓ Kept: "Updated block-manager tests pass empty wallet sets, no longer exercising per-wallet routing in process_block_for_wallets" (warning, high confidence) — "Impact: Medium (test passes vacuously and doesn't validate the new per-wallet path), Likelihood: Certain (test currently passes nothing through the routing). Two reviewers flagged (9, 16). Real coverage gap on the headline feature."
  • ✓ Kept: "rescan_batch log loses total address count after per-wallet restructure" (nitpick, high confidence) — "Impact: Low (debugging convenience), Likelihood: Certain. Cosmetic logging change."
  • ✗ Dropped: "AlreadyProcessed case permanently drops late-joining wallets from processed blocks" (ignore, high confidence) — "Merged with finding 2/7 — same underlying issue (runtime wallet catch-up gap for already-processed blocks) flagged by a third reviewer."
  • ✓ Kept: "BlocksNeeded event is overloaded as both a download request and a wallet-set merge signal" (suggestion, medium confidence) — "Impact: Medium (API clarity / coupling), Likelihood: N/A — design quality. One reviewer; legitimate design feedback open for discussion."
  • ✓ Kept: "scan_batch clones entire filtered BlockFilter map into a local relevant HashMap" (suggestion, high confidence) — "Impact: Medium (transient O(batch × filter) allocation negates part of the union-pass win), Likelihood: Certain. Reasonable optimization suggestion; unchanged from prior review thread."
  • ✗ Dropped: "Tracing log in blocks/manager.rs reports wallet count as address-count proxy" (ignore, high confidence) — "Duplicate misread: the variables are correct — new_addresses_total is total addresses, new_addresses.len() is wallet count, both are passed in the right order in the format string. The reviewer's complaint that .len() is presented as wallet count is actually what the message intends."
  • ✗ Dropped: "test_blocks_manager_handles_blocks_needed_event passes empty wallet set, skipping per-wallet routing" (ignore, high confidence) — "Duplicate of finding 9 — same test issue flagged by another reviewer."
  • ✓ Kept: "No test for BlockTrackResult::InFlight path — late-arriving wallet wallet IDs are not merged" (warning, high confidence) — "Impact: Medium-High (the actual catch-up mechanism the PR title advertises is untested), Likelihood: Certain coverage gap. Keep at warning — this is the headline feature with no test."
  • ✓ Kept: "No test for rescan_batch with multiple wallets contributing disjoint addresses" (warning, high confidence) — "Impact: Medium (incorrect attribution would silently broaden block routing), Likelihood: Certain coverage gap. Carries over from previous review unaddressed."
  • ✓ Kept: "scan_batch union+attribution false-positive elimination is not tested" (suggestion, medium confidence) — "Impact: Medium (false-positive filtering correctness), Likelihood: Certain coverage gap. Test would be hard to construct deterministically given GCS probabilistic nature."
  • ✓ Kept: "Behind-wallet with zero monitored addresses is not excluded from synced_height advancement in a dedicated test" (suggestion, high confidence) — "Impact: Low-Medium (regression detection), Likelihood: Certain coverage gap. Useful focused test, low priority."
  • ✓ Kept: "track_block_match state transitions are not directly unit tested" (suggestion, high confidence) — "Impact: Low (covered indirectly), Likelihood: Certain. Nice-to-have unit test, low priority."

Timing:

  • Parse: 4.4s
  • Review agents: 853.2s
  • Judge: 48.1s
  • Total: 905.8s

@manki-review manki-review Bot dismissed their stale review April 27, 2026 07:06

Superseded by new review

manki-review[bot]
manki-review Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

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

Two reviewers independently flag the same data-integrity gap: wallets added mid-session silently miss any block already in matched_block_hashes (findings 2, 7, 11), and zero-address behind-wallets perpetually re-trigger scan work without ever advancing synced_height (findings 6, 12). Several previous threads remain unaddressed — particularly the missing tests for InFlight re-emission, per-wallet rescan attribution, and the union+attribution false-positive elimination.

📊 16 findings (6 warning, 5 suggestion, 5 nitpick) · 1695 lines · 906s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 905806,
  "diffLines": 1695,
  "diffAdditions": 1411,
  "diffDeletions": 284,
  "filesReviewed": 20,
  "agents": [
    "Security & Safety",
    "Correctness & Logic",
    "Architecture & Design",
    "Testing & Coverage"
  ],
  "findingsRaw": 22,
  "findingsKept": 16,
  "findingsDropped": 6,
  "severity": {
    "blocker": 0,
    "warning": 6,
    "suggestion": 5,
    "nitpick": 5
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 122,
  "commitSha": "1ab329241813fbe9c9805dc9f9149ecd612754d4",
  "agentMetrics": [
    {
      "name": "Security & Safety",
      "findingsRaw": 5,
      "findingsKept": 5,
      "responseLength": 5522
    },
    {
      "name": "Correctness & Logic",
      "findingsRaw": 5,
      "findingsKept": 4,
      "responseLength": 7105
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 5,
      "findingsKept": 3,
      "responseLength": 6657
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 7,
      "findingsKept": 6,
      "responseLength": 5776
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 16,
      "medium": 3,
      "low": 0
    },
    "severityChanges": 19,
    "mergedDuplicates": 3,
    "defensiveHardeningCount": 3,
    "verdictReason": "novel_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 20
    },
    "findingsPerFile": {
      "dash-spv/src/sync/filters/manager.rs": 12,
      "dash-spv/src/sync/blocks/pipeline.rs": 1,
      "dash-spv-ffi/src/callbacks.rs": 1,
      "dash-spv/src/sync/blocks/manager.rs": 1,
      "dash-spv/src/sync/events.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/blocks/manager.rs Outdated
Comment thread dash-spv/src/sync/events.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs Outdated
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Tighten `scan_batch` so wallets at `synced == batch_end` are skipped
(use `wallets_behind(batch_end)` instead of `wallets_not_yet_at`),
ensure behind wallets with zero monitored addresses still advance their
`synced_height` at commit, and surface filter errors and
`AlreadyProcessed` skips through `tracing::warn!` instead of silent
fallthrough.

Add a `min_height` parameter to `check_compact_filters_for_addresses`
so the union pass can skip irrelevant heights without cloning the
filter map. Reuse the existing `batch_filters` borrow for attribution.

Add tests covering:
- `scan_batch` advancing zero-address wallets
- `BlockTrackResult::InFlight` re-emission for late-arriving wallets
- `BlockTrackResult::AlreadyProcessed` skip path
- Union+attribute false-positive elimination
- Rescan from non-zero `synced_height` (not just genesis)
- `BlocksManager::process_buffered_blocks` routing the wallet set
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (21s)
    feature · 2015 lines · 4 agents
    review effort: medium · judge effort: high

Review — 18 findings
    ✅ Correctness & Logic — 4 (987s)
    ✅ Architecture & Design — 5 (382s)
    ✅ Testing & Coverage — 5 (161s)
    ✅ Performance & Efficiency — 4 (178s)

Judge — 17 kept · 0 dropped (82s)
    kept: 2 blocker · 5 warning · 6 suggestion · 4 nitpick

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 2015 lines)
  • Team: Correctness & Logic, Architecture & Design, Testing & Coverage, Performance & Efficiency
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "InFlight block re-emission calls coordinator.enqueue() for already-queued hashes" (blocker, medium confidence) — "Impact: High (corrupts coordinator pending count, possible duplicate peer requests), Likelihood: Probable (every InFlight re-emission path triggered by runtime wallet catch-up). The fix is concrete and the test gap confirms the regression risk."
  • ✓ Kept: "Attribution pass uses expect() — panics if check_compact_filters_for_addresses returns a key absent from batch_filters" (nitpick, high confidence) — "Impact: Critical if hit (panic loses sync progress), Likelihood: Unlikely under current invariants but in a hot path. Warn-and-skip is safer for a node component."
  • ✓ Kept: "scanned_wallets is empty for storage-loaded blocks, silently skipping per-wallet synced_height advance" (blocker, medium confidence) — "Impact: High (perpetual rescans of already-committed ranges, wasted bandwidth/CPU), Likelihood: Probable (any restart with cached blocks hits this path). Concrete data-flow bug with a clear fix."
  • ✓ Kept: "total_new_addresses silently truncates from usize to u32 in FFI callback" (nitpick, high confidence) — "Impact: Medium if hit, Likelihood: Unlikely (4B addresses unrealistic in single callback). Saturating cast is trivial hardening but practical risk is negligible."
  • ✓ Kept: "Duplicated track_block_match + BlocksNeeded emission loop across scan_batch and rescan_batch" (suggestion, high confidence) — "Impact: Medium (drift risk between two paths handling same protocol invariants), Likelihood: Probable over time. Refactor is reasonable but optional."
  • ✓ Kept: "scan_batch method accumulates too many responsibilities" (suggestion, high confidence) — "Impact: Medium (maintainability, borrow gymnastics indicate structural smell), Likelihood: Certain on read. Style/architecture suggestion."
  • ✓ Kept: "BlockTrackResult encodes asymmetric caller obligations in comments rather than in types" (suggestion, medium confidence) — "Impact: Low-Medium (future call sites must rediscover invariants), Likelihood: Possible. Reasonable API improvement."
  • ✓ Kept: "Unnecessary .clone() on owned values in BlocksNeeded handler in sync_manager.rs" (nitpick, high confidence) — "Impact: Low (extra BTreeSet alloc per block), Likelihood: Certain. Easy cleanup but not behavioral. Duplicate of finding 16."
  • ✓ Kept: "test_process_buffered_blocks_routes_wallet_set only tests positive routing, not exclusion" (warning, high confidence) — "Impact: Medium-High (the central routing invariant of the PR is half-tested), Likelihood: Certain that broken implementations could pass. Adding excluded-wallet assertion is straightforward."
  • ✓ Kept: "AlreadyProcessed path in scan_batch has no test coverage" (warning, high confidence) — "Impact: High (silent data-loss path for runtime-added wallets), Likelihood: Possible. Multiple reviewers (open thread PRRT_kwDOQSlaXs59v7R-) flagged related concerns. Coverage gap for a documented data-loss branch."
  • ✓ Kept: "BlockTrackResult::InFlight wallet-merge path has no test" (warning, high confidence) — "Impact: High (untested mechanism for runtime catch-up of in-flight downloads), Likelihood: Probable. Open thread PRRT_kwDOQSlaXs59v7Sc tracks the same gap."
  • ✓ Kept: "FFI callback behavioral changes in callbacks.rs have no test updates" (suggestion, medium confidence) — "Impact: Medium (count semantics changed for C consumers), Likelihood: Possible regressions. Tests would prevent silent C-ABI count drift."
  • ✓ Kept: "rescan_batch uses min_synced=0 without per-wallet height filtering, no test for multi-wallet attribution" (warning, medium confidence) — "Impact: High (spurious matches route to AlreadyProcessed and silently drop), Likelihood: Possible when wallets have differing synced_heights during rescan. Real interaction bug worth addressing."
  • ✓ Kept: "Script bytes re-allocated per (block × wallet) in attribution loop" (suggestion, high confidence) — "Impact: Low-Medium (M×N allocations in attribution loop), Likelihood: Certain on hot path. Pre-serialize per wallet is a clean optimization."
  • ✓ Kept: "rescan_batch runs one full filter-scan pass per wallet instead of union-then-attribute" (suggestion, high confidence) — "Impact: Medium (O(N×batch) vs O(batch+N×matches) when many wallets add addresses), Likelihood: Probable. Consistency improvement with scan_batch."
  • ✓ Kept: "Unnecessary wallets.clone() in mutually exclusive branches" (nitpick, high confidence) — "Impact: Low (extra alloc per block per event), Likelihood: Certain. Trivial cleanup. Duplicate of finding 8."
  • ✓ Kept: "Runtime-added wallets that hit AlreadyProcessed have no catch-up mechanism" (warning, medium confidence) — "Impact: High (unrecoverable missed blocks for runtime wallets if storage doesn't retain), Likelihood: Possible. A surfaced event/error would let the app layer prompt rescan."

Timing:

  • Parse: 4.0s
  • Review agents: 1011.4s
  • Judge: 82.2s
  • Total: 1097.6s

@manki-review manki-review Bot dismissed their stale review April 27, 2026 07:38

Superseded by new review

manki-review[bot]
manki-review Bot previously requested changes Apr 27, 2026
Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

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

New round adds attribution-pass safety concerns and runtime catch-up gaps. Finding 1 (re-enqueue corrupting coordinator state) and finding 3 (storage-loaded blocks never advance per-wallet synced_height) are the load-bearing correctness issues; the rest are real but lower severity.

📊 17 findings (2 blocker, 5 warning, 6 suggestion, 4 nitpick) · 2015 lines · 1098s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 1097640,
  "diffLines": 2015,
  "diffAdditions": 1720,
  "diffDeletions": 295,
  "filesReviewed": 22,
  "agents": [
    "Correctness & Logic",
    "Architecture & Design",
    "Testing & Coverage",
    "Performance & Efficiency"
  ],
  "findingsRaw": 18,
  "findingsKept": 17,
  "findingsDropped": 1,
  "severity": {
    "blocker": 2,
    "warning": 5,
    "suggestion": 6,
    "nitpick": 4
  },
  "verdict": "REQUEST_CHANGES",
  "prNumber": 122,
  "commitSha": "62a70d92c393eb2f31d1b0c660e2b5584f936ceb",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 4,
      "findingsKept": 4,
      "responseLength": 5384
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 5,
      "findingsKept": 4,
      "responseLength": 5905
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 5,
      "findingsKept": 5,
      "responseLength": 5865
    },
    {
      "name": "Performance & Efficiency",
      "findingsRaw": 4,
      "findingsKept": 4,
      "responseLength": 4814
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 11,
      "medium": 6,
      "low": 0
    },
    "severityChanges": 17,
    "mergedDuplicates": 0,
    "defensiveHardeningCount": 1,
    "verdictReason": "required_present"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 21,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv/src/sync/blocks/pipeline.rs": 1,
      "dash-spv/src/sync/filters/manager.rs": 11,
      "dash-spv-ffi/src/callbacks.rs": 2,
      "dash-spv/src/sync/blocks/sync_manager.rs": 2,
      "dash-spv/src/sync/blocks/manager.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

Comment thread dash-spv/src/sync/blocks/pipeline.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv-ffi/src/callbacks.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
Comment thread dash-spv/src/sync/filters/manager.rs
`BlocksPipeline::queue` unconditionally called `coordinator.enqueue` for every entry, including hashes that were already pending or in flight from a prior call. When a late-arriving wallet match for an already-queued block was emitted as `BlocksNeeded`, the duplicate `enqueue` corrupted the coordinator's pending count and could trigger a duplicate request to the peer. Only enqueue when the hash is not yet tracked, while still merging the late wallet ids into the per-block wallet set.
`scan_batch` recorded the per-batch `scanned_wallets` set only after the empty-filters early return, so a batch that hit that path was committed with an empty set and the per-wallet `synced_height` never advanced for that range. The next tick relisted the same wallets via `wallets_behind` and re-scanned the same range. Move the `set_scanned_wallets` call ahead of the empty-filter and empty-address fast paths so every behind wallet's `synced_height` advances when the batch commits.

Also pass each wallet's own `synced_height` as `min_synced` to `check_compact_filters_for_addresses` in `rescan_batch`. Otherwise a new address could spuriously match heights the wallet has already processed and `track_block_match` would route the result into `AlreadyProcessed` and silently drop it.
Add a `BlocksManager` test that asserts a wallet absent from the pipeline's interested set never receives `process_block_for_wallets`, complementing the existing positive routing test. Add `dash-spv-ffi` callback tests covering the two behavioural changes of this PR: `BlocksNeeded` dispatch reports unique `FilterMatchKey` count (not inflated by the per-block wallet attribution), and `BlockProcessed` dispatch reports the total address count summed across the per-wallet `new_addresses` map.
@manki-review
Copy link
Copy Markdown

manki-review Bot commented Apr 27, 2026

Manki — Review complete

Planner (26s)
    feature · 2321 lines · 4 agents
    review effort: medium · judge effort: high

Review — 13 findings
    ✅ Correctness & Logic — 4 (546s)
    ✅ Architecture & Design — 0 (309s)
    ✅ Testing & Coverage — 5 (185s)
    ✅ Dependencies & Integration — 4 (354s)

Judge — 2 kept · 3 dropped (217s)
    kept: 2 nitpick
    dropped: 2 warning · 1 suggestion

Review metadata

Config:

  • Models: reviewer=claude-sonnet-4-6, judge=claude-opus-4-7
  • Review level: large (auto, 2321 lines)
  • Team: Correctness & Logic, Architecture & Design, Testing & Coverage, Dependencies & Integration
  • Memory: disabled
  • Nit handling: issues

Judge decisions:

  • ✓ Kept: "add_from_storage omits hash_to_height update, breaking queue's dedup guard for same-hash re-queues" (nitpick, high confidence) — "Real consistency gap: storage-loaded blocks bypass the hash_to_height invariant that queue() relies on for dedup. However, FiltersManager.track_block_match acts as the primary defense (returning AlreadyProcessed/InFlight before BlocksNeeded is emitted), so this is a belt-and-suspenders fix rather than a live bug. Impact: Medium (redundant download + possible re-process), Likelihood: Unlikely (upstream guard prevents the trigger) → suggestion."
  • ✗ Dropped: "hash_to_height grows unboundedly — never pruned after take_next_ordered_block" (ignore, high confidence) — "Premise is incorrect: receive_block (line 154) calls self.hash_to_height.remove(&hash) when each block arrives, so entries are bounded by pending+in-flight blocks, not session-cumulative. There is no 180MB leak. Reviewer was reading the diff against an outdated mental model of where the prune happens."
  • ✓ Kept: "Static AtomicU32 sentinels in FFI dispatch tests are fragile with parallel test runs" (nitpick, high confidence) — "Each test uses its own private static within its function scope; cargo runs tests in parallel across threads but the statics aren't shared between distinct tests. The hypothetical 'future helper' concern is purely speculative future-proofing. Impact: Low (cosmetic), Likelihood: Unlikely → nitpick."
  • ✗ Dropped: "Orphaned hash_to_wallets entry during take→process window" (ignore, high confidence) — "The described mechanism is wrong. The reviewer claims hash_to_height is 'intentionally left intact' as a sentinel, so queue() would 'find the hash in hash_to_height, correctly skip re-enqueueing.' But hash_to_height is removed at receive_block (line 154), long before take_next_ordered_block, so the chain the reviewer describes (sentinel hit → orphaned wallet merge) cannot occur. The proposed is_live fix doesn't address any real failure mode in the actual code."
  • ✗ Dropped: "scan_batch truncated — set_scanned_wallets call cannot be verified" (ignore, high confidence) — "Verifiably wrong by reading manager.rs:762 — let scanned_wallets: BTreeSet<WalletId> = behind.clone(); then batch.set_scanned_wallets(scanned_wallets) at line 770. The full behind set is used, not wallet_states-derived IDs. Address-less wallets do advance synced_height; the regression the reviewer feared does not exist. This is a 'I couldn't see the diff' artifact, not a finding."

Timing:

  • Parse: 2.1s
  • Review agents: 576.6s
  • Judge: 217.4s
  • Total: 796.1s

@manki-review manki-review Bot dismissed their stale review April 27, 2026 08:09

Superseded by new review

Copy link
Copy Markdown

@manki-review manki-review Bot left a comment

Choose a reason for hiding this comment

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

Three of the five new findings rest on incorrect premises about how hash_to_height is managed: receive_block already removes entries (it's not a permanent sentinel), and scanned_wallets is verifiably set from the full behind set on line 762 — not the truncated mystery the reviewer feared. Only finding 1 (storage-loaded blocks bypass the dedup invariant) is a clean catch worth a defensive fix.

📊 2 findings (2 nitpick) · 2321 lines · 796s

Review stats
{
  "model": "claude-sonnet-4-6",
  "reviewTimeMs": 796117,
  "diffLines": 2321,
  "diffAdditions": 2011,
  "diffDeletions": 310,
  "filesReviewed": 22,
  "agents": [
    "Correctness & Logic",
    "Architecture & Design",
    "Testing & Coverage",
    "Dependencies & Integration"
  ],
  "findingsRaw": 13,
  "findingsKept": 2,
  "findingsDropped": 11,
  "severity": {
    "blocker": 0,
    "warning": 0,
    "suggestion": 0,
    "nitpick": 2
  },
  "verdict": "APPROVE",
  "prNumber": 122,
  "commitSha": "b78ba3172c4b917c24f8af99bc6de7ebf6deb823",
  "agentMetrics": [
    {
      "name": "Correctness & Logic",
      "findingsRaw": 4,
      "findingsKept": 1,
      "responseLength": 5390
    },
    {
      "name": "Architecture & Design",
      "findingsRaw": 0,
      "findingsKept": 0,
      "responseLength": 8091
    },
    {
      "name": "Testing & Coverage",
      "findingsRaw": 5,
      "findingsKept": 1,
      "responseLength": 5990
    },
    {
      "name": "Dependencies & Integration",
      "findingsRaw": 4,
      "findingsKept": 0,
      "responseLength": 5313
    }
  ],
  "judgeMetrics": {
    "confidenceDistribution": {
      "high": 5,
      "medium": 0,
      "low": 0
    },
    "severityChanges": 5,
    "mergedDuplicates": 0,
    "defensiveHardeningCount": 1,
    "verdictReason": "only_nit_or_suggestion"
  },
  "fileMetrics": {
    "fileTypes": {
      ".rs": 21,
      ".toml": 1
    },
    "findingsPerFile": {
      "dash-spv/src/sync/blocks/pipeline.rs": 1,
      "dash-spv-ffi/src/callbacks.rs": 1
    }
  },
  "reviewerModel": "claude-sonnet-4-6",
  "judgeModel": "claude-opus-4-7"
}

@xdustinface xdustinface merged commit eb2226c into v0.42-dev Apr 27, 2026
72 checks passed
@xdustinface xdustinface deleted the feat/per-wallet-filter-scan branch April 27, 2026 08:29
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.

2 participants