Skip to content

fix(key-wallet): stop pruning spent CoinJoin addresses from the filter scan - #985

Open
ZocoLini wants to merge 1 commit into
devfrom
fix/coinjoin-scan-keeps-spent-addresses
Open

fix(key-wallet): stop pruning spent CoinJoin addresses from the filter scan#985
ZocoLini wants to merge 1 commit into
devfrom
fix/coinjoin-scan-keeps-spent-addresses

Conversation

@ZocoLini

@ZocoLini ZocoLini commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

#949 dropped a CoinJoin address from the forward compact-filter query once it was used and held no unspent output, on the grounds that CoinJoin addresses are single-use by protocol, so nothing would ever pay one again.

Mainnet does not honour that. On a restore of a mixing-heavy wallet, 287 CoinJoin addresses were paid by more than one transaction, some of them nearly 100 000 blocks after being emptied. Eight of those fell in a window where the wallet had already stopped watching them, and the scan never saw the payments:

9 transactions missed between heights 2 170 285 and 2 170 440
5 outputs never recorded (80 000 sat)
9 spends never recorded (176 780 sat)
4 already-spent outputs left on the books as spendable
balance overstated by 96 780 sat

I looked for options to kept CoinJoin addresses collection as small as possible but the bug can use any address at any point, so all of them have to be tracked. I was also reported that the bug is still present

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 11 minutes.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 901e03e6-d1d6-4482-8852-8696a902b2a0

📥 Commits

Reviewing files that changed from the base of the PR and between 237f79a and a1d383d.

📒 Files selected for processing (9)
  • dash-spv/src/sync/filters/manager.rs
  • key-wallet-manager/benches/filter_scan.rs
  • key-wallet-manager/src/process_block.rs
  • key-wallet-manager/src/test_utils/mock_wallet.rs
  • key-wallet-manager/src/wallet_interface.rs
  • key-wallet/src/managed_account/managed_core_funds_account.rs
  • key-wallet/src/tests/mod.rs
  • key-wallet/src/tests/scan_script_pubkeys_tests.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs

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 Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.15%. Comparing base (237f79a) to head (a1d383d).

Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #985      +/-   ##
==========================================
+ Coverage   77.14%   77.15%   +0.01%     
==========================================
  Files         329      329              
  Lines       82998    83031      +33     
==========================================
+ Hits        64026    64060      +34     
+ Misses      18972    18971       -1     
Flag Coverage Δ
core 78.25% <ø> (ø)
ffi 52.41% <ø> (+<0.01%) ⬆️
rpc 20.00% <ø> (ø)
spv 91.98% <ø> (-0.02%) ⬇️
wallet 79.26% <100.00%> (+0.03%) ⬆️
Files with missing lines Coverage Δ
dash-spv/src/sync/filters/manager.rs 97.91% <ø> (ø)
key-wallet-manager/src/process_block.rs 93.15% <100.00%> (+0.84%) ⬆️
key-wallet-manager/src/wallet_interface.rs 9.37% <ø> (ø)
.../src/managed_account/managed_core_funds_account.rs 87.59% <ø> (-0.18%) ⬇️
...allet/managed_wallet_info/wallet_info_interface.rs 80.70% <ø> (+0.23%) ⬆️

... and 3 files with indirect coverage changes

@ZocoLini
ZocoLini force-pushed the fix/coinjoin-scan-keeps-spent-addresses branch 2 times, most recently from aa2b6fb to d52b0f1 Compare August 27, 2026 12:02
…r scan

The forward compact-filter query dropped a CoinJoin address once it was used
and held no unspent output, on the grounds that CoinJoin addresses are
single-use by protocol, so nothing would ever pay one again.

Mainnet does not honour that. On a restore of a mixing-heavy wallet, 287
CoinJoin addresses were paid by more than one transaction — verified on the
scriptPubKey, not on a derivation label — some of them nearly 100 000 blocks
after being emptied. Eight of those fell in a window where the wallet had
already stopped watching them, and the scan never saw the payments:

  9 transactions missed between heights 2 170 285 and 2 170 440
  5 outputs never recorded (80 000 sat)
  9 spends never recorded (176 780 sat)
  4 already-spent outputs left on the books as spendable
  balance overstated by 96 780 sat

Reproduced identically across four full syncs and by two independent methods
(an A/B diff of per-address dumps, and an end-of-sync re-match of the whole
chain against still-unspent scripts, which named the same heights and the
same addresses without being told what to look for).

The saving did not pay for that either. Pruning shrinks the query sixfold at
the tip (13 378 to 2 200 scripts), but over a full mainnet restore it moves
the sync by 7.7% in blocks and 2.3% in time — inside the run-to-run spread,
with the fastest of the four runs being an unpruned one.

So the mechanism goes rather than being switched off. ManagedWalletInfo no
longer overrides scan_script_pubkeys, and
ManagedCoreFundsAccount::unspent_or_unused_script_pubkeys goes with it, having
had no other caller. The seam in the trait stays, documented with why the scan
watches every CoinJoin address however long ago it was emptied.

One test covers it, replacing the four that asserted set membership at two
layers and now would only be checking that a defaulted method calls the one it
delegates to. It pays a CoinJoin address, spends the output, then builds a
real BIP158 filter over the block carrying a second payment and requires the
scan query to match — and fails if the pruning comes back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015NhHBDGiKfiGpy7FwooyfS
@ZocoLini
ZocoLini force-pushed the fix/coinjoin-scan-keeps-spent-addresses branch from d52b0f1 to a1d383d Compare August 27, 2026 12:42
@ZocoLini
ZocoLini requested a review from xdustinface August 27, 2026 12:45
@ZocoLini
ZocoLini marked this pull request as ready for review August 27, 2026 12:45
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