Repository navigation
feat(platform-wallet)!: keep masternode collateral out of coin selection - #5113
Draft
QuantumExplorer wants to merge 5 commits into
Draft
QuantumExplorer wants to merge 5 commits into
QuantumExplorer wants to merge 5 commits into
Conversation
…l lock backport Moves every rust-dashcore dependency from 719de34b to beb262b0, the backport of dashpay/rust-dashcore#1078 onto 719de34b (branch backport/key-wallet-collateral-lock-on-719de34b). The pin should move to the merged rev once #1078 lands. Only the rust-dashcore source lines of Cargo.lock change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wallet now keeps outputs that back a registered masternode out of coin selection, persists those locks, and exposes explicit lock and unlock. Spending the collateral would end the masternode registration. key-wallet locks the collateral of every ProRegTx a transaction check processes and skips locked coins in every selection strategy, the asset-lock builder and special-transaction funding. This carries that lock set through platform-wallet: - PlatformWalletInfo::check_core_transaction queues the outpoints a check locked. No WalletEvent carries them, so the wallet-event adapter drains the queue into the wallet's next changeset, as CoreChangeSet::outpoint_locks. The load-time replay of unconfirmed sends queues its locks the same way. - PlatformWallet::lock_outpoint / unlock_outpoint / locked_outpoints lock and unlock by hand and persist the change before returning. - On load, and when a wallet is registered, the collateral of every ProRegTx in a wallet's history and of every tracked masternode whose registration is known is locked; a tracked masternode's refresh locks it once its registration is fetched. This covers a registration the wallet never processed and one restored without a check. - SQLite: V019 adds core_locked_outpoints, keyed (wallet_id, outpoint), written by core_state::apply and handed back through lock_outpoint on load; restored coins carry their lock. - FFI: a persist / load / free trio on the persistence extension, the OUTPOINT_LOCKS capability (bit 13), and platform_wallet_lock_outpoint, platform_wallet_unlock_outpoint and platform_wallet_locked_outpoints. - Swift: PersistentLockedOutpoint rows, the trio's callbacks, and ManagedPlatformWallet.lockedOutpoints / lockOutpoint / unlockOutpoint. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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 |
Collaborator
|
🕓 Review not started yet because this PR is a draft.
Commit b5c401d. Normal review starts when eligible; priority review starts as soon as a slot is available. |
… is mined Move the rust-dashcore pin to e49cb97310, the collateral lock backport with its follow-up: key-wallet locks a collateral a ProRegTx names only once the ProRegTx is in a block (InBlock or InChainLockedBlock), and a collateral the ProRegTx creates as its own output from any sighting. An InstantSend context counts as unconfirmed. A lock also refreshes the balance on the mempool path. Only the rust-dashcore `source` lines of Cargo.lock change. The known-masternode pass follows the same rule. It locked the collateral of every ProRegTx in a wallet's transaction history, including one recorded unconfirmed; it now locks a collateral a ProRegTx names only from a record in a block, and one it creates from any record. The transaction check that confirms an unconfirmed registration locks its collateral, and the lock rides the wallet's next changeset as before. The docs that said every processed ProRegTx locks its collateral now state the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the prepare check `load_state` reads `core_locked_outpoints` with `prepare`, a read-only SELECT like the unspent-UTXO read beside it, so it joins the allow-list of `tc_p1_003_prepare_cached_in_writers`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moves the eight rust-dashcore pins from e49cb973 to 0c0dca37: the key-wallet collateral lock backport plus one fix. A transaction builder now drops a coin a funding account holds locked from the candidates just before coin selection, so a stale copy of the masternode collateral passed to `add_inputs` after `add_funding` is never spent. Before, only a copy seeded before the funding call was dropped. As with a reserved coin, the locked one is dropped silently. Only the rust-dashcore `source` lines of Cargo.lock change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 of 18 tasks
8 of 22 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.
Basic explanation
The wallet now keeps outputs that back a registered masternode out of coin selection, persists those locks, and exposes explicit lock and unlock. Spending the collateral would end the masternode registration.
The lock itself lives in key-wallet (dashpay/rust-dashcore#1078): a ProRegTx the wallet sees in a block locks the collateral it names, a ProRegTx that creates its collateral as its own output locks it from any sighting, mempool included, and every coin selection path skips a locked coin. This PR makes platform-wallet persist that lock set in every store, bring it back on load, lock the collateral of masternodes the wallet already knows about, and let hosts list, lock and unlock outpoints.
Example: a wallet holding its own 1,000 DASH collateral and a 5 DASH coin sends 1 DASH.
Before:
After:
Value: a masternode owner can keep the collateral in the wallet they spend from. Plain sends, DashPay payments, asset locks (identity funding, top-ups, platform-address and shielded funding) and the fee of a ProUpServTx leave it alone, the locked balance shows it, and the lock survives a restart.
Risks:
unlock_outpoint. A tracked masternode's collateral is locked in every wallet of the manager, so a wallet that never holds that coin lists an entry that matches nothing.backport/key-wallet-collateral-lock-on-719de34b, 0c0dca374e). The pin should move to the merged rev once chore(drive): payout fees only to single well-known Identity #1078 lands.Issue being fixed or feature implemented
key-wallet can now keep masternode collateral out of coin selection, but platform-wallet did not persist its lock set: the SQLite store reloaded every coin with
is_locked: false, the mobile stores had nowhere to keep a lock, a lock can exist before its coin arrives, and a registration the wallet never processed (funded and signed elsewhere) never locked anything.What was done?
Persistence pipeline (rs-platform-wallet)
CoreChangeSet::outpoint_locks: BTreeMap<OutPoint, bool>:truelocks,falseunlocks; merge is last-write-wins per outpoint.PlatformWalletInfo::check_core_transactionqueuesTransactionCheckResult::locked_outpoints(filled also for otherwise irrelevant transactions). NoWalletEventcarries them, so the wallet-event adapter drains the queue into the wallet's next projected changeset. TheSyncHeightAdvancedwatermark that certifies a block is a later event for the same wallet, so a lock is stored with it or before it.PlatformWallet::lock_outpoint,unlock_outpointandlocked_outpoints. Lock and unlock update the UI balance, drop any queued lock for the outpoint, and store the change before returning.PersistenceCapabilities::OUTPOINT_LOCKS(bit 13).SQLite (rs-platform-wallet-storage)
core_locked_outpoints (wallet_id, outpoint), cascading with the wallet. A table of its own, not acore_utxoscolumn, because a lock can exist before its coin and outlives the coin's spend.core_state::applyinserts and deletes rows;load_statereads them intooutpoint_locksand sets each restored coin'sis_lockedfrom them;apply_persisted_core_statehands each lock toManagedWalletInfo::lock_outpointbefore the balance refresh, so the loaded locks win.OUTPOINT_LOCKS.FFI (rs-platform-wallet-ffi)
on_persist_wallet_changeset_outpoint_locks_fn(fired in the round after the other core callbacks, only when the round changes a lock),on_load_wallet_locked_outpoints_fnandon_load_wallet_locked_outpoints_free_fn(called per restored wallet).OUTPOINT_LOCKSis attested only with all three wired and declared.platform_wallet_locked_outpoints/_free,platform_wallet_lock_outpoint,platform_wallet_unlock_outpoint.Swift SDK
PersistentLockedOutpoint(network, wallet id, outpoint), added to the unreleased live schema V3, purged with its wallet.PlatformWalletPersistenceHandler, and theoutpointLockscapability declared.ManagedPlatformWallet.lockedOutpoints(),lockOutpoint(_:),unlockOutpoint(_:).Kotlin (follow-up)
The JNI bridge builds its extension with
..Default::default(), so it compiles unchanged and leaves the trio unwired: Android keeps locks for the session only and does not attestOUTPOINT_LOCKS. Persisting them needs a Room table and migration, three bridge methods and their trampolines, and the lock / unlock / list bindings; that is a separate change.How Has This Been Tested?
New tests, all named
should_..., each with a wallet holding a 1,000 DASH collateral locked by a processed ProRegTx and a 5 DASH coin unless noted:wallet::core::transaction): largest-first 1 DASH and a drain spend only the spare; 6 DASH is refused;pooled_spendable_balanceis the spare andpooled_max_sendablethe spare less the fee; a reservation-only build seeded with the collateral has nothing to fund from; after an unlock largest-first spends the collateral.payments): 1 DASH to a contact spends only the spare (checked on the broadcast transaction); 6 DASH is refused.asset_lock::build): a 6 DASH lock is refused; a drain locks only the spare.update_service): the ProUpServTx fee comes from the spare; with the spare locked too, the build is refused.core_bridge): a registration's lock rides the wallet's next changeset once, a lock-only changeset still reaches the persister, and a registration seen again queues nothing.wallet::outpoint_locks): unlock and lock are persisted, the queued lock is dropped by the unlock, the UI balance follows, the unlocked collateral funds a send and the relocked one does not.load_from_persistorlocks a tracked masternode's collateral (registration never processed by the wallet), keeps it out of the pooled balance, and queues it for persistence.tests/sqlite_locked_outpoints.rs): a locked collateral comes back in the locked balance after reopen and out ofget_spendable_utxos; a lock stored before its coin and a coin arriving after the reload both come back locked; an unlock is not restored; a lock-then-unlock fold keeps the unlock; locks go with their wallet; the store attestsOUTPOINT_LOCKS.OUTPOINT_LOCKSneeds the whole trio declared; lock, list and unlock through the entry points; extension layout and capability pins updated.Run locally: the 16 new platform-wallet tests and the whole
cargo test -p platform-wallet --lib(1168 passed);cargo test -p platform-wallet-storage(984 passed, includingsqlite_locked_outpoints,sqlite_schema_pinning,every_table_in_the_schema_is_accounted_forand the capability test; thepreparecheck's allow-list gains the read-only locked-outpoint load query); the capability bit tests inpersistence_capabilities.rs; rs-platform-wallet-ffi builds with its tests, andcargo test -p platform-wallet-ffi --libpasses (388, the 4 new lock tests included);cargo clippy --tests -- -D warningson platform-wallet, platform-wallet-ffi and platform-wallet-storage;cargo fmt;cargo metadata --lockedon the repinned lockfile. The V019 fingerprints insqlite_schema_pinningwere refreshed from that test's own output. The rust-dashcore backport (0c0dca374e):cargo test -p key-wallet(764 passed) and-p key-wallet-manager(84 passed),cargo fmt --all --check,cargo clippy -p key-wallet -p key-wallet-manager --all-features --all-targets -- -D warnings.Not yet run locally: the Swift SDK build and tests. CI is the first compile of the Swift changes.
Breaking Changes
CoreChangeSetgainsoutpoint_locks, andPlatformWalletInfoa crate-private field.PersistenceCallbacksExtensionandPersistenceExtensionCallbacksgain three fields (additive and size-negotiated for C hosts).TransactionCheckResultgainslocked_outpoints, key-wallet-manager'sCheckTransactionsResultgainsper_wallet_locked_outpoints, and a coin a ProRegTx names as collateral is no longer spendable, once the ProRegTx is in a block, until unlocked.Checklist:
structure.rs, regeneratedgrovedb-structure.json, and checked the structure viewer link posted on this pull requestFor repository code-owners and collaborators only
🤖 Generated with Claude Code