Skip to content

Commit 92aa0ba

Browse files
committed
feat(key-wallet): seed sync checkpoint from birth_height
Move sync checkpoint seeding into `ManagedWalletInfo` construction so it can't be forgotten, and let callers control the scan range when importing. - `WalletInfoInterface::from_wallet` now takes `birth_height` and seeds `synced_height` and `last_processed_height` to `birth_height.saturating_sub(1)`. Previously every wallet-add path had to remember to call `set_birth_height` separately, and forgetting it dragged `WalletManager::synced_height` (a min across wallets) back to genesis on every add. - `WalletManager::import_wallet_from_extended_priv_key`, `import_wallet_from_xpub`, and `import_wallet_from_bytes` (plus the matching FFI) gain a `birth_height` parameter so previously-used keys can rescan from a chosen height instead of being silently anchored at the chain tip. - Unused `ManagedWalletInfo::with_birth_height` removed.
1 parent 677a67c commit 92aa0ba

8 files changed

Lines changed: 33 additions & 11 deletions

File tree

dash-spv-ffi/tests/test_wallet_manager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ mod tests {
9494
wallet_manager_ptr,
9595
serialized_wallet.as_ptr(),
9696
serialized_wallet.len(),
97+
0,
9798
imported_wallet_id.as_mut_ptr(),
9899
&mut error as *mut FFIError,
99100
);

key-wallet-ffi/FFI_API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ Get wallet IDs # Safety - `manager` must be a valid pointer to an FFIWalletMan
679679
#### `wallet_manager_import_wallet_from_bytes`
680680

681681
```c
682-
wallet_manager_import_wallet_from_bytes(manager: *mut FFIWalletManager, wallet_bytes: *const u8, wallet_bytes_len: usize, wallet_id_out: *mut u8, error: *mut FFIError,) -> bool
682+
wallet_manager_import_wallet_from_bytes(manager: *mut FFIWalletManager, wallet_bytes: *const u8, wallet_bytes_len: usize, birth_height: u32, wallet_id_out: *mut u8, error: *mut FFIError,) -> bool
683683
```
684684

685685
**Module:** `wallet_manager`

