Skip to content

Review fixes for #909: generation guard, surfaced record rewrites, restored public API#1

Merged
bfoss765 merged 2 commits into
bfoss765:fix/wallet-utxo-spend-not-marked-649from
dashpay:pr-909-review-fixes
Jul 22, 2026
Merged

Review fixes for #909: generation guard, surfaced record rewrites, restored public API#1
bfoss765 merged 2 commits into
bfoss765:fix/wallet-utxo-spend-not-marked-649from
dashpay:pr-909-review-fixes

Conversation

@QuantumExplorer

Copy link
Copy Markdown

Review fixes for dashpay#909

Two commits addressing findings from review of dashpay#909, targeting this PR's branch so they land as part of it. All findings were independently verified against the code before fixing.

Round 1 (aac1bd99)

  • Gate the net_amount recompute in compensate_for_observed_spends: only replace the match-derived net_amount when the compensation actually dropped an output detail, keeping the no-observed-spend path byte-identical to pre-bug: out-of-order block processing causes SPV wallet to miss UTXO spends dashpay/rust-dashcore#649 behavior. Pinned by a new unit test.
  • Report observed-spend map growth as a state modification: record_observed_spends now returns whether the persisted map changed and check_core_transaction surfaces that via state_modified — a consumer persisting only on reported modifications must not lose a recorded spend across a restart (that would reopen bug: out-of-order block processing causes SPV wallet to miss UTXO spends dashpay/rust-dashcore#649 for the coin). Pinned by a new regression test.
  • Replace a comment reference to a nonexistent test with the inline rationale.
  • Tighten MAX_OBSERVED_SPENT_OUTPOINTS 10M → 1M (load-time allocation cap from ~hundreds of MB to ~tens of MB).

Round 2 (aa250e49) — external review findings

  1. [P1] Generation guard for mid-flight account adds. The height-only contiguity guard could still certify unscanned coverage: a rewind landing inside a scanned batch's range passes the height check (scan [5000..9999] at 9000, rewind to 7499 → committed to 9999), and an account add that moves no heights (checkpoint already at the birth floor) is invisible to any height comparison. ManagedWalletInfo now carries an in-memory account_generation bumped on every account add — even height-invisible ones; scan_batch snapshots it per wallet (FiltersBatch.scanned_wallets is now a wallet → generation map, captured under the same read lock as the behind-set) and commit refuses to advance any wallet whose generation changed since scan. The wallet stays behind and the tick rescan picks it up. Pinned by three new dash-spv tests including both repro shapes.

  2. [P2] Surface guard-rewritten funding records. remove_spent_from_accounts rewrote funding records (compensation) without emitting them; on the unmatched path the result returned is_relevant == false, so downstream persistence could retain the stale +value record. It now returns post-compensation clones, the checker emits them as updated_records on both the relevant and irrelevant paths, and WalletManager propagates updated_records independent of is_relevant. The compensation itself is kept: it is what keeps aggregate history consistent with in-order processing — the defect was the silent mutation.

  3. [P2] Restore the public account API. ManagedAccountRefMut::record_transaction/confirm_transaction had gone pubpub(crate) with changed signatures on a publicly re-exported type. The public methods are restored with their original signatures (recording with no observed-spend context — the pre-bug: out-of-order block processing causes SPV wallet to miss UTXO spends dashpay/rust-dashcore#649 behavior for callers driving an account directly); the wallet checker uses new pub(crate) *_with_observed_spends variants.

Verification

🤖 Generated with Claude Code

QuantumExplorer and others added 2 commits July 22, 2026 21:46
… modification, tighten deser cap

- compensate_for_observed_spends: only replace the match-derived net_amount
  when the compensation actually dropped an output detail, keeping the
  no-observed-spend path byte-identical to pre-#649 behavior; pinned by a
  new unit test.
- record_observed_spends: report whether the persisted observed-spent map
  actually changed, and surface that as state_modified in
  check_core_transaction — a consumer persisting only on reported
  modifications must not lose a recorded spend across a restart; pinned by
  a new regression test (new spend reports, unchanged redelivery and
  mempool spends do not).
- Replace a comment reference to a nonexistent test with the inline
  rationale for why input_details and account_match.sent populate together.
- Tighten MAX_OBSERVED_SPENT_OUTPOINTS 10M -> 1M (load-time allocation cap
  from a few hundred MB to a few tens of MB), still far above any
  legitimate size.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…urface guard-rewritten records, restore public account API

Addresses three external review findings on top of PR #909:

1. [P1] The commit-time contiguity guard could still certify unscanned
   coverage for a newly added account: a rewind landing INSIDE a scanned
   batch's range passed the height check, and an account add that moved
   no heights (checkpoint already at the birth floor) was undetectable
   by any height comparison. ManagedWalletInfo now carries an in-memory
   account_generation counter bumped on every account add (even
   height-invisible ones); filter scan snapshots it per wallet and
   commit refuses to advance a wallet whose generation changed since
   scan. Pinned by three new dash-spv tests including the mid-batch
   rewind repro (9000 -> scan [5000..9999] -> rewind 7499 -> commit
   keeps 7499) and the unmoved-checkpoint case.

2. [P2] The funding-first guard rewrote funding records without ever
   surfacing them: remove_spent_from_accounts now returns
   post-compensation clones of every rewritten record, the checker adds
   them to updated_records on both the relevant and irrelevant paths,
   and the manager propagates updated_records independent of
   is_relevant, so consumers persisting per-record updates see the
   rewrite.

3. [P2] ManagedAccountRefMut::record_transaction/confirm_transaction had
   silently gone pub -> pub(crate) with changed signatures. The public
   methods are restored with their original signatures (recording with
   no observed-spend context, the pre-#649 behavior); the checker uses
   new pub(crate) *_with_observed_spends variants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bfoss765
bfoss765 merged commit 3723925 into bfoss765:fix/wallet-utxo-spend-not-marked-649 Jul 22, 2026
1 of 2 checks passed
@bfoss765
bfoss765 deleted the pr-909-review-fixes branch July 22, 2026 23:19
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