Repository navigation
Conversation
Build on rust-dashcore PR #979 and account-local correction/event fixes at ed4c02e119898f1bb510cf79abc8a125a9bc9bea. Cargo metadata --locked validates the pin; wallet integration checks follow with the storage changes.
Repair fully resolved history from durable TXOs at round commit and wallet load, preserve funded asset-lock accounting during context-only recovery, and scope history presentation to the selected wallet. Swift tests require macOS tooling and were not executable on this host. <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
Use one ownership predicate for persisted inputs, outputs, and scoped history amounts, including legacy watch-only TXOs. Preserve accountless rows with known wallet ownership. <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
Index historical inputs to repair late funding and existing SQLite history, preserve known ownership across partial replay, and project corrected bridge topology without double-counting account snapshots. Keep unknown credit verdicts from inventing spends or resurrecting coins. Co-Authored-By: Codex GPT-6 <noreply@openai.com> <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
Replace the PR 979-based dependency with the isolated accounting correction backported onto the original e4208c90 base. The identical upstream patch is available on current rust-dashcore dev without SPV or address-pool changes. Validated 24 Core storage and 132 changeset tests with the new pin; scoped Clippy with CI flags and formatting checks pass. Co-Authored-By: Codex <noreply@openai.com>
|
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 configuration
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 |
|
⛔ Final review complete — 4 blocking finding(s) (commit d397edb) · triage: critical |
#5043 introduced PLATFORM_V15 and made it latest, but the committed grovedb-structure.json and one structure test still expected protocol 14, so `structure::tests` has failed on v4.3-dev since it merged: - should_match_committed_grovedb_structure_json: regenerate the snapshot. Only the origin labels and latest_protocol_version change (14 -> 15); v15 has the same state structure as v14. - should_record_a_contract_layer_with_its_documents_on_top: read the expected origin from PlatformVersion::latest() instead of a hard-coded 14, so the next protocol bump does not break it again. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sync Replay persisted confirmed transactions through the wallet checker on load, retain only persisted unspent outputs not consumed by confirmed history, and restore finality before sync checkpoint pruning. Use dash-async for synchronous persistence loading. Cover restart, stale projections, funding redelivery, finality, same-block spends, reserved inputs, and synchronous/current-thread/multi-thread callers. Co-authored-by: Codex <noreply@openai.com> <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
…count The load projection parks an unattributable UTXO in the first funds account; replaying confirmed history then credits the same outpoint to its real owner. The post-replay filter only checked a wallet-wide unspent set, so both copies survived and update_balance double counted. Drop the load-time fallback copy whenever replay credits the outpoint to a different funds account, while still refusing to re-credit anything persistence excluded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dable Pre-fix builds persisted DashPay contact (watch-only) outputs as unspent core_utxos rows. V019 re-labels their history as Sent but left the rows unspent, so load routed them to the first funds account: the balance included coins the wallet cannot sign and coin selection could pick one. load_state now skips unspent contact-only rows. The rows are kept in SQLite (no destructive migration step), so the exclusion is reversible. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eset loadWalletList saved or rolled back the background context unconditionally after reconciling transaction accounting. Inside an open begin/endChangeset round that committed half of the round or silently discarded its staged writes while the round still reported success. Guard the pass with !inChangeset like every other load-time writer; the round reconciles its own dirty rows and the next load runs the full pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thepastaclaw
left a comment
There was a problem hiding this comment.
Preliminary review — Phase 1 blocker gate
The SQLite repair logic addresses the stated restart and historical-accounting failures, but the confirmed-spend replay is implemented only in the SQLite persister while the FFI/SwiftData restore path does not rebuild the corresponding spend guards. Several additional correctness and scalability issues remain in the new history-repair and Swift reconciliation code.
Validated blockers were found by the Phase-1 review and confirmed by a fresh verifier. Phase 2 is deferred until a fresh same-head revalidation clears the blocker gate.
🔴 1 blocking | 🟡 7 suggestion(s)
1 finding(s) not shown inline (the lines are not part of this PR's diff)
🟡 Suggestion: History replay holds the SQLite connection lock for the full confirmed history
packages/rs-platform-wallet-storage/src/sqlite/persister.rs
load_one_wallet invokes restore_confirmed_transactions while load holds the connection mutex. The replay performs an async block_on round trip and runs check_core_transaction once per confirmed record, even though the replay itself does not access SQLite. Large histories therefore block concurrent store and flush operations for the duration of the replay. Snapshot the required records and release the connection lock before replaying, or document and bound this startup write-stall behavior.
source: muse-spark-1.3-contributor (phase1-reviewer: general, architecture-layering, rust-quality)
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: architecture-layering); reviewer 3: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6-astra (agent: astra-gate-verifier, role: verifier)
- Triage:
criticalbygpt-6-astra(effort low) — The diff combines intricate persisted-accounting repair and a new SQLite migration with changes to restore_confirmed_transactions in packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs that directly determine spendable UTXOs through transaction replay, spent-output exclusion, account deduplication, and finality restoration. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— architecture-layering (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 13% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Fresh verifier:
gpt-6-astra— verifier; agentastra-gate-verifier - Phase 2 reviewers: not run (deferred by blocker gate)
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs:423-505: Confirmed-history replay is implemented only for SQLite
The restart invariant is that every persister must hand WalletManager a spend projection containing confirmed spent outpoints. This change enforces that invariant only in SQLite by replaying persisted records through `check_core_transaction` inside `restore_confirmed_transactions`. The FFI restore path still reconstructs spendability from persisted TXO rows, and the Swift reconciliation pass only rewrites `netAmount` and `direction`; it does not rebuild Rust's `spent_outpoints` or remove confirmed inputs from the in-memory UTXO set. Consequently, a wallet restored through the FFI/SwiftData path can still rehydrate a confirmed-spent output as selectable, while SQLite behaves differently. Move the confirmed replay to the shared async `load_from_persistor` boundary, or provide equivalent replay semantics for every persister before the manager consumes the start state.
In `packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift`:
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift:6811-6863: SwiftData independently re-derives Rust-owned accounting
The SwiftData repair path recomputes ownership, amounts, and direction from decoded prevouts, while SQLite's `repair_record` derives the same values from persisted UTXO state and the Rust wallet projection. These implementations already have materially different rules: Swift requires every input prevout to resolve, passes `allOutputsOwned = false` to its helper, and re-encodes contact exclusion using a raw account-type tag, whereas the Rust repair can recover ownership from the UTXO table and distinguishes internal transactions from external outputs. Future checker or contact-ownership changes can therefore make the two persistence backends repair the same transaction differently. Centralize this accounting projection in Rust and have Swift persist the returned verdict rather than reimplementing it.
- [SUGGESTION] packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift:6915-6934: Load-time repair scans the entire store and fails all wallets on one bad row
`loadWalletList` fetches every `PersistentTransaction` and every `PersistentTxo`, then filters transactions in memory for the wallets being loaded. This makes startup work proportional to the entire SwiftData store even when loading one wallet. The same `do` block also treats any reconciliation or fetch error as a failure for the complete wallet list, so one unreadable transaction or TXO hides otherwise healthy wallets. Scope both fetches to the loaded wallet IDs, or batch per wallet and isolate per-wallet reconciliation failures.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs`:
- [SUGGESTION] packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs:32-36: Same-txid redelivery with different witness bytes aborts persistence
`preserve_known_details` rejects a persisted transaction whenever `previous.transaction != incoming.transaction`. A transaction's txid excludes witness data, so legitimate observations of the same txid can differ only in witness bytes. That causes the entire changeset to fail even though the row is already keyed by txid and the ownership details below could be merged. Accept same-txid redeliveries whose non-witness transaction identity is unchanged, or compare an appropriate txid-level identity rather than full transaction equality. If a mismatch must remain fatal, include the txid in the typed error so the failure is diagnosable.
- [SUGGESTION] packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs:150-157: Contact filtering hardcodes a database label
`contact_only_script` embeds `account_type != 'dashpay_external'` directly in SQL, while the canonical mapping is `accounts::account_type_db_label`. If the persisted label changes, the writer and this repair query can silently diverge and contact-only outputs can be treated as wallet-owned during repair. Use a shared constant or construct the query with the canonical label and add a test tying the SQL value to `account_type_db_label`.
- [SUGGESTION] packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs:292-313: Migration performs redundant decoding and wallet lookups per record
During migration, each row is decoded by `get_tx_record`, then `repair_record` calls `get_tx_record` again for the same txid. The loop also calls `network(tx, &wallet_id)` once per transaction, which repeats the wallets-table lookup for every record. Because this runs inside the migration transaction, large histories hold the migration lock while doing twice the decoding and redundant queries. Cache the network per wallet and pass the already-decoded record into the repair function.
In `packages/rs-platform-wallet/src/changeset/changeset.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/changeset/changeset.rs:628-643: Account coalescing mixes finality from one observation with body data from another
When a later record has lower context rank, the code keeps the later record's amounts, inputs, and outputs but replaces only its context with the earlier record's more-confirmed context. That produces a synthetic record combining the stale observation's body with finality it did not carry. If corrected-then-stale ordering is reachable, the higher-ranked observation should win wholesale; if it is impossible, this branch is dead and should be removed. Add a regression test for corrected-then-stale ordering and replace the record wholesale based on context rank.
In `packages/rs-platform-wallet-storage/src/sqlite/persister.rs`:
- [SUGGESTION] packages/rs-platform-wallet-storage/src/sqlite/persister.rs: History replay holds the SQLite connection lock for the full confirmed history
`load_one_wallet` invokes `restore_confirmed_transactions` while `load` holds the connection mutex. The replay performs an async `block_on` round trip and runs `check_core_transaction` once per confirmed record, even though the replay itself does not access SQLite. Large histories therefore block concurrent store and flush operations for the duration of the replay. Snapshot the required records and release the connection lock before replaying, or document and bound this startup write-stall behavior.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…PV15 #5057 folded DRIVE_ABCI_QUERY_VERSIONS_V3 into V2 (identical values) and deleted v3.rs; PV14 was updated but PV15, introduced on v4.3-dev, still imported V3, so the v4.2-dev -> v4.3-dev merge stopped compiling. Pure rename; the selected query versions are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The v4.2-dev -> v4.3-dev merge (704307e) left an unused import and stray blank lines in dpns.rs, failing clippy -D warnings and rustfmt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The structure generator describes PlatformVersion::latest(), which is 15 once PLATFORM_V15 compiles. The committed JSON and a hard-coded "@14" assertion were stale. Regenerated the JSON (version labels only) and derive the expected fixture origin from PlatformVersion::latest(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restore recordless spent guards with claimant provenance, remove swept asset-lock lifecycle rows atomically, reject unsafe replay operands, and return wallet-scoped reconciliation failures. Pin the minimal upstream restoration API and cover restart, finality, conflicts, and rollback. Validation: 133 targeted tests, package rustfmt, and CI-matching Clippy pass. Co-Authored-By: Codex (GPT-6) <noreply@openai.com> <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
One in-scope blocker remains: load-time conflict repair can erase a durable spend claim with a NULL claimant and make the coin selectable. The cumulative-sum overflow report is refuted by the per-iteration range check, while full-history replay cost remains explicitly deferred. Verification was static; the supplied CI snapshot contains no Rust-wallet or Swift-migration test results, and the documented upstream-pin merge gate remains outstanding.
🔴 1 blocking
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The large, intricate changes in sqlite/rehydrate.rs, sqlite/schema/core_state.rs, and sqlite/migrations/legacy_v019.rs alter persisted spend claims, spendable-output restoration, and storage migration behavior, directly affecting coin selection and durable wallet state. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/core_state.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/schema/core_state.rs:529-533: Preserve unknown durable claims during load-time conflict repair
The `.optional()?.flatten()` treats an existing `spent = 1` row with a NULL `spent_in_txid` exactly like an absent row, so it bypasses the durable-claim veto. NULL provenance is supported: V019 can delete an unreadable confirmed spender while retaining its spent outputs, and the transaction-delete trigger clears `spent_in_txid` without clearing `spent`.
Persist a mempool loser L spending A and B, a settled winner W spending only A, and an independent unknown durable claim on B whose spender body is absent. Conflict planning runs before durable claims are restored into memory and reports B released. This branch skips B; the generic sweep then marks its materialized row unspent or deletes its placeholder. The second snapshot rebuild consequently loses the claim and can return an already-spent coin as selectable. Restoring spent outpoints afterward cannot recover a row that has been released or deleted.
Distinguish an absent row from a present row with an unknown claimant. Preserve the latter unless removal is established, including its nullable provenance through the post-sweep restoration. Add a restart regression that persists the conflicting records and NULL claimant before loading, for both materialized and placeholder rows. The existing unknown-claim matrix creates its competing spend only after load, so it does not exercise this SQL repair path.
Out-of-scope follow-up suggestions (1)
These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.
- Complete confirmed-history restoration at the shared persistence boundary — The reviewed shared loader still replays only host-supplied unconfirmed outgoing transactions; complete recorded-history replay remains SQLite-specific. The confirmed-spend restart gap in the FFI/SwiftData path predates this PR, and the author explicitly separates the ABI-changing restoration work into #5220.
- Follow-up: Retain the confirmed-spend FFI restart regression and shared recorded-history restoration work in the already-tracked #5220.
|
Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix, 1 thread unresolved — resolve it. Wait for the missing reviews, or a writer can post |
Keep existing spent rows with NULL claimants distinct from absent rows. Preserve their nullable claimant and height through conflict repair, reload, and funding redelivery while allowing known loser claims to release. Validation: 76 targeted restart/sweep tests, rustfmt, and CI-matching Clippy. Co-Authored-By: Codex (GPT-6) <noreply@openai.com> <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
|
Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix, 2 threads unresolved — resolve them. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
At head a2b6f50, 19 prior findings are addressed and six explicitly scoped follow-ups remain deferred. Two in-scope blockers remain: ordinary mempool persistence omits claimant provenance needed for conflict release, and malformed peer observations can be persisted successfully but subsequently prevent wallet restoration. Verification was static only; the supplied CI snapshot still had Rust workspace tests and the Swift frozen-schema check running, with dedicated Rust wallet tests skipped.
🔴 2 blocking
Review provenance
Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 8: gpt-6-astra (agent: phase2-reviewer, role: general); reviewer 9: gpt-6-astra (agent: phase2-reviewer, role: architecture-layering); reviewer 10: gpt-6-astra (agent: phase2-reviewer, role: platform-versioning); reviewer 11: gpt-6-astra (agent: phase2-reviewer, role: rust-quality); reviewer 12: gpt-6-astra (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The large, intricate changes in sqlite/rehydrate.rs, sqlite/schema/core_state.rs, and sqlite/migrations/legacy_v019.rs directly alter persisted spend claims, spendable-output restoration, conflict resolution, and storage migration behavior. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— general (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6-astra— security-auditor (completed, effort xhigh); agentphase2-reviewer - Model comparison: every Phase-2 reviewer also ran on
gpt-6-astra; the verifier weighed both sets without knowing which model wrote which
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs:222-225: Record claimant provenance for newly persisted mempool spends
The new protection for unknown durable claims also captures ordinary mempool reservations created by the production writer. TransactionDetected projects owned inputs into spent_utxos, but core_state::apply only sets spent = 1, and this condition skips assigning spent_in_txid for Mempool records. If a persisted loser spends A+B and a separately persisted InstantSend winner spends only A, replay reports B as released. apply_replay_sweeps nevertheless withholds B because its claimant is NULL, deletes the loser, and restores B's NULL spend claim; subsequent loads keep B unavailable even though no surviving transaction spends it. The release regression masks this mismatch by manually assigning the loser's txid at sqlite_spent_rehydration.rs:970–983, which the normal mempool persistence path does not do. Atomically record known claimant provenance when creating a new live mempool reservation, without overwriting unrelated or genuinely unknown historical claims. Add a store/reopen/conflict regression without the fixture's manual provenance UPDATE. This is release after a proven conflict, not the deferred expiry of unresolved mempool observations.
In `packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs:634-642: Reject malformed mempool amounts before they can poison wallet startup
With mempool monitoring enabled, a malicious connected peer can send a decodable transaction containing a foreign input, a small output to a watched receive address, and a foreign P2PKH output worth i64::MAX duffs. In the exact pinned dependency, MempoolManager::handle_tx forwards the transaction to process_mempool_transaction without monetary-range validation. Account matching sums only owned outputs, and record_transaction omits ordinary foreign output details when there are no owned inputs, so this observation produces a small positive receipt without overflowing live accounting. core_bridge then persists the complete transaction body and only the small owned UTXO; core_history::repair_record also calculates accounting from owned details, allowing persistence to succeed. On reopening, this new validation sums all raw outputs and returns IntegerOverflow. The error propagates through load_wallet_snapshot: default Strict loading fails the store, while Recovery omits the affected wallet. The offending record remains stored, so later startups fail without further attacker interaction. Keep the replay arithmetic guard, reject unsafe unauthenticated observations before persistence, and safely isolate or quarantine already-persisted malformed mempool records. Cover the ingress-to-persistence-to-reopen path with a small owned receipt and an overflowing foreign-output total. This attack needs no victim-owned input and is distinct from the deferred reservation-expiry issue.
Out-of-scope follow-up suggestions (1)
These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.
- Bound startup history replay without weakening spend guards — Outside the focused repair scope and already reconciled under 62e8727af31c. Full-history replay still holds the connection mutex, but bounding it is an explicitly deferred performance change rather than an additional correctness requirement.
- Follow-up: Consider creating a separate issue or author/maintainer-requested PR for this.
| // Stale mempool rows cannot overrule a later sweep's release. | ||
| if !matches!(record.context, TransactionContext::Mempool) { | ||
| // Record the spender so the mark stays attributable and | ||
| // reversible; an existing claim by another spender stands. |
There was a problem hiding this comment.
🔴 Blocking: Record claimant provenance for newly persisted mempool spends
The new protection for unknown durable claims also captures ordinary mempool reservations created by the production writer. TransactionDetected projects owned inputs into spent_utxos, but core_state::apply only sets spent = 1, and this condition skips assigning spent_in_txid for Mempool records. If a persisted loser spends A+B and a separately persisted InstantSend winner spends only A, replay reports B as released. apply_replay_sweeps nevertheless withholds B because its claimant is NULL, deletes the loser, and restores B's NULL spend claim; subsequent loads keep B unavailable even though no surviving transaction spends it. The release regression masks this mismatch by manually assigning the loser's txid at sqlite_spent_rehydration.rs:970–983, which the normal mempool persistence path does not do. Atomically record known claimant provenance when creating a new live mempool reservation, without overwriting unrelated or genuinely unknown historical claims. Add a store/reopen/conflict regression without the fixture's manual provenance UPDATE. This is release after a proven conflict, not the deferred expiry of unresolved mempool observations.
source: gpt-6-astra (phase2-reviewer: general)
| total( | ||
| "replay.outputs", | ||
| record | ||
| .transaction | ||
| .output | ||
| .iter() | ||
| .chain(credit_outputs) | ||
| .map(|o| o.value), | ||
| )?; |
There was a problem hiding this comment.
🔴 Blocking: Reject malformed mempool amounts before they can poison wallet startup
With mempool monitoring enabled, a malicious connected peer can send a decodable transaction containing a foreign input, a small output to a watched receive address, and a foreign P2PKH output worth i64::MAX duffs. In the exact pinned dependency, MempoolManager::handle_tx forwards the transaction to process_mempool_transaction without monetary-range validation. Account matching sums only owned outputs, and record_transaction omits ordinary foreign output details when there are no owned inputs, so this observation produces a small positive receipt without overflowing live accounting. core_bridge then persists the complete transaction body and only the small owned UTXO; core_history::repair_record also calculates accounting from owned details, allowing persistence to succeed. On reopening, this new validation sums all raw outputs and returns IntegerOverflow. The error propagates through load_wallet_snapshot: default Strict loading fails the store, while Recovery omits the affected wallet. The offending record remains stored, so later startups fail without further attacker interaction. Keep the replay arithmetic guard, reject unsafe unauthenticated observations before persistence, and safely isolate or quarantine already-persisted malformed mempool records. Cover the ingress-to-persistence-to-reopen path with a small owned receipt and an overflowing foreign-output total. This attack needs no victim-owned input and is distinct from the deferred reservation-expiry issue.
source: gpt-6-astra (phase2-reviewer: security-auditor)
|
Your move: thepastaclaw requested changes on this head; dismiss the review or push a fix; thepastaclaw left review threads unresolved; resolve them. |
Drive real wallet events through the SQLite adapter, reopen the database, and verify funding replay cannot make spent coins available. Cover pending, mined and finalized spends plus recordless conflict winners. Removing only restore_spent_outpoints reproduces both recordless-winner failures while ordinary spends and uninterrupted controls remain correct. Co-Authored-By: Codex <noreply@openai.com> <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
Merge current v5.1-dev and PR #4932, then pin all eight rust-dashcore dependencies to PR #1112 (870af146). Preserve DPP crypto implementations, canonical node IDs, and legacy Core RPC behavior. Use the upstream blst no_std API fix at 71a00877 for WASM compatibility. Validated wallet/storage, signing/BLS, encryption and RPC regressions, scoped strict Clippy, and WASM builds. The published tree matches the locally tested tree b69a241. <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
|
Waiting for bot review — coderabbitai not yet · thepastaclaw requested changes — dismiss the review or push a fix, 4 threads unresolved — resolve them. Wait for the missing reviews, or a writer can post |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
Verified the exact head and independently reconciled all 27 prior findings. Two carried blockers remain at transaction admission and mempool spend attribution; the new restoration API also loses protection for later-added funding accounts, and two new query sites fail the existing SQLite CI gate. This was a static review: the Rust job reports 17,171 passing tests, one failure, and 1,318 tests not run; the platform and browser suites have since passed, while the Swift build was skipped.
🔴 4 blocking
2 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 8: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The large, intricate changes in packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs, src/sqlite/schema/core_state.rs, and src/sqlite/migrations/legacy_v019.rs directly alter persisted spend claims, spendable-output restoration, conflict resolution, and storage migration behavior. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— ffi-engineer (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-wallet-storage/src/sqlite/persister.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/persister.rs:1905-1908: Keep restored spend claims effective for later-added funding accounts
At the pinned revision, ManagedWalletInfo::restore_spent_outpoints copies claims only into the funding accounts currently present; it retains no wallet-level claim set. Its documentation explicitly requires reapplication after adding funding accounts. Platform calls it only here during loading, while register_contact_account later constructs and inserts a fresh ManagedCoreFundsAccount without restoring those claims. A recordless or unknown-owner claim restored before its receiving account is registered is therefore absent from that new account. When the funding transaction is redelivered, the new account can credit the consumed output: update_utxos consults that account's spent set, and the recordless case need not retain spender history or an observed-spend entry. This breaks the PR's promised protection for unknown-owner and recordless claims. Retain durable claims at wallet scope and apply their exclusion to every funding account, including later insertions, or provide an equivalent guaranteed registration hook. Add a restart regression that restores a recordless claim, registers its previously unknown receiving account, and redelivers the funding transaction.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/asset_locks.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/schema/asset_locks.rs:209-211: Restore the SQLite query-caching invariant for the new queries
The exact-head Rust job fails tc_p1_003_prepare_cached_in_writers on this new tx.prepare call and conn.prepare in core_state::load_spent_claims at line 973. I checked the test and failed-job log: these are its two reported offenders, and neither query matches the explicit read-only allowlist. This is the workspace job's sole reported failure and cancels execution of 1,318 remaining tests. Use prepare_cached at both sites, or explicitly register an eligible read-only query in the existing allowlist, so the workspace gate can complete.
In `packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs:634-642: Reject malformed mempool amounts before they can poison wallet startup
(existing thread: https://github.com/dashpay/platform/pull/5150#discussion_r4185552401)
The replay bounds are checked only after the transaction body has become durable. At the exact rust-dashcore pin, MempoolManager::handle_tx forwards peer transactions to wallet processing without monetary validation, and the account checker sums only matching wallet outputs. A transaction with foreign inputs, a small output to a wallet address, and a separate foreign output worth 1_u64 << 63 therefore has small wallet-owned accounting. The foreign output is omitted from an incoming record's accounting details but remains in its raw transaction body. core_state::apply can store that body, and core_history::repair_record checks the owned net rather than every raw output. On reopening, this loop rejects the foreign amount with IntegerOverflow: Strict load fails, and Recovery omits the affected wallet. Reject malformed observations before state mutation and record emission, and enforce replay-safe amount bounds before committing incoming history. Retain the defensive load checks and add an event-driven ingestion → persistence → reopen regression; the existing malformed-blob fixtures do not cover admission.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs:222-225: Record claimant provenance for newly persisted mempool spends
(existing thread: https://github.com/dashpay/platform/pull/5150#discussion_r4185552392)
The production TransactionDetected bridge emits spent_utxos for pending transactions, but core_state::apply writes only spent = 1, and this guard skips assigning spent_in_txid for Mempool records. A freshly persisted pending spend consequently reloads with NULL claimant provenance even though its spending record identifies the claimant. The new restoration API makes that omission consequential: restore_spent_outpoints installs a None claim, and upstream release_spent_marks deliberately retains it when the actual transaction is removed. For a pending loser spending A+B and a final winner spending only A, B remains reserved after restart even after the loser is definitively removed. Startup reconciliation similarly vetoes B's release because its persisted claimant is NULL. The existing release controls manually UPDATE spent_in_txid to the loser, bypassing the missing production write; the clean event-driven tests cover preservation rather than this extra-input release. Attribute spends when the current changeset establishes them, separately from historical accounting repair, while preserving genuinely unknown claims and preventing stale mempool history from recreating released reservations. Add a clean event-driven two-input loser → restart → one-input final winner → funding-redelivery regression.
Out-of-scope follow-up suggestions (1)
These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.
- Bound unauthenticated pending-spend reservations — The pinned peer-processing path still reserves tracked inputs without verifying spending signatures, and replay preserves these reservations without provenance-aware expiry or revalidation. The PR explicitly separates this lifecycle work into #5225. It remains a concrete security follow-up, distinct from the in-scope failure to release a definitively defeated claimant.
- Follow-up: Complete the tracked #5225 work across peer processing, wallet state, and persistence, clearing expired or rejected reservations consistently without releasing settled claims.
| // Restore durable claims after replay/finality, including claims with no spend body. | ||
| let spent = | ||
| schema::core_state::load_spent_claims(conn, &wallet_id).map_err(PersistenceError::from)?; | ||
| wallet_info.restore_spent_outpoints(&spent); |
There was a problem hiding this comment.
🔴 Blocking: Keep restored spend claims effective for later-added funding accounts
At the pinned revision, ManagedWalletInfo::restore_spent_outpoints copies claims only into the funding accounts currently present; it retains no wallet-level claim set. Its documentation explicitly requires reapplication after adding funding accounts. Platform calls it only here during loading, while register_contact_account later constructs and inserts a fresh ManagedCoreFundsAccount without restoring those claims. A recordless or unknown-owner claim restored before its receiving account is registered is therefore absent from that new account. When the funding transaction is redelivered, the new account can credit the consumed output: update_utxos consults that account's spent set, and the recordless case need not retain spender history or an observed-spend entry. This breaks the PR's promised protection for unknown-owner and recordless claims. Retain durable claims at wallet scope and apply their exclusion to every funding account, including later insertions, or provide an equivalent guaranteed registration hook. Add a restart regression that restores a recordless claim, registers its previously unknown receiving account, and redelivers the funding transaction.
source: gpt-6.1-sol (phase2-reviewer: architecture-layering)
| let mut stmt = tx.prepare( | ||
| "SELECT length(outpoint), outpoint FROM asset_locks WHERE wallet_id = ?1 AND status != 'consumed'", | ||
| )?; |
There was a problem hiding this comment.
🔴 Blocking: Restore the SQLite query-caching invariant for the new queries
The exact-head Rust job fails tc_p1_003_prepare_cached_in_writers on this new tx.prepare call and conn.prepare in core_state::load_spent_claims at line 973. I checked the test and failed-job log: these are its two reported offenders, and neither query matches the explicit read-only allowlist. This is the workspace job's sole reported failure and cancels execution of 1,318 remaining tests. Use prepare_cached at both sites, or explicitly register an eligible read-only query in the existing allowlist, so the workspace gate can complete.
| let mut stmt = tx.prepare( | |
| "SELECT length(outpoint), outpoint FROM asset_locks WHERE wallet_id = ?1 AND status != 'consumed'", | |
| )?; | |
| let mut stmt = tx.prepare_cached( | |
| "SELECT length(outpoint), outpoint FROM asset_locks WHERE wallet_id = ?1 AND status != 'consumed'", | |
| )?; |
source: gpt-6.1-sol (phase2-reviewer: general)
Merge the migration-only base without changing the source tree. <sub>🤖 Co-authored by [Claudius the Magnificent](https://github.com/lklimek/claudius) AI Agent</sub>
|
PR Hygiene is not checking this pull request: it targets |
thepastaclaw
left a comment
There was a problem hiding this comment.
Re-review — Final validation — Phase 1 + Phase 2
Verified the exact head d397edb, its tree equivalence to the previous review head, and all 29 prior findings against the source. Four in-scope blockers remain: statement-cache gate compliance, restored claims missing from subsequently added accounts, malformed amounts admitted before replay validation, and missing claimant provenance for fresh mempool spends. This was a static review; no builds or tests were run, and the supplied CI snapshot still has Rust workspace tests pending.
🔴 4 blocking
4 carried-forward finding(s) already raised on this PR; not re-posting as new inline comments.
Review provenance
Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: architecture-layering); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: platform-versioning); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: rust-quality); reviewer 5: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 7: muse-spark-1.3-contributor (agent: phase1-reviewer, role: rust-quality); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)
- Triage:
criticalbygpt-6.1-sol(effort low) — The diff makes intricate changes to spent-output restoration and replay in packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs and introduces the historical-accounting storage migration in src/sqlite/migrations/legacy_v019.rs, directly affecting coin selectability and persisted wallet state. - Phase 1 reviewers:
muse-spark-1.3-contributor— general (completed, effort xhigh); agentphase1-reviewer,muse-spark-1.3-contributor— rust-quality (completed, effort xhigh); agentphase1-reviewer - Phase 1 model:
muse-spark-1.3-contributor— not quota-gated; passed overgemini-3.8-flash-high(antigravity below 15% reserve: weekly 15% left, 5h 100% left),glm-5.3-flash(not used above high effort; tier asks max) - Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
- Fresh verifier:
gpt-6.1-sol— final-verifier; agentsol-verifier - Phase 2 reviewers:
gpt-6.1-sol— general (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— architecture-layering (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— platform-versioning (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— rust-quality (completed, effort xhigh); agentphase2-reviewer,gpt-6.1-sol— security-auditor (completed, effort xhigh); agentphase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/asset_locks.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/schema/asset_locks.rs:209-211: Restore the SQLite query-caching invariant for the new queries
(existing thread: https://github.com/dashpay/platform/pull/5150#discussion_r4194208705)
The source-scanning test `tc_p1_003_prepare_cached_in_writers` rejects schema `.prepare()` calls unless their SQL matches `READ_ONLY_PREPARE_ALLOWED`. Neither this `remove_swept` query nor `core_state::load_spent_claims` at line 973 matches an allowed entry, so the current tree deterministically violates that gate. The relevant evidence is this statement-cache test, not the separate load query-count bound cited in one reviewer explanation. Use `prepare_cached` at both sites, or add narrowly justified read-only exceptions to the existing allowlist; fixing only this query leaves the second offender. The disclosed targeted test results do not establish that this gate passes.
In `packages/rs-platform-wallet-storage/src/sqlite/persister.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/persister.rs:1905-1908: Keep restored spend claims effective for later-added funding accounts
(existing thread: https://github.com/dashpay/platform/pull/5150#discussion_r4194208698)
At the exact pinned rust-dashcore revision `870af146`, `ManagedWalletInfo::restore_spent_outpoints` only distributes claims to existing funding accounts. It retains no wallet-level claim ledger, and its documentation explicitly requires reapplication after adding funding accounts. This loader discards the loaded vector after that call, while `PlatformWalletInfo` account-add methods delegate upstream and `register_contact_account` inserts a fresh `DashpayReceivingFunds` account without transferring the claims. Consequently, a recordless or unknown-owner durable spend protects accounts present at startup but not a subsequently added account owning the funding script; funding redelivery can credit the already-spent output, especially after the temporary observed-spend guard has been pruned. Retain and propagate the durable claims at a wallet-wide boundary, or reapply them on every funding-account addition. Add a load → add matching account → redeliver funding → coin-selection regression.
In `packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/rehydrate.rs:634-642: Reject malformed mempool amounts before they can poison wallet startup
(existing thread: https://github.com/dashpay/platform/pull/5150#discussion_r4185552401)
The new operand and aggregate checks are enforced on load, but ordinary ingestion and persistence do not enforce the same acceptance domain. In the pinned dependency, `MempoolManager::handle_tx` forwards peer transactions to the wallet checker without a monetary-range check. A transaction containing a small owned receipt and a foreign output valued at `1_u64 << 63` produces fitting wallet-relative accounting: the account checker sums matched outputs, the record retains the complete transaction body, and SQLite repair computes only owned outputs minus owned inputs. `core_state::apply` can therefore commit that body successfully. On the next load, this raw-output validation returns `IntegerOverflow`; Strict load aborts, while Recovery omits the affected wallet. The existing amount regressions inject malformed stored blobs rather than exercising this admission path. Validate replay-relevant amounts and totals before accepting or committing incoming records, retain this replay guard as corruption defense, and provide a safe disposition for already-stored invalid unconfirmed records. Cover peer/event ingestion followed by reopening.
In `packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs`:
- [BLOCKING] packages/rs-platform-wallet-storage/src/sqlite/schema/core_history.rs:222-225: Record claimant provenance for newly persisted mempool spends
(existing thread: https://github.com/dashpay/platform/pull/5150#discussion_r4185552392)
Fresh mempool spends reach `core_state::apply` through `cs.spent_utxos`, whose materialized-row writer only sets `spent = 1`; its upsert also leaves new claimant provenance NULL. This repair branch skips attribution for mempool records, even when the incoming record identifies the spender. The new replay repair then correctly treats those NULL rows as independent unknown durable claims and vetoes their release. If a pending loser spends A and B but its settled competitor spends only A, replay removes the loser and identifies B as released, yet SQLite keeps B spent and subsequently restores its unknown guard. The loser history is deleted, leaving no attributable claim to remove. `assert_conflict_restart` masks this ordinary write-path state by manually assigning the loser txid before load. Attribute newly accepted spend reservations atomically from incoming record/input evidence, without overwriting pre-existing unknown or independent claims. Keep the stale-mempool repair guard and add an event-produced conflict/restart regression without SQL provenance patching; this is distinct from the deferred reservation-expiry policy.
Out-of-scope follow-up suggestions (2)
These are valid observations, but they are outside this PR's scope and should be handled in separate issues or author/maintainer-requested PRs rather than blocking this review.
- Expire or revalidate unauthenticated mempool reservations — The peer-processing path still permits unauthenticated observations to reserve wallet inputs, and replay preserves those reservations without expiry. The implementation explicitly documents this behavior, and the PR assigns provenance-aware validation, expiry, and coordinated removal to #5225. This remains a concrete availability exposure, separate from the attributable-conflict release defect retained above.
- Follow-up: Complete #5225 with coordinated persisted and in-memory release semantics that preserve confirmed and InstantSend-locked claims.
- Complete shared history restoration across persistence backends — Confirmed-history replay remains SQLite-specific: the shared manager consumes projected wallet state, and FFI restoration carries the separate unconfirmed-outgoing replay bodies rather than complete recorded history. This backend gap predates the PR and is explicitly separated into #5220 because closing it changes the FFI ABI and Swift load path.
- Follow-up: Validate #5220 separately, including its full FFI funding-redelivery and spend-guard restoration coverage.
Two cases where a free coin stays marked spent after a restartFound while reviewing the wallet side (rust-dashcore #1112) against this PR at 1. A pending spend is restored as an unknown, permanent claim
Sequence: Possible fix on this side: at load, pass 2. A held input is attributed to the sweep's winner, not to the transaction still spending itThe live sweep path attributes every non-released input of a removed transaction to Sequence: Also noted
🤖 Co-authored by Claudius the Magnificent AI Agent |
TL;DR: Repair Core transaction history that omits spent inputs and prevent already-spent outputs from becoming selectable after a SQLite wallet restart.
Stacked on #5307, the
v5.1-devport of #4932. The rust-dashcore dependency migration is reviewed in that base PR; this diff contains the wallet accounting and persistence repairs. Merge the base PR first, then retarget this PR tov5.1-dev.Issue being fixed or feature implemented
Fixes #5126. As a wallet user, I want history to reflect what each transaction actually spent and received, including when funding is discovered late or an asset lock is restored after restart.
What was done?
The base PR supplies rust-dashcore#1112 (
870af146), including merged #1082. This PR uses its spent-claim restoration API. Core spending does not imply Platform asset-lock consumption. No consensus-versioned behavior changes are introduced in this diff.Deferred and related work
WALLET_RESTORE; feat(wallet-storage): restore complete Core wallet snapshots from SQLite #5210 adds complete SQLite snapshots; fix(platform-wallet)!: replay recorded transaction history on load for every persister #5220 moves history replay to the shared load boundary, including FFI/SwiftData. fix(platform-wallet)!: preserve Core wallet snapshots on restart #4777 remains separate.How Has This Been Tested?
5ee6f20ec4690c17a8fa4c494aea3c50801fb192.Breaking Changes
WalletStorageErrorbecomes#[non_exhaustive]and gainsTransactionBodyConflict,UnknownWalletNetworkandNetAmountOverflow; downstream exhaustive matches need a wildcard arm. SQLite adds V019 and SwiftData adds V4. Development databases that applied an earlier version of this unreleased V019 must be recreated because refinery rejects divergent migrations.Core cryptography API changes, Node.js >=20 and upstream development-database compatibility requirements belong to the base PR #5307. The FFI ABI is unchanged.
Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Co-authored by Claudius the Magnificent AI Agent