Repository navigation
fix(platform-wallet)!: keep a contact's payment account across relaunches and replace it cleanly on rotation - #5587
Draft
romchornyi wants to merge 9 commits into
Draft
romchornyi wants to merge 9 commits into
romchornyi wants to merge 9 commits into
Conversation
… finds the same xpub Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t was built from Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ool and keep its request digest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
… its request digest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
…s request digest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
…ts request digest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
… live request confirms Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
…is queued for Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
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: true
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 |
5 of 8 tasks
Collaborator
|
🕓 Review not started yet because this PR is a draft.
Commit e091889. Normal review starts when eligible; priority review starts as soon as a slot is available. |
The FFI slots, Swift and Kotlin storage of the pool reset and request digest land in their own PRs on top of this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WYhtAUUCkvoBmp19LA8Z31
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
What this does: To pay a DashPay contact, the wallet derives addresses from an "outbound account" built from the key the contact sent us. The wallet now keeps that account across restarts when the contact's key is unchanged, instead of rebuilding it from scratch on every launch. When the contact sends a new key, the account is replaced cleanly. The account row records which request it was built from, so a restart still knows whether the account matches the contact's current key. This PR holds the rule, in Rust, and the SQLite backend; the iOS and Android storage follow in the stacked PRs below.
Value: Contact payments survive a relaunch without losing the record of which addresses were already used. On SQLite, a contact's new key is actually saved; before, the new public keys were rejected and the rotation never persisted. A payment never goes to addresses the contact stopped watching, even across a restart.
Risks: Medium.
Stack (review and merge in order):
Issue being fixed or feature implemented
Follow-up to #5548 / #5549, found while reviewing them.
external_account_referencemarker. So every launch treated every outbound account as rotated, tore it down and rebuilt it from an empty pool. That undid fix(platform-wallet-storage): restore DashPay contact address pools on SQLite load #5549's pool restore in memory and clearedreserved_aton disk.(pool_type, index)slots collide with the old ones.apply_poolsrejected the new public keys (TypedPoolKeyConflict), and old rows above the new window keptused = MAX.What was done?
platform-wallet
register_external_contact_accountkeeps a registered account whose xpub (key and chain code) and pools match the decrypted payload, and replaces it otherwise. It does the replacement underlock_contact_payments, so a payment's deferred pool store cannot land after the reset.AccountRegistrationEntry::encrypted_xpub_digestis the SHA-256 of the request'sencrypted_public_keythe xpub was decrypted from. It is written in the same round as the xpub, on keep and on replace.PlatformWalletChangeSet::account_pool_resetsdrops an account's stored pool before the round's snapshots. It is emitted only when an already registered account is replaced. On merge, a later reset drops the earlier rounds' snapshots of that account.apply_outbound_account_digests(manager/load.rs):reserve_contact_payment_addressrefuses exactly the registered accounts thatoutbound_account_is_currentrejects: a pending rotation, or a marker not naming the live request. Those are the accounts the payment's own drain is queued to rebuild (queue_outbound_rebuildinsend_payment/reserve_payment_address). A broken channel gets its own message.platform-wallet-storage (SQLite)
account_registrations.encrypted_xpub_digest(CHECKlength 32).AccountRegistrationWirekeeps the published blob layout, so old rows decode unchanged.core_pool::reset_poolsdeletes by the full account key, inside the round's transaction.ClientWalletStartState::outbound_account_digests.platform-wallet-ffi: only fills the new struct fields. The host slots come in #5589.
Follow-ups, not in this stack:
isUsedmonotonicity on Swift/Kotlin is tracked in DashPay contact address pool: a used address can be stored back as available (ungated event adapter, verbatim isUsed) #5563.How Has This Been Tested?
cargo test --all-featuresforplatform-wallet(1608),platform-wallet-storage(432 lib + 556 integration),platform-wallet-ffi(474) andrs-unified-sdk-jni(52).cargo clippy --all-features --tests -D warningsis clean. New tests:network/payments.rs:changeset.rs: merge rules foraccount_pool_resets.sqlite_outbound_account_replace.rs: the replacement stores and keeps only the new rows, the conflict without a reset, and digest load / overwrite / clear.The key tests were mutation-checked: removing the gate lock, the queue call sites, the digest branches or the SQLite reset each fails a test.
Breaking Changes
AccountRegistrationEntry::encrypted_xpub_digest,PlatformWalletChangeSet::account_pool_resetsandClientWalletStartState::outbound_account_digests. Struct literals outside this repository must add them.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