fix(platform-wallet): act on swept transactions at the persistence seam - #4560
fix(platform-wallet): act on swept transactions at the persistence seam#4560romchornyi wants to merge 2 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
|
🕓 Ready for review — 27 ahead in queue (commit 267ecca) |
b7f2e47 to
4861a82
Compare
8a655b8 to
cffc998
Compare
|
@coderabbitai review |
|
|
@coderabbitai review |
|
Bumps the rust-dashcore pin to dev and projects the `TransactionsSwept` event the bump brings with it. The two halves are one commit by construction: `WalletEvent` is not `#[non_exhaustive]` and platform has four exhaustive matches over it, so new-pin code cannot compile without the arms — and arms that did nothing would be worse than none, because upstream's removal is unconditional. The wallet drops the losing rows in memory; a store that keeps them replays them at the next load and re-creates the phantom balance the upstream fix exists to kill. The projection is one `SweepBatch` per event, and a sweep-only round is counted in `is_empty_no_records` so a round carrying nothing but a sweep still reaches the persister. The gate is what makes every intermediate host state safe. A backend that has not attested `CORE_SWEEP_REMOVAL` is not known to have applied the round's subtractive half, so its watermark is stripped BEFORE the store and the wallet faults exactly as it would on a rejection — reporting the height durable first and faulting after cannot retract a height a legacy backend already committed. Such a host freezes its sync watermark on the first sweep it meets instead of diverging: fail-closed, funds-safe, and unfrozen the moment its persister ships. A record arriving after a sweep of the same txid retracts that txid from the folded sweep, since persisters write records before replaying sweeps and would otherwise delete a row the wallet has brought back. The asset-lock half mirrors it: a sweep removes the tracked entry its funding transaction created, and `AssetLockChangeSet::merge` now cancels a folded tombstone against a reinstating upsert (and vice versa), so no store ever sees an upsert/tombstone pair for one outpoint whose outcome depends on which it applies first. The pin also carries rust-dashcore#981, which collapses BIP-39 parsing onto one auto-detecting path. Platform's four hand-rolled "try every wordlist" helpers are now that function, and the call sites drop their `Language` argument. It is unrelated to sweeps and rides here only because the sweep chain and the payload-finalization seam this branch's base already depends on both sit above it on dev. `spend_observer`'s two projections gain sweep arms that report no observed spend: a sweep's released outpoints are coins that came back free, and the inputs it kept spent are precisely the ones it does not name, so the held set cannot be derived from the event at all.
…ouched `cargo fmt --check --all` is a CI gate and the collapsed `Mnemonic::from_phrase` calls left two of them wrapped.
cffc998 to
267ecca
Compare
Issue being fixed or feature implemented
Nothing yet emits a sweep. This PR bumps the rust-dashcore pin and projects the
TransactionsSweptevent the bump brings with it, so the seam (#4558) and the store (#4559) finally carry the removal a losing double-spend requires.The pin bump and the arms are one commit by construction.
WalletEventis not#[non_exhaustive]and platform has four exhaustive matches over it, so new-pin code cannot compile without the arms — and arms that did nothing would be worse than none, because upstream's removal is unconditional (wallet_checker.rs): the wallet drops the losing rows in memory, and a store that keeps them replays them at the next load.What was done?
The pin
4db5c367→ rust-dashcoredev(21aaafed).Worth knowing why it moves this far:
v4.2-devwas pinned to a curated rebase line (chore/sync-fixes-payload-seam) that deliberately omits the whole sweep chain (#961/#962/#966/#969/#975) but carries #991'sset_payload_finalizer, whichmasternode/update_service.rsnow requires. Our previous pin had the reverse. No revision carrying both existed, so this takesdev, which carries everything.devalso carries rust-dashcore#981, which collapses BIP-39 parsing onto one auto-detecting path. Platform's four hand-rolled "try every wordlist" helpers become that function and the call sites drop theirLanguageargument (13 files). Unrelated to sweeps; it rides here only because the sweep chain and the payload-finalization seam this branch's base already depends on both sit above it ondev.The producer
TransactionsSwept→ oneSweepBatch(core_bridge.rs);is_empty_no_recordscounts sweeps, so a sweep-only round still reaches the persister.balance_handler.rs(routes the post-removal balance snapshot — a sweep is the one event that can lower a balance) andpayment_handler.rs(deliberate no-ops; the payment coupling is fix(platform-wallet): couple a sweep's payment flips to their own persistence round #4442).spend_observer.rsgains sweep arms that report no observed spend: a sweep's released outpoints are coins that came back free, and the inputs it kept spent are precisely the ones it does not name, so the held set cannot be derived from the event at all.The gate — what makes every intermediate host state safe
A backend that has not attested
CORE_SWEEP_REMOVALis not known to have applied the round's subtractive half, so its watermark is stripped before the store and the wallet faults exactly as on a rejection. Order is load-bearing: reporting the height durable first and faulting after cannot retract a height a legacy backend already committed. Such a host freezes its sync watermark on the first sweep it meets instead of diverging — fail-closed, funds-safe, and unfrozen the moment its persister ships (#4406's Swift and Kotlin PRs).Reinstatement
A record arriving after a sweep of the same txid retracts that txid from the folded sweep, since persisters write records before replaying sweeps and would otherwise delete a row the wallet has brought back. The asset-lock half mirrors it: a sweep removes the tracked entry its funding transaction created, and
AssetLockChangeSet::mergecancels a folded tombstone against a reinstating upsert (and vice versa), so no store sees an upsert/tombstone pair whose outcome depends on which it applies first.How Has This Been Tested?
cargo test -p platform-wallet -p platform-wallet-ffi -p platform-wallet-storage— 928 + 310 + 138 pass, plus every integration suite in those crates.Tests travelling with the change (
core_bridge.rs):sweep_without_declared_capability_freezes_the_wallet_despite_a_successful_storepins the gate;sweep_names_the_dead_transactions_and_nothing_else,sweep_reaches_the_persister,merged_sweeps_stay_separate_and_ordered;transactions_swept_removes_the_tracked_asset_lock_it_fundedanda_reinstating_reconstruction_folded_after_a_sweep_cancels_its_tombstone;transactions_swept_does_not_drive_payment_hookspins the handler no-op.Breaking Changes
None for platform's own API.
Release-timing constraint, not a merge constraint: do not cut a swift-sdk or kotlin-sdk release from a base that contains this PR but not its Swift/Kotlin counterparts. A mobile host at that base freezes its sync watermark on the first sweep it meets — funds-safe, but a user-visible stall. Between merges on
v4.2-devnothing auto-ships.The pin bump also carries rust-dashcore#981's breaking mnemonic API; the platform-side adaptation is included here and is mechanical.
Checklist:
For repository code-owners and collaborators only