-
Notifications
You must be signed in to change notification settings - Fork 54
feat(platform-wallet): add birth_height_override to wallet creation API #3636
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: v3.1-dev
Are you sure you want to change the base?
Changes from all commits
b6e9dcb
3cf159f
59d49f9
e246dd3
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 |
|---|---|---|
|
|
@@ -101,7 +101,7 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_seed( | |
| }; | ||
|
|
||
| let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { | ||
| runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts)) | ||
| runtime().block_on(manager.create_wallet_from_seed_bytes(network, seed, accounts, None)) | ||
| }); | ||
| let result = unwrap_option_or_return!(option); | ||
| let wallet = unwrap_result_or_return!(result); | ||
|
|
@@ -139,7 +139,12 @@ pub unsafe extern "C" fn platform_wallet_manager_create_wallet_from_mnemonic( | |
| }; | ||
|
|
||
| let option = PLATFORM_WALLET_MANAGER_STORAGE.with_item(manager_handle, |manager| { | ||
| runtime().block_on(manager.create_wallet_from_mnemonic(mnemonic_str, network, accounts)) | ||
| runtime().block_on(manager.create_wallet_from_mnemonic( | ||
| mnemonic_str, | ||
| network, | ||
| accounts, | ||
| None, | ||
| )) | ||
|
Comment on lines
103
to
+147
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: FFI constructors hardcode Both exported constructors still pass source: ['codex'] 🤖 Fix this with AI agents |
||
| }); | ||
| let result = unwrap_option_or_return!(option); | ||
| let wallet = unwrap_result_or_return!(result); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -56,11 +56,23 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> { | |||||||||||||||||||||||||||||||||||||||||
| /// [`parse_mnemonic_any_language`]). For passphrase-only flows or | ||||||||||||||||||||||||||||||||||||||||||
| /// out-of-band seed material, derive the seed externally and use | ||||||||||||||||||||||||||||||||||||||||||
| /// [`Self::create_wallet_from_seed_bytes`]. | ||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||
| /// `birth_height_override` controls SPV's compact-filter scan | ||||||||||||||||||||||||||||||||||||||||||
| /// window for the new wallet. `None` (the default for fresh | ||||||||||||||||||||||||||||||||||||||||||
| /// wallets) seeds the birth height to SPV's current confirmed | ||||||||||||||||||||||||||||||||||||||||||
| /// header tip, so the scan window is `[H_now, ∞)` — anything | ||||||||||||||||||||||||||||||||||||||||||
| /// funded before init is invisible. `Some(0)` requests a full | ||||||||||||||||||||||||||||||||||||||||||
| /// historical scan from genesis (use sparingly — expensive on | ||||||||||||||||||||||||||||||||||||||||||
| /// long-lived chains, but required when an address may have | ||||||||||||||||||||||||||||||||||||||||||
| /// received funds before the wallet was first registered). | ||||||||||||||||||||||||||||||||||||||||||
| /// `Some(h)` pins the scan start to a specific block height, | ||||||||||||||||||||||||||||||||||||||||||
| /// useful when a known funding block is on record. | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
+69
Contributor
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. Align the
Also applies to: 91-96, 128-140 🤖 Prompt for AI Agents
Comment on lines
+60
to
+69
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: Public The rustdoc says 💡 Suggested change
Suggested change
source: ['codex'] 🤖 Fix this with AI agents |
||||||||||||||||||||||||||||||||||||||||||
| pub async fn create_wallet_from_mnemonic( | ||||||||||||||||||||||||||||||||||||||||||
| &self, | ||||||||||||||||||||||||||||||||||||||||||
| mnemonic_phrase: &str, | ||||||||||||||||||||||||||||||||||||||||||
| network: Network, | ||||||||||||||||||||||||||||||||||||||||||
| accounts: WalletAccountCreationOptions, | ||||||||||||||||||||||||||||||||||||||||||
| birth_height_override: Option<u32>, | ||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<Arc<PlatformWallet>, PlatformWalletError> { | ||||||||||||||||||||||||||||||||||||||||||
| let mnemonic = parse_mnemonic_any_language(mnemonic_phrase) | ||||||||||||||||||||||||||||||||||||||||||
| .map_err(|e| PlatformWalletError::WalletCreation(format!("Invalid mnemonic: {}", e)))?; | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -70,35 +82,64 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> { | |||||||||||||||||||||||||||||||||||||||||
| e | ||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||||||||||||||
| self.register_wallet(wallet).await | ||||||||||||||||||||||||||||||||||||||||||
| self.register_wallet(wallet, birth_height_override).await | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// Create a PlatformWallet from raw seed bytes, initialize persisted | ||||||||||||||||||||||||||||||||||||||||||
| /// state, register it with the manager and return an `Arc` handle. | ||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||
| /// See [`Self::create_wallet_from_mnemonic`] for the | ||||||||||||||||||||||||||||||||||||||||||
| /// `birth_height_override` semantics. `None` keeps the | ||||||||||||||||||||||||||||||||||||||||||
| /// pre-existing behaviour (scan from current SPV tip forward); | ||||||||||||||||||||||||||||||||||||||||||
| /// `Some(h)` is for callers that need to see funding deposited | ||||||||||||||||||||||||||||||||||||||||||
| /// before the wallet was registered (e.g. a long-lived bank | ||||||||||||||||||||||||||||||||||||||||||
| /// address pre-funded with testnet duffs). | ||||||||||||||||||||||||||||||||||||||||||
| pub async fn create_wallet_from_seed_bytes( | ||||||||||||||||||||||||||||||||||||||||||
| &self, | ||||||||||||||||||||||||||||||||||||||||||
| network: Network, | ||||||||||||||||||||||||||||||||||||||||||
| seed_bytes: [u8; 64], | ||||||||||||||||||||||||||||||||||||||||||
| accounts: WalletAccountCreationOptions, | ||||||||||||||||||||||||||||||||||||||||||
| birth_height_override: Option<u32>, | ||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<Arc<PlatformWallet>, PlatformWalletError> { | ||||||||||||||||||||||||||||||||||||||||||
| let wallet = Wallet::from_seed_bytes(seed_bytes, network, accounts).map_err(|e| { | ||||||||||||||||||||||||||||||||||||||||||
| PlatformWalletError::WalletCreation(format!( | ||||||||||||||||||||||||||||||||||||||||||
| "Failed to create wallet from seed bytes: {}", | ||||||||||||||||||||||||||||||||||||||||||
| e | ||||||||||||||||||||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||||||||||||||||||||
| self.register_wallet(wallet).await | ||||||||||||||||||||||||||||||||||||||||||
| self.register_wallet(wallet, birth_height_override).await | ||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
70
to
111
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. 💬 Nitpick: Breaking change to public Rust API not surfaced in PR title
source: ['claude'] |
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| /// Register a pre-built `Wallet` with the manager: insert into the | ||||||||||||||||||||||||||||||||||||||||||
| /// `WalletManager`, build a `PlatformWallet` handle, load persisted | ||||||||||||||||||||||||||||||||||||||||||
| /// state, and return an `Arc` to the managed wallet. | ||||||||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||||||||
| /// `birth_height_override` flows through to both the in-memory | ||||||||||||||||||||||||||||||||||||||||||
| /// `ManagedWalletInfo` sync checkpoint and the persisted | ||||||||||||||||||||||||||||||||||||||||||
| /// `WalletMetadataEntry` so the SPV scan window is consistent | ||||||||||||||||||||||||||||||||||||||||||
| /// across restarts. See [`Self::create_wallet_from_mnemonic`] for | ||||||||||||||||||||||||||||||||||||||||||
| /// the contract. | ||||||||||||||||||||||||||||||||||||||||||
| #[allow(clippy::type_complexity)] | ||||||||||||||||||||||||||||||||||||||||||
| async fn register_wallet( | ||||||||||||||||||||||||||||||||||||||||||
| &self, | ||||||||||||||||||||||||||||||||||||||||||
| wallet: Wallet, | ||||||||||||||||||||||||||||||||||||||||||
| birth_height_override: Option<u32>, | ||||||||||||||||||||||||||||||||||||||||||
| ) -> Result<Arc<PlatformWallet>, PlatformWalletError> { | ||||||||||||||||||||||||||||||||||||||||||
| let wallet_info = ManagedWalletInfo::from_wallet(&wallet, 0); | ||||||||||||||||||||||||||||||||||||||||||
| // Birth height resolution: explicit override wins; otherwise | ||||||||||||||||||||||||||||||||||||||||||
| // fall back to SPV's confirmed header tip (default for fresh | ||||||||||||||||||||||||||||||||||||||||||
| // wallets — they only need to see funding from now on); 0 if | ||||||||||||||||||||||||||||||||||||||||||
| // SPV isn't running yet. | ||||||||||||||||||||||||||||||||||||||||||
| let birth_height: u32 = match birth_height_override { | ||||||||||||||||||||||||||||||||||||||||||
| Some(h) => h, | ||||||||||||||||||||||||||||||||||||||||||
| None => self | ||||||||||||||||||||||||||||||||||||||||||
| .spv_manager | ||||||||||||||||||||||||||||||||||||||||||
| .sync_progress() | ||||||||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||||||
| .and_then(|p| p.headers().ok().map(|h| h.tip_height())) | ||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or(0), | ||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| let wallet_info = ManagedWalletInfo::from_wallet(&wallet, birth_height); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| let balance = Arc::new(WalletBalance::new()); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -192,17 +233,10 @@ impl<P: PlatformWalletPersistence + 'static> PlatformWalletManager<P> { | |||||||||||||||||||||||||||||||||||||||||
| // the persister is a best-effort channel, not a source of | ||||||||||||||||||||||||||||||||||||||||||
| // truth in steady state. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // Birth height = SPV's confirmed header tip if SPV is running, | ||||||||||||||||||||||||||||||||||||||||||
| // otherwise 0 (caller can bump it later when SPV catches up). | ||||||||||||||||||||||||||||||||||||||||||
| // 0 means "scan from genesis", which is safe-correct for | ||||||||||||||||||||||||||||||||||||||||||
| // fresh wallets. | ||||||||||||||||||||||||||||||||||||||||||
| let birth_height: u32 = self | ||||||||||||||||||||||||||||||||||||||||||
| .spv_manager | ||||||||||||||||||||||||||||||||||||||||||
| .sync_progress() | ||||||||||||||||||||||||||||||||||||||||||
| .await | ||||||||||||||||||||||||||||||||||||||||||
| .and_then(|p| p.headers().ok().map(|h| h.tip_height())) | ||||||||||||||||||||||||||||||||||||||||||
| .unwrap_or(0); | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| // `birth_height` was resolved at the top of `register_wallet` | ||||||||||||||||||||||||||||||||||||||||||
| // and seeded into `ManagedWalletInfo`; reuse it here so the | ||||||||||||||||||||||||||||||||||||||||||
| // persisted `WalletMetadataEntry` agrees with the in-memory | ||||||||||||||||||||||||||||||||||||||||||
| // sync checkpoint. | ||||||||||||||||||||||||||||||||||||||||||
| let mut registration_changeset = PlatformWalletChangeSet { | ||||||||||||||||||||||||||||||||||||||||||
| wallet_metadata: Some(WalletMetadataEntry { | ||||||||||||||||||||||||||||||||||||||||||
| network: self.sdk.network, | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
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: FFI hardcodes
None, regressing first-run pre-funded mnemonic imports for iOSBoth
platform_wallet_manager_create_wallet_from_seed(line 104) andplatform_wallet_manager_create_wallet_from_mnemonic(line 146) unconditionally passNoneforbirth_height_override. Before this PR,ManagedWalletInfowas hardcoded tobirth_height = 0at registration, so an FFI caller's first-process SPV scan started at genesis and would surface coins funded before the wallet was registered. After this PR,Noneresolves to the current SPV header tip whenever SPV is up, so the same flow silently misses pre-existing UTXOs. This is exactly the failure mode the PR description cites as the motivation for the new override (bank-wallet pre-funded with testnet duffs), yet the override is unreachable from the FFI/Swift surface — which is the primary consumer of this crate. The pre-PR behaviour was already broken across restart (persisted birth_height was SPV-tip), so this is a tradeoff not a pure regression, but it leaves the stated use case unaddressable from iOS. Add a sibling*_with_birth_heightFFI entry (or abirth_height_override: i64with sentinel) so iOS callers can opt intoSome(0)/Some(h). Not provided as asuggestionpatch because exposing it cleanly requires an additional FFI export, not a one-line edit.source: ['claude', 'codex']
🤖 Fix this with AI agents