Conversation
V019 and the per-round history repair decoded stored records strictly, so one undecodable record_blob aborted the migration (blocking every open, Recovery included) and made each later write of that txid fail. History repair is best-effort accounting: unreadable stored data is now logged and skipped. Each record is repaired inside its own savepoint so a skipped one leaves no partial writes, the corrupt row is left untouched, and a later write of the same transaction replaces it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
V019 and the per-round history repair rewrote stored transaction records in place and marked owned inputs spent without saying which transaction spent them, so a wrong repair could not be undone after the one-off pre-migration backup. Before its first rewrite, a record's original blob is now copied verbatim into the append-only core_transaction_record_originals table (V019 is unreleased and edited in place), and a repair's spent mark records its spender in spent_in_txid. Automatic release after a reorg is left as a TODO pending verification of upstream reorg handling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
V019 called the live per-round history repair, so any later edit to that code (or to the queried tables) would silently change what V019 does for users upgrading from V018 or earlier. Move V019's repair into the migrations-local legacy_v019 module, following the legacy_v008 precedent, with its own record reader. Only the low-level blob codec stays shared; TransactionRecord's encoding is owned upstream. A pinning test fixes V019's result on a V018-shaped database: the repaired record bytes, the preserved original, the input index and the spent marks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on load Load replayed only block-confirmed records, so a mempool or InstantSend spend never reached the account's spent set. A redelivered funding transaction (rescan, reorg re-connect, or the funding confirming after a restart) then re-credited the output that spend reserves, and the round wrote it back to SQLite as unspent. Replay unconfirmed records after the confirmed ones, parents before children, so the checker skips re-crediting reserved outputs. The persisted-unspent filter still keeps their own outputs from being credited unless persistence holds them. The regression tests now redeliver funding after reload, for confirmed and still-unconfirmed funding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…idge The wallet checker is async only by trait shape and never awaits, yet load drove it through dash_async::block_on, which spawns a thread and runtime on current-thread runtimes, and surfaced bridge failures through a new public WalletStorageError::CoreHistoryReplay variant (a breaking change, as the enum is exhaustive). Poll the replay once instead. If a future upstream checker ever suspends, load logs an error and keeps the pre-replay projection rather than failing the wallet; a unit test pins first-poll completion so such a change fails in CI. This removes the variant and the dash-async dependency. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Swift accounting reconcile and the SQLite history repair disagreed on direction: Swift reported an asset lock as internal even when an output left the wallet, and a spend with no remaining outputs as internal. Swift now uses the Rust repair's rule (internal only when nothing leaves the wallet and something stays in it, or an asset lock burns into Platform). The Rust rule moves into a pure helper, and both sides test the same case table. The upstream rust-dashcore recompute stays out of scope. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The asset-lock fixtures used an empty-script output as the burn, which the aligned direction rule rightly treats as leaving the wallet. Use a real OP_RETURN so the fixtures match what an asset lock carries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
netAmount(for:) returned the stored scalar through its single-wallet fast path before checking for unresolved inputs, and the accounting reconcile counted an address-matched output of any local wallet, so a transfer to another local wallet whose TXO was not linked yet showed the sender only part of what it paid. Unresolved inputs now make every amount provisional, and an unlinked address-matched output counts only when it belongs to a spending wallet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The guard that keeps a funded asset lock's Core debit and fee from being overwritten by a context-only zero update required an already-linked owned input. With every prevout still pending, the synthetic update erased the debit and fee, and reconcile could not restore them. A stored negative amount is itself the proof the wallet funded the lock, so the guard now keys on it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loadWalletList's history accounting pass returned a load failure on any fetch, reconcile or save error, so a problem in display-only amounts left every wallet unrestored. It also fetched a PersistentCoreAddress per unlinked output. A failed pass now rolls back its own edits, logs a distinct event and lets the restore continue; addresses are read once into a lookup. A persisted completion marker is deferred (TODO) because it needs a SwiftData shape change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Transaction views treat an unresolved per-wallet amount as unavailable everywhere, so the fee no longer shows beside "Amount unavailable". - A failed accounting reconcile no longer fails the persistence round and is reported as persistence_transaction_accounting_failed, not save_failed. - Name the Swift transaction-kind and direction values instead of 1/3/6. - Rename the verdict test that never covered Uncredited and add one that does; V019's confirmed-spend marking is pinned by the V019 fixture test. - Drop the hand-written Unreleased section from the generated CHANGELOG. - Mark the divergent record-coalescing helpers with a TODO. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… restores it Rework of 63a7f8e, which skipped undecodable records everywhere. Normal operation is strict again: the per-round repair and preserve_known_details treat a corrupt stored record as an error. V019 (which has no recovery mode) drops an undecodable record only when a Core resync re-delivers it: the row has a block height, so a filter rescan finds it again. It then deletes the row and its input-index rows and lowers that wallet's synced_height to just below its birth height; load hands that checkpoint to SPV, which rescans from birth and re-records the transaction. Spent marks and outputs it already produced are kept until the rescan confirms them. An unconfirmed corrupt record cannot be restored that way, so the migration still fails and rolls back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: OpenAI Codex <noreply@openai.com>
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks 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 |
4 tasks done
Collaborator
|
🕓 Review not started yet because this PR is a draft.
Commit b27eb83. Normal review starts when eligible; priority review starts as soon as a slot is available. |
This was referenced Sep 30, 2026
…wallet-restore # Conflicts: # CHANGELOG.md
5 of 7 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
Reopening a SQLite-backed wallet restores its Core address reservations and used-key positions, so previously handed-out funding keys are not offered again.
User story
As a wallet user, I can restart the app with my saved Core balances, pending spends and address reservations intact, and receive an error when required saved data cannot be restored.
Scenario
Use funding addresses or reserve a new address, close the wallet, and reopen the same database. The wallet restores the used and reserved addresses before allocating another key. Concurrent database writes cannot mix different snapshots within one load.
Detailed discussion
Dependencies and overlap
Stacked on #5207 (
fix/sqlite-asset-lock-reconciliation). Also incorporates #5208 at338067889e4c01651eb1742d5ba83e09955c1834and its #5150 prerequisites. The new implementation commit changes four files. Their history replay, pending-spend reservations, accounting repair and migrations are reused unchanged. Until those prerequisites land in the base stack, GitHub's cumulative diff includes their commits; this PR's new implementation is limited to SQLite pool restoration, coherent loading and focused tests.What was done?
WALLET_RESTOREcapability after completing its Core-snapshot restore path. Capability meanings and bit values remain unchanged.PROVIDER_TRANSACTIONSis not newly declared.WALLET_RESTOREis the existing persisted Core wallet snapshot contract. Separate token caches, DashPay profile overlays, deferred contact queues and invitation UI records are not made complete by this flag; their readback remains separate. DET's #1036 pin remains on the narrower #5207.How Has This Been Tested?
cargo fmt -p platform-wallet-storage -- --check, scoped all-target/all-feature Clippy with--no-deps -D warnings, and diff checks passed.Breaking Changes
None. No FFI callback, layout or capability-contract change.
Checklist
🤖 Co-authored by Claudius the Magnificent AI Agent