Skip to content

fix(key-wallet): prune observed spends during the initial sync - #1014

Open
ZocoLini wants to merge 1 commit into
devfrom
fix/prune-observed-spends-during-sync
Open

ZocoLini wants to merge 1 commit into
devfrom
fix/prune-observed-spends-during-sync

Conversation

@ZocoLini

@ZocoLini ZocoLini commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

observed_spent_outpoints records every input of every transaction in every block the wallet processes, false positives included, and is pruned only up to a chainlock the wallet has applied. The client defers chainlocks until SyncComplete, so nothing is pruned during an initial sync. On a mainnet restore of the bench wallet the map reached 1 487 967 entries (every input of 10 295 blocks, 505 946 transactions), above the 1 000 000 entries the serde adapter accepts on load, so a wallet persisted mid-sync could not be loaded back.

The dispatcher still defers applying chainlocks, but now passes the deferred chainlock's height on through note_chain_lock_height. The wallet uses it only as a finality boundary for pruning: entries at or below min(synced_height, highest chainlock applied or noted) are evicted. As before, only chain-locked spends are forgotten, and no record is promoted.

Pruning mid-sync opened one path: redelivering a funding transaction whose output sits in spent_before_funded, after its observed-spend entry was evicted, re-inserted the spent coin into utxos. update_utxos now keeps such an output held; the new test fails without that guard.

Validated with 5 consecutive mainnet restores at 100 Mbit / 100 ms

Closes #899

Summary by CodeRabbit

  • Bug Fixes

    • Prevented previously spent wallet outputs from reappearing in the UTXO set during transaction reprocessing, rescans, or mempool-to-block transitions.
    • Wallets now recognize newly observed chain locks earlier, allowing finalized spent-output records to be pruned sooner and more accurately.
  • Tests

    • Added coverage for chain-lock-based pruning and ensuring spent outputs remain excluded after pruning.

PR Hygiene · 3a427a4

  • Bots — coderabbitai ✓
  • Self-review — posted; again after any push
  • Within your 5 open PRs
  • Build green
  • Approvals
    • dash-spv (dash-spv/src/client/event_handler.rs) — QuantumExplorer or xdustinface
    • key-wallet-manager (key-wallet-manager/src/process_block.rs, key-wallet-manager/src/wallet_interface.rs) — QuantumExplorer or xdustinface
    • key-wallet (key-wallet/src/managed_account/managed_core_funds_account.rs, key-wallet/src/tests/observed_spent_outpoints_tests.rs, key-wallet/src/wallet/managed_wallet_info/mod.rs and 1 more) — QuantumExplorer or xdustinface

When every box is checked the PR Hygiene check passes and this can merge.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: bc569c1b-b009-45d0-b623-894c91a822a9

📥 Commits

Reviewing files that changed from the base of the PR and between ed0df9d and 3a427a4.

📒 Files selected for processing (7)
  • dash-spv/src/client/event_handler.rs
  • key-wallet-manager/src/process_block.rs
  • key-wallet-manager/src/wallet_interface.rs
  • key-wallet/src/managed_account/managed_core_funds_account.rs
  • key-wallet/src/tests/observed_spent_outpoints_tests.rs
  • key-wallet/src/wallet/managed_wallet_info/mod.rs
  • key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change propagates buffered chainlock heights to wallets, enables observed-spend pruning before chainlock application, and prevents reprocessed spent outputs from returning to the UTXO set. Tests cover both behaviors.

Changes

Chainlock-aware wallet pruning

Layer / File(s) Summary
Propagate buffered chainlock heights
dash-spv/src/client/event_handler.rs, key-wallet-manager/src/...
Wallet interfaces now accept chainlock height notifications. The wallet manager forwards heights to all managed wallets. Buffered cycle-0 chainlocks notify wallets when a newer height arrives.
Track noted heights and prune spends
key-wallet/src/wallet/managed_wallet_info/...
ManagedWalletInfo stores the highest noted chainlock height. Observed-spend pruning uses the higher of noted and applied chainlock heights, capped by synced_height.
Preserve spent-before-funded outputs
key-wallet/src/managed_account/..., key-wallet/src/tests/...
update_utxos excludes outputs already recorded in spent_before_funded. Tests cover pruning from a noted chainlock and reprocessing a funding transaction.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 3a427

The available evidence identifies no confirmed behavior that would block merging.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 7 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: pruning observed spends during the initial key-wallet synchronization. It is concise and related to the changeset.
Linked Issues check ✅ Passed The changes address issue #899. Buffered chainlocks now report their heights through note_chain_lock_height. ManagedWalletInfo prunes observed_spent_outpoints up to `min(synced_height, highest a…
Out of Scope Changes check ✅ Passed The changes stay within issue #899. The wallet-interface and SPV changes deliver buffered chainlock heights to the wallet. The update_utxos guard and its test preserve UTXO correctness when pruning …
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/prune-observed-spends-during-sync

