Skip to content

fix(dash-spv): collect the scripts derived by every application of a block, not only the tracked one - #1007

Closed
llbartekll wants to merge 1 commit into
fix/key-wallet-rescan-changesetfrom
fix/dash-spv-redelivered-block-scripts
Closed

fix(dash-spv): collect the scripts derived by every application of a block, not only the tracked one#1007
llbartekll wants to merge 1 commit into
fix/key-wallet-rescan-changesetfrom
fix/dash-spv-redelivered-block-scripts

Conversation

@llbartekll

Copy link
Copy Markdown
Contributor

Follow-up to #979, fixes #1006. Stacked on fix/key-wallet-rescan-changeset (#979) because it lands on top of the durable pending-sweep set that branch introduces; it can be re-targeted to dev once #979 merges.

Issue being fixed

A block is applied to the wallet more than once during a scan: re-applied by a batch rescan for scripts derived after its first application, or delivered again for an in-flight re-request. Each application can recognise outputs the previous one could not (the pools grew in between) and derive further scripts through gap widening. The BlockProcessed handler collected those scripts only when the tracker still held the block's in-flight entry — which the first delivery consumes — so the scripts derived by every later application were dropped on the floor: neither recorded in the durable pending-sweep set nor collected for the batch rescan and the committed-range sweep.

Measured on a CoinJoin-heavy mainnet wallet (fresh import, release build, iOS Simulator, counters added temporarily to the handler): 3 645 scripts were derived during the scan; 3 348 reached the sweep and 297 — derived by 14 blocks that were applied 2–4 times each while their batch still had other blocks outstanding — were lost. The blocks that spend the coins paid to the lost addresses match only on the coins' prevout scripts (their outputs are not ours), so they were never found, requested or applied; the wallet ended the scan with 11 spent coins credited (0.11 DASH) and nothing in the engine could ever remove them: no spender record for the born-spent attribution, observed_spent_outpoints never saw the spend, spent_outpoints is only fed by matched transactions. The same 11 coins came back after every restart because the persister mirrors the engine (dashpay/platform#4575, dashpay/platform#4638).

What was done

  • sync_manager.rs: settle the in-flight entry and the batch's pending count from the tracked delivery only, but collect new scripts from every delivery — into the tracked batch while it is active, else the batch covering the block, else the lowest active batch, whose commit rescans it, every later batch and the committed prefix. With no active batch the scripts stay in the durable pending-sweep set and are replayed by the next scan, as before.
  • The Rescan filters and Rescan committed filters log lines now carry the script count, which is what made the loss visible.
  • Regression test scripts_derived_by_a_redelivered_block_enter_the_cascade in coinjoin_gap_discovery_tests.rs: a second BlockProcessed for an already-settled block must leave its scripts both in the pending-sweep set and in the batch's collected scripts. Red before the fix (the pending-sweep assertion), green after.

How has this been tested

  • cargo test -p dash-spv --lib sync::filters: 117 passed (116 existing + the new one); cargo fmt --check and cargo clippy --all-targets clean for dash-spv.
  • Same wallet, same simulator, release platform-wallet FFI with this commit applied on the fix(key-wallet, dash-spv): re-emit late knowledge — record corrections, durable rescans, address-pool repair #979 compat rebase:
    • before: sweep ran with 3 348 scripts, found 10 265 blocks, 12 480 blocks applied, 11 coins credited after the scan;
    • after: sweep ran with 3 645 scripts, found 11 169 blocks, 14 754 blocks applied, utxo_count=0 for the CoinJoin account, the SwiftData store holds 0 unspent rows, and a restart restores nothing. This matches the only earlier run of the same wallet that had converged (by timing luck).

Not addressed here

🤖 Generated with Claude Code

…block, not only the tracked one

A block is applied more than once during a scan: re-applied by a batch
rescan for scripts derived after its first application, or delivered again
for an in-flight re-request. Each application can recognise outputs the
previous one could not, and derive further scripts through gap widening.
The BlockProcessed handler collected new scripts only when the tracker still
held the block's in-flight entry — which the first delivery consumes — so
the scripts derived by every later application were dropped: neither noted
in the durable pending-sweep set nor collected for the batch rescan and the
committed-range sweep.

On a CoinJoin-heavy mainnet wallet (fresh import, release build) 297 of the
3 645 gap-widened scripts were derived by re-applied blocks and lost. The
blocks that spend the coins paid to those addresses match only on the
coins' prevout scripts, so they were never found; the wallet ended the
scan with 11 spent coins credited (#1006).

Settle the in-flight entry and the pending count from the tracked delivery
only, but collect new scripts from every delivery: into the tracked batch
while it is active, else the batch covering the block, else the lowest
active batch — whose commit rescans it, every later batch and the committed
prefix. With no active batch the scripts stay in the durable pending-sweep
set and are replayed by the next scan, as before. The rescan and sweep log
lines now carry the script count.

Regression test: a second BlockProcessed for an already-settled block must
leave its scripts both in the pending-sweep set and in the batch's
collected scripts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.71%. Comparing base (f66f1b3) to head (108ee9c).

Files with missing lines Patch % Lines
dash-spv/src/sync/filters/sync_manager.rs 84.61% 2 Missing ⚠️
Additional details and impacted files
@@                       Coverage Diff                        @@
##           fix/key-wallet-rescan-changeset    #1007   +/-   ##
================================================================
  Coverage                            77.70%   77.71%           
================================================================
  Files                                  329      329           
  Lines                                84256    84271   +15     
================================================================
+ Hits                                 65475    65494   +19     
+ Misses                               18781    18777    -4     
Flag Coverage Δ
core 78.25% <ø> (ø)
ffi 54.46% <ø> (+<0.01%) ⬆️
rpc 20.00% <ø> (ø)
spv 92.11% <86.66%> (+0.02%) ⬆️
wallet 79.98% <ø> (ø)
Files with missing lines Coverage Δ
dash-spv/src/sync/filters/manager.rs 97.72% <100.00%> (+<0.01%) ⬆️
dash-spv/src/sync/filters/sync_manager.rs 95.74% <84.61%> (-4.26%) ⬇️

... and 7 files with indirect coverage changes

@llbartekll

Copy link
Copy Markdown
Contributor Author

Superseded by #1008, the same fix as a standalone PR on dev (no dependency on #979). Closing this stacked variant to avoid a duplicate review; #979 can rebase over #1008 with a one-line conflict in the same handler.

@llbartekll llbartekll closed this Sep 9, 2026
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