key-wallet-ffi/src/wallet_manager.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ pub unsafe extern "C" fn wallet_manager_free_wallet_bytes(wallet_bytes: *mut u8,
295295
/// - `manager` must be a valid pointer to an FFIWalletManager instance
296296
/// - `wallet_bytes` must be a valid pointer to bincode-serialized wallet bytes
297297
/// - `wallet_bytes_len` must be the exact length of the wallet bytes
298+
/// - `birth_height` is the birth height to seed the wallet's sync checkpoint
299+
/// (0 to rescan from genesis)
298300
/// - `wallet_id_out` must be a valid pointer to a 32-byte array that will receive the wallet ID
299301
/// - `error` must be a valid pointer to an FFIError structure
300302
/// - The caller must ensure all pointers remain valid for the duration of this call
@@ -304,6 +306,7 @@ pub unsafe extern "C" fn wallet_manager_import_wallet_from_bytes(
304306
manager: *mut FFIWalletManager,
305307
wallet_bytes: *const u8,
306308
wallet_bytes_len: usize,
309+
birth_height: u32,
307310
wallet_id_out: *mut u8,
308311
error: *mut FFIError,
309312
) -> bool {
@@ -316,7 +319,7 @@ pub unsafe extern "C" fn wallet_manager_import_wallet_from_bytes(
316319
// Import the wallet using async runtime
317320
let result = manager_ref.runtime.block_on(async {
318321
let mut manager_guard = manager_ref.manager.write().await;
319-
manager_guard.import_wallet_from_bytes(wallet_bytes_slice)
322+
manager_guard.import_wallet_from_bytes(wallet_bytes_slice, birth_height)
320323
});
321324

322325
let wallet_id = unwrap_or_return!(result, error);

key-wallet-ffi/src/wallet_manager_serialization_tests.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ mod tests {
252252
manager2,
253253
wallet_bytes_out,
254254
wallet_bytes_len_out,
255+
0,
255256
imported_wallet_id.as_mut_ptr(),
256257
error,
257258
)

key-wallet-ffi/src/wallet_manager_tests.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,7 @@ mod tests {
949949
manager3,
950950
wallet_bytes_slice.as_ptr(),
951951
wallet_bytes_slice.len(),
952+
0,
952953
import_wallet_id_out.as_mut_ptr(),
953954
error,
954955
)
@@ -1109,6 +1110,7 @@ mod tests {
11091110
manager2,
11101111
wallet_bytes_copy.as_ptr(),
11111112
wallet_bytes_copy.len(),
1113+
0,
11121114
import_wallet_id_out.as_mut_ptr(),
11131115
error,
11141116
)

key-wallet-ffi/tests/test_import_wallet.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ mod tests {
6060
manager2,
6161
ptr::null(),
6262
0,
63+
0,
6364
imported_wallet_id.as_mut_ptr(),
6465
&mut error,
6566
);

key-wallet-manager/src/lib.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
322322
///
323323
/// # Arguments
324324
/// * `xprv` - The extended private key string (base58check encoded)
325+
/// * `birth_height` - Birth height for wallet scanning (0 to sync from genesis)
325326
/// * `account_creation_options` - Specifies which accounts to create during initialization
326327
///
327328
/// # Returns
@@ -330,6 +331,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
330331
pub fn import_wallet_from_extended_priv_key(
331332
&mut self,
332333
xprv: &str,
334+
birth_height: CoreBlockHeight,
333335
account_creation_options: key_wallet::wallet::initialization::WalletAccountCreationOptions,
334336
) -> Result<WalletId, WalletError> {
335337
// Parse the extended private key
@@ -349,7 +351,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
349351
}
350352

351353
// Create managed wallet info
352-
let mut managed_info = T::from_wallet(&wallet, self.last_processed_height());
354+
let mut managed_info = T::from_wallet(&wallet, birth_height);
353355
managed_info.set_first_loaded_at(current_timestamp());
354356

355357
self.wallets.insert(wallet_id, wallet);
@@ -365,6 +367,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
365367
///
366368
/// # Arguments
367369
/// * `xpub` - The extended public key string (base58check encoded)
370+
/// * `birth_height` - Birth height for wallet scanning (0 to sync from genesis)
368371
/// * `can_sign_externally` - If true, creates an externally signable wallet (e.g., for hardware wallets).
369372
/// If false, creates a pure watch-only wallet.
370373
///
@@ -374,6 +377,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
374377
pub fn import_wallet_from_xpub(
375378
&mut self,
376379
xpub: &str,
380+
birth_height: CoreBlockHeight,
377381
can_sign_externally: bool,
378382
) -> Result<WalletId, WalletError> {
379383
// Parse the extended public key
@@ -396,7 +400,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
396400
}
397401

398402
// Create managed wallet info
399-
let mut managed_info = T::from_wallet(&wallet, self.last_processed_height());
403+
let mut managed_info = T::from_wallet(&wallet, birth_height);
400404
managed_info.set_first_loaded_at(current_timestamp());
401405

402406
self.wallets.insert(wallet_id, wallet);
@@ -411,8 +415,14 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
411415
/// This is useful for restoring wallets from backups or transferring wallets
412416
/// between systems.
413417
///
418+
/// The serialized form contains only the immutable `Wallet`, not the mutable
419+
/// `ManagedWalletInfo`, so the caller must supply a `birth_height` to seed the
420+
/// new wallet's sync checkpoint. Pass 0 to rescan from genesis, or the chain
421+
/// tip if no historical scan is desired.
422+
///
414423
/// # Arguments
415424
/// * `wallet_bytes` - The bincode-serialized wallet bytes
425+
/// * `birth_height` - Birth height for wallet scanning (0 to sync from genesis)
416426
///
417427
/// # Returns
418428
/// * `Ok(WalletId)` - The computed wallet ID of the imported wallet
@@ -421,6 +431,7 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
421431
pub fn import_wallet_from_bytes(
422432
&mut self,
423433
wallet_bytes: &[u8],
434+
birth_height: CoreBlockHeight,
424435
) -> Result<WalletId, WalletError> {
425436
// Deserialize the wallet from bincode
426437
let wallet: Wallet = bincode::decode_from_slice(wallet_bytes, bincode::config::standard())
@@ -437,10 +448,8 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
437448
return Err(WalletError::WalletExists(wallet_id));
438449
}
439450

440-
// Create managed wallet info from the imported wallet, using the manager's
441-
// current aggregated last-processed height as the fallback birth height
442-
// since the serialized form does not preserve it.
443-
let mut managed_info = T::from_wallet(&wallet, self.last_processed_height());
451+
// Create managed wallet info from the imported wallet
452+
let mut managed_info = T::from_wallet(&wallet, birth_height);
444453
managed_info.set_first_loaded_at(current_timestamp());
445454

446455
self.wallets.insert(wallet_id, wallet);

key-wallet-manager/tests/test_serialized_wallets.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,16 @@ mod tests {
6767
assert_eq!(wallet_id, wallet_id3);
6868
println!("Externally signable wallet ID: {}", hex::encode(wallet_id3));
6969

70-
// Test 4: Import the serialized wallet back
70+
// Test 4: Import the serialized wallet back with a specific birth height
7171
let mut manager4 = WalletManager::<ManagedWalletInfo>::new(Network::Testnet);
72-
let import_result = manager4.import_wallet_from_bytes(&bytes);
72+
let import_result = manager4.import_wallet_from_bytes(&bytes, 50_000);
7373
assert!(import_result.is_ok());
74-
assert_eq!(import_result.unwrap(), wallet_id);
74+
let imported_id = import_result.unwrap();
75+
assert_eq!(imported_id, wallet_id);
76+
let imported = manager4.get_wallet_info(&imported_id).unwrap();
77+
assert_eq!(imported.birth_height(), 50_000);
78+
assert_eq!(imported.synced_height(), 49_999);
79+
assert_eq!(imported.last_processed_height(), 49_999);
7580
}
7681

7782
#[test]

0 commit comments

Comments
 (0)