Warning

Git: CodeRabbit could not clone the repository, so clone-backed analysis was skipped and this review may be incomplete. Verify repository clone access, such as SSH credentials, before requesting another full review. If clone access is intentionally unavailable, use path_filters to narrow the review scope.


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 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 63.63636% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.19%. Comparing base (ed0df9d) to head (3a427a4).
⚠️ Report is 8 commits behind head on dev.

Files with missing lines Patch % Lines
key-wallet-manager/src/process_block.rs 0.00% 5 Missing ⚠️
key-wallet-manager/src/wallet_interface.rs 0.00% 1 Missing ⚠️
key-wallet/src/wallet/managed_wallet_info/mod.rs 83.33% 1 Missing ⚠️
...allet/managed_wallet_info/wallet_info_interface.rs 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev    #1014      +/-   ##
==========================================
- Coverage   77.20%   77.19%   -0.02%     
==========================================
  Files         329      329              
  Lines       83862    83880      +18     
==========================================
  Hits        64747    64747              
- Misses      19115    19133      +18     
Flag Coverage Δ
core 78.24% <ø> (ø)
ffi 50.94% <ø> (ø)
rpc 20.00% <ø> (ø)
spv 92.02% <100.00%> (-0.05%) ⬇️
wallet 79.77% <61.90%> (-0.02%) ⬇️
Files with missing lines Coverage Δ
dash-spv/src/client/event_handler.rs 93.75% <100.00%> (-0.27%) ⬇️
.../src/managed_account/managed_core_funds_account.rs 87.56% <100.00%> (+0.01%) ⬆️
key-wallet-manager/src/wallet_interface.rs 9.09% <0.00%> (-0.29%) ⬇️
key-wallet/src/wallet/managed_wallet_info/mod.rs 74.24% <83.33%> (+0.01%) ⬆️
...allet/managed_wallet_info/wallet_info_interface.rs 80.82% <85.71%> (+0.12%) ⬆️
key-wallet-manager/src/process_block.rs 92.24% <0.00%> (-0.91%) ⬇️

... and 5 files with indirect coverage changes

`observed_spent_outpoints` records every input of every transaction in every
block the wallet processes, false positives included, and is pruned only up to
a chainlock the wallet has applied. The client defers chainlocks until
`SyncComplete`, so nothing is pruned during an initial sync. On a mainnet
restore of the bench wallet the map reached 1 487 967 entries (every input of
10 295 blocks, 505 946 transactions), above the 1 000 000 entries the serde
adapter accepts on load, so a wallet persisted mid-sync could not be loaded
back.

The dispatcher still defers applying chainlocks, but now passes the deferred
chainlock's height on through `note_chain_lock_height`. The wallet uses it
only as a finality boundary for pruning: entries at or below
min(synced_height, highest chainlock applied or noted) are evicted. As before,
only chain-locked spends are forgotten, and no record is promoted.

Pruning mid-sync opened one path: redelivering a funding transaction whose
output sits in `spent_before_funded`, after its observed-spend entry was
evicted, re-inserted the spent coin into `utxos`. `update_utxos` now keeps such
an output held; the new test fails without that guard.

Validated with 5 consecutive mainnet restores at 100 Mbit / 100 ms, all ending
at 7112 records, 14 114 383 sat and 13 389 addresses. Peak RSS was 1378–1724
MiB against 1488 MiB before, within run-to-run spread.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017DruChNTWXwJoWPartZwCf
@ZocoLini
ZocoLini force-pushed the fix/prune-observed-spends-during-sync branch from 026fba0 to 3a427a4 Compare September 16, 2026 08:35
@github-actions github-actions Bot added the ready-for-review CodeRabbit has approved this PR label Sep 16, 2026
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Ready for review — needs QuantumExplorer or xdustinface.
Full checklist in the description.

@github-actions github-actions Bot added the waiting-self-review Waiting for the author to post /self-reviewed label Sep 20, 2026
@ZocoLini

Copy link
Copy Markdown
Collaborator Author

/self-reviewed

@github-actions github-actions Bot added ready-for-human Bots have reported, the author has self-reviewed, and the build is green: this needs a human. and removed waiting-self-review Waiting for the author to post /self-reviewed labels Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Bots have reported, the author has self-reviewed, and the build is green: this needs a human. ready-for-review CodeRabbit has approved this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

key-wallet: observed_spent_outpoints grows unpruned for the whole initial sync

1 participant