-
Notifications
You must be signed in to change notification settings - Fork 59
fix(platform-wallet): scan DashPay contact accounts from request height #4740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v4.2-dev
Are you sure you want to change the base?
Changes from all commits
16afd79
f93aa3f
5adbe62
881dbd0
40e04c2
d320b35
1363aa0
2b12cfb
6e168f7
a9dbddb
f1250ab
0b08878
e1c6c89
df13421
19ac6fa
9e03d41
239acb0
6e78eb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ use dpp::identity::Identity; | |
| use dpp::prelude::Identifier; | ||
| use key_wallet::account::AccountType; | ||
| use key_wallet::managed_account::managed_account_trait::ManagedAccountTrait; | ||
| use key_wallet::wallet::managed_wallet_info::managed_account_operations::ManagedAccountOperations; | ||
| use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; | ||
|
|
||
| use super::*; | ||
| use crate::broadcaster::TransactionBroadcaster; | ||
|
|
@@ -14,6 +16,60 @@ use crate::wallet::identity::types::dashpay::established_contact::EstablishedCon | |
| use crate::wallet::identity::types::dashpay::payment::DashpayAddressMatch; | ||
| use crate::wallet::platform_wallet::PlatformWalletInfo; | ||
|
|
||
| /// Return the last certified Core height to keep when adding a contact account. | ||
| /// DIP-15 records height `H` so recovery resumes at `H + 1`; locally rotated | ||
| /// relationships fall back to wallet birth because their original `H` is gone. | ||
| pub(super) fn contact_scan_checkpoint( | ||
| info: &crate::wallet::PlatformWalletInfo, | ||
| owner: &Identifier, | ||
| contact: &Identifier, | ||
| ) -> u32 { | ||
| let birth_checkpoint = info.core_wallet.birth_height().saturating_sub(1); | ||
| let Some(managed) = info.identity_manager.managed_identity(owner) else { | ||
| return birth_checkpoint; | ||
| }; | ||
| let dashpay = managed.dashpay(); | ||
|
|
||
| let mut requests = Vec::with_capacity(2); | ||
| if let Some(established) = dashpay.established_contacts().get(contact) { | ||
| requests.push(&established.outgoing_request); | ||
| requests.push(&established.incoming_request); | ||
| } | ||
| requests.extend(dashpay.sent_contact_requests().get(contact)); | ||
| requests.extend(dashpay.incoming_contact_requests().get(contact)); | ||
| let request_checkpoint = (!requests.is_empty() | ||
| && requests | ||
| .iter() | ||
| .all(|request| request.account_reference >> 28 == 0)) | ||
| .then(|| { | ||
| requests | ||
| .iter() | ||
| .map(|request| request.core_height_created_at) | ||
| .min() | ||
| .unwrap_or(0) | ||
| }); | ||
|
|
||
| request_checkpoint | ||
| .unwrap_or(birth_checkpoint) | ||
| .max(birth_checkpoint) | ||
| } | ||
|
Comment on lines
+40
to
+55
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: Validate the contact-request height before using it as a scan checkpoint The checkpoint uses the minimum source:
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resolved (re-reviewed at |
||
|
|
||
| fn add_managed_contact_account( | ||
| info: &mut crate::wallet::PlatformWalletInfo, | ||
| wallet: &key_wallet::Wallet, | ||
| account_type: AccountType, | ||
| scan_checkpoint: u32, | ||
| ) -> key_wallet::Result<()> { | ||
| let previous_checkpoint = info.core_wallet.synced_height(); | ||
| // Upstream adds the account, bumps the scanner generation, and rewinds to | ||
| // wallet birth. Under this same manager write lock, restore only the range | ||
| // certified for the new account while preserving any deeper pending scan. | ||
| info.add_managed_account(wallet, account_type)?; | ||
| info.core_wallet | ||
| .update_synced_height(previous_checkpoint.min(scan_checkpoint)); | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Build the persistence round for a newly registered DashPay account | ||
| /// (`DashpayReceivingFunds` / `DashpayExternalAccount`): the | ||
| /// [`AccountRegistrationEntry`] plus the account's initial address-pool | ||
|
|
@@ -203,9 +259,9 @@ impl<B: TransactionBroadcaster + ?Sized> DashPayView<'_, B> { | |
| is_watch_only: false, | ||
| }; | ||
|
|
||
| // DashPay accounts are funds-bearing; use the typed | ||
| // `insert_funds_bearing_account` API exposed by the post-split | ||
| // collection rather than wrapping in `OwnedManagedCoreAccount`. | ||
| // Build the initial funds-bearing state for persistence. The live | ||
| // insertion below goes through `ManagedAccountOperations` so upstream | ||
| // also invalidates the wallet's prior filter-scan generation. | ||
| let managed = key_wallet::managed_account::ManagedCoreFundsAccount::from_account(&account); | ||
|
|
||
| // Persist the registration BEFORE the in-memory inserts: a store | ||
|
|
@@ -227,6 +283,7 @@ impl<B: TransactionBroadcaster + ?Sized> DashPayView<'_, B> { | |
| let (wallet, info) = wm | ||
| .get_wallet_mut_and_info_mut(&self.wallet_id) | ||
| .ok_or_else(|| PlatformWalletError::WalletNotFound(hex::encode(self.wallet_id)))?; | ||
| let scan_checkpoint = contact_scan_checkpoint(info, our_identity_id, contact_identity_id); | ||
|
|
||
| // Mirror the restored shape: the immutable `wallet.accounts` | ||
| // collection holds the Account (like `build_wallet_start_state` | ||
|
|
@@ -239,14 +296,22 @@ impl<B: TransactionBroadcaster + ?Sized> DashPayView<'_, B> { | |
| "Failed to add contact account to wallet: {e}" | ||
| )) | ||
| })?; | ||
| info.core_wallet | ||
| .accounts | ||
| .insert_funds_bearing_account(managed) | ||
| .map_err(|e| { | ||
| PlatformWalletError::InvalidIdentityData(format!( | ||
| "Failed to register contact account: {e}" | ||
| )) | ||
| })?; | ||
| add_managed_contact_account(info, wallet, account_type, scan_checkpoint).map_err(|e| { | ||
| PlatformWalletError::InvalidIdentityData(format!( | ||
| "Failed to register contact account: {e}" | ||
| )) | ||
| })?; | ||
| if let Some(managed) = info.identity_manager.managed_identity_mut(our_identity_id) { | ||
| if managed | ||
| .dashpay() | ||
| .established_contacts() | ||
| .contains_key(contact_identity_id) | ||
| { | ||
| managed | ||
| .dashpay_rescan_triggered_mut() | ||
| .insert(*contact_identity_id); | ||
| } | ||
| } | ||
|
|
||
| tracing::info!( | ||
| our_identity = %our_identity_id, | ||
|
|
@@ -534,8 +599,9 @@ impl<B: TransactionBroadcaster + ?Sized> DashPayView<'_, B> { | |
| is_watch_only: true, | ||
| }; | ||
|
|
||
| // DashpayExternalAccount is funds-bearing; insert via the | ||
| // typed `insert_funds` API after the upstream split. | ||
| // Build the initial funds-bearing state for persistence. The live | ||
| // insertion below goes through `ManagedAccountOperations` so upstream | ||
| // also invalidates the wallet's prior filter-scan generation. | ||
| let managed = key_wallet::managed_account::ManagedCoreFundsAccount::from_account(&account); | ||
|
|
||
| // Persist the registration BEFORE the in-memory inserts (same | ||
|
|
@@ -562,6 +628,7 @@ impl<B: TransactionBroadcaster + ?Sized> DashPayView<'_, B> { | |
| self.wallet_id, | ||
| ))) | ||
| })?; | ||
| let scan_checkpoint = contact_scan_checkpoint(info, our_identity_id, &contact_identity_id); | ||
|
|
||
| // (a) Insert Account into the immutable wallet account collection so the | ||
| // xpub is accessible by `send_payment`. | ||
|
|
@@ -574,16 +641,13 @@ impl<B: TransactionBroadcaster + ?Sized> DashPayView<'_, B> { | |
| ))) | ||
| })?; | ||
|
|
||
| // (b) Insert ManagedCoreFundsAccount for address-pool tracking. | ||
| info.core_wallet | ||
| .accounts | ||
| .insert_funds_bearing_account(managed) | ||
| .map_err(|e| { | ||
| Transient(PlatformWalletError::InvalidIdentityData(format!( | ||
| "Failed to register external contact account: {}", | ||
| e | ||
| ))) | ||
| })?; | ||
| // (b) Insert the managed account and invalidate prior filter coverage. | ||
| add_managed_contact_account(info, wallet, account_type, scan_checkpoint).map_err(|e| { | ||
| Transient(PlatformWalletError::InvalidIdentityData(format!( | ||
| "Failed to register external contact account: {}", | ||
| e | ||
| ))) | ||
| })?; | ||
|
|
||
| tracing::info!( | ||
| our_identity = %our_identity_id, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: Name the DIP-15 rotation-bit shift instead of inlining
>> 28The
request.account_reference >> 28 == 0test re-derives the DIP-15 layout (low 28 bits masked index, top 4 bits rotation version) whose canonical owner isrs-platform-encryption(calculate_account_reference/unmask_account_referenceinaccount_reference.rs). The same literal shape appears as1 << 28in the payments.rs tests, so a future layout change must be found by text search across three sites. Behavior is correct today and the fallback (rewind to wallet birth) is the safe direction, so this is maintainability only: a shared named constant or a secret-free version-bit accessor in the owning crate would keep the single source of truth.source:
muse-spark-1.3-contributor(phase2-reviewer: general, architecture-layering, rust-quality)