diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index 75535f458..7d4675863 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -42,7 +42,7 @@ Functions: 3 ### Wallet Manager -Functions: 19 +Functions: 18 | Function | Description | Module | |----------|-------------|--------| @@ -63,12 +63,11 @@ Functions: 19 | `wallet_manager_get_wallet_ids` | Get wallet IDs # Safety - `manager` must be a valid pointer to an... | wallet_manager | | `wallet_manager_import_wallet_from_bytes` | No description | wallet_manager | | `wallet_manager_process_transaction` | Process a transaction through all wallets Checks a transaction against all... | wallet_manager | -| `wallet_manager_update_height` | Update block height for a network # Safety - `manager` must be a valid... | wallet_manager | | `wallet_manager_wallet_count` | Get wallet count # Safety - `manager` must be a valid pointer to an... | wallet_manager | ### Wallet Operations -Functions: 62 +Functions: 63 | Function | Description | Module | |----------|-------------|--------| @@ -99,6 +98,7 @@ Functions: 62 | `managed_wallet_info_free` | Free managed wallet info returned by wallet_manager_get_managed_wallet_info ... | managed_wallet | | `managed_wallet_mark_address_used` | Mark an address as used in the pool This updates the pool's tracking of... | address_pool | | `managed_wallet_set_gap_limit` | Set the gap limit for an address pool The gap limit determines how many... | address_pool | +| `managed_wallet_synced_height` | Get current synced height from wallet info # Safety - `managed_wallet`... | managed_wallet | | `wallet_add_account` | Add an account to the wallet without xpub # Safety This function... | wallet | | `wallet_add_account_with_string_xpub` | Add an account to the wallet with xpub as string # Safety This function... | wallet | | `wallet_add_account_with_xpub_bytes` | Add an account to the wallet with xpub as byte array # Safety This... | wallet | @@ -666,22 +666,6 @@ Process a transaction through all wallets Checks a transaction against all wall --- -#### `wallet_manager_update_height` - -```c -wallet_manager_update_height(manager: *mut FFIWalletManager, height: c_uint, error: *mut FFIError,) -> bool -``` - -**Description:** -Update block height for a network # Safety - `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - -**Safety:** -- `manager` must be a valid pointer to an FFIWalletManager - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call - -**Module:** `wallet_manager` - ---- - #### `wallet_manager_wallet_count` ```c @@ -1120,6 +1104,22 @@ Set the gap limit for an address pool The gap limit determines how many unused --- +#### `managed_wallet_synced_height` + +```c +managed_wallet_synced_height(managed_wallet: *const FFIManagedWalletInfo, error: *mut FFIError,) -> c_uint +``` + +**Description:** +Get current synced height from wallet info # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call + +**Safety:** +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure or null - The caller must ensure all pointers remain valid for the duration of this call + +**Module:** `managed_wallet` + +--- + #### `wallet_add_account` ```c diff --git a/key-wallet-ffi/include/key_wallet_ffi.h b/key-wallet-ffi/include/key_wallet_ffi.h index 6cdf65c6f..7da7c404c 100644 --- a/key-wallet-ffi/include/key_wallet_ffi.h +++ b/key-wallet-ffi/include/key_wallet_ffi.h @@ -3089,6 +3089,20 @@ bool managed_wallet_get_balance(const FFIManagedWalletInfo *managed_wallet, FFIError *error) ; +/* + Get current synced height from wallet info + + # Safety + + - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo + - `error` must be a valid pointer to an FFIError structure or null + - The caller must ensure all pointers remain valid for the duration of this call + */ + +unsigned int managed_wallet_synced_height(const FFIManagedWalletInfo *managed_wallet, + FFIError *error) +; + /* Free managed wallet info @@ -4087,21 +4101,6 @@ bool wallet_manager_process_transaction(FFIWalletManager *manager, FFIError *error) ; -/* - Update block height for a network - - # Safety - - - `manager` must be a valid pointer to an FFIWalletManager - - `error` must be a valid pointer to an FFIError structure or null - - The caller must ensure all pointers remain valid for the duration of this call - */ - -bool wallet_manager_update_height(FFIWalletManager *manager, - unsigned int height, - FFIError *error) -; - /* Get current height for a network diff --git a/key-wallet-ffi/src/managed_wallet.rs b/key-wallet-ffi/src/managed_wallet.rs index bde2027dd..f807349b5 100644 --- a/key-wallet-ffi/src/managed_wallet.rs +++ b/key-wallet-ffi/src/managed_wallet.rs @@ -5,12 +5,13 @@ //! use std::ffi::CString; -use std::os::raw::c_char; +use std::os::raw::{c_char, c_uint}; use std::ptr; use crate::error::{FFIError, FFIErrorCode}; use crate::types::FFIWallet; use key_wallet::managed_account::address_pool::KeySource; +use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use std::ffi::c_void; @@ -585,6 +586,31 @@ pub unsafe extern "C" fn managed_wallet_get_balance( true } +/// Get current synced height from wallet info +/// +/// # Safety +/// +/// - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo +/// - `error` must be a valid pointer to an FFIError structure or null +/// - The caller must ensure all pointers remain valid for the duration of this call +#[no_mangle] +pub unsafe extern "C" fn managed_wallet_synced_height( + managed_wallet: *const FFIManagedWalletInfo, + error: *mut FFIError, +) -> c_uint { + if managed_wallet.is_null() { + FFIError::set_error( + error, + FFIErrorCode::InvalidInput, + "Managed wallet is null".to_string(), + ); + return 0; + } + let managed_wallet = unsafe { &*managed_wallet }; + FFIError::set_success(error); + managed_wallet.inner().synced_height() +} + /// Free managed wallet info /// /// # Safety diff --git a/key-wallet-ffi/src/wallet_manager.rs b/key-wallet-ffi/src/wallet_manager.rs index d2d029860..6bdec2876 100644 --- a/key-wallet-ffi/src/wallet_manager.rs +++ b/key-wallet-ffi/src/wallet_manager.rs @@ -787,35 +787,6 @@ pub unsafe extern "C" fn wallet_manager_process_transaction( !relevant_wallets.is_empty() } -/// Update block height for a network -/// -/// # Safety -/// -/// - `manager` must be a valid pointer to an FFIWalletManager -/// - `error` must be a valid pointer to an FFIError structure or null -/// - The caller must ensure all pointers remain valid for the duration of this call -#[no_mangle] -pub unsafe extern "C" fn wallet_manager_update_height( - manager: *mut FFIWalletManager, - height: c_uint, - error: *mut FFIError, -) -> bool { - if manager.is_null() { - FFIError::set_error(error, FFIErrorCode::InvalidInput, "Manager is null".to_string()); - return false; - } - - let manager_ref = &*manager; - - manager_ref.runtime.block_on(async { - let mut manager_guard = manager_ref.manager.write().await; - manager_guard.update_height(height); - }); - - FFIError::set_success(error); - true -} - /// Get current height for a network /// /// # Safety diff --git a/key-wallet-ffi/src/wallet_manager_tests.rs b/key-wallet-ffi/src/wallet_manager_tests.rs index f0507e2ae..f11c69618 100644 --- a/key-wallet-ffi/src/wallet_manager_tests.rs +++ b/key-wallet-ffi/src/wallet_manager_tests.rs @@ -208,34 +208,6 @@ mod tests { } } - #[test] - fn test_height_management() { - let mut error = FFIError::success(); - let error = &mut error as *mut FFIError; - - let manager = wallet_manager::wallet_manager_create(FFINetwork::Testnet, error); - assert!(!manager.is_null()); - - // Get initial height - let height = unsafe { wallet_manager::wallet_manager_current_height(manager, error) }; - assert_eq!(height, 0); - - // Update height - let success = - unsafe { wallet_manager::wallet_manager_update_height(manager, 100000, error) }; - assert!(success); - - // Verify height was updated - let height = unsafe { wallet_manager::wallet_manager_current_height(manager, error) }; - assert_eq!(height, 100000); - - // Clean up - unsafe { - wallet_manager::wallet_manager_free(manager); - (*error).free_message(); - } - } - #[test] fn test_error_handling() { let mut error = FFIError::success(); @@ -469,7 +441,7 @@ mod tests { } #[test] - fn test_wallet_manager_height_operations() { + fn test_wallet_manager_current_height() { let mut error = FFIError::success(); let error = &mut error as *mut FFIError; @@ -477,12 +449,17 @@ mod tests { assert!(!manager.is_null()); // Get initial height - let _height = unsafe { wallet_manager::wallet_manager_current_height(manager, error) }; + let height = unsafe { wallet_manager::wallet_manager_current_height(manager, error) }; + assert_eq!(height, 0); // Update height let new_height = 12345; unsafe { - wallet_manager::wallet_manager_update_height(manager, new_height, error); + let manager_ref = &*manager; + manager_ref.runtime.block_on(async { + let mut manager_guard = manager_ref.manager.write().await; + manager_guard.update_height(new_height); + }); } // Get updated height diff --git a/key-wallet-manager/src/wallet_manager/mod.rs b/key-wallet-manager/src/wallet_manager/mod.rs index 46f3e2cbe..d52b8c368 100644 --- a/key-wallet-manager/src/wallet_manager/mod.rs +++ b/key-wallet-manager/src/wallet_manager/mod.rs @@ -935,9 +935,12 @@ impl WalletManager { self.current_height } - /// Update current block height for a specific network + /// Update current block height and propagate to all wallet infos pub fn update_height(&mut self, height: u32) { - self.current_height = height + self.current_height = height; + for info in self.wallet_infos.values_mut() { + info.update_synced_height(height); + } } /// Get monitored addresses for all wallets for a specific network diff --git a/key-wallet-manager/tests/integration_test.rs b/key-wallet-manager/tests/integration_test.rs index 99c5e04e9..4ede4e4fd 100644 --- a/key-wallet-manager/tests/integration_test.rs +++ b/key-wallet-manager/tests/integration_test.rs @@ -5,6 +5,7 @@ use key_wallet::wallet::initialization::WalletAccountCreationOptions; use key_wallet::wallet::managed_wallet_info::transaction_building::AccountTypePreference; +use key_wallet::wallet::managed_wallet_info::wallet_info_interface::WalletInfoInterface; use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo; use key_wallet::{mnemonic::Language, Mnemonic, Network}; use key_wallet_manager::wallet_manager::{WalletError, WalletManager}; @@ -155,8 +156,75 @@ fn test_balance_calculation() { fn test_block_height_tracking() { let mut manager = WalletManager::::new(Network::Testnet); + // Initial state assert_eq!(manager.current_height(), 0); + // Set height before adding wallets + manager.update_height(1000); + assert_eq!(manager.current_height(), 1000); + + let mnemonic1 = Mnemonic::generate(12, Language::English).unwrap(); + let wallet_id1 = manager + .create_wallet_from_mnemonic( + &mnemonic1.to_string(), + "", + 0, + WalletAccountCreationOptions::Default, + ) + .unwrap(); + + let mnemonic2 = Mnemonic::generate(12, Language::English).unwrap(); + let wallet_id2 = manager + .create_wallet_from_mnemonic( + &mnemonic2.to_string(), + "", + 0, + WalletAccountCreationOptions::Default, + ) + .unwrap(); + + assert_eq!(manager.wallet_count(), 2); + + // Verify both wallets have synced_height of 0 initially + for wallet_info in manager.get_all_wallet_infos().values() { + assert_eq!(wallet_info.synced_height(), 0); + } + + // Update height - should propagate to all wallets manager.update_height(12345); assert_eq!(manager.current_height(), 12345); + + // Verify all wallets got updated + let wallet_info1 = manager.get_wallet_info(&wallet_id1).unwrap(); + let wallet_info2 = manager.get_wallet_info(&wallet_id2).unwrap(); + assert_eq!(wallet_info1.synced_height(), 12345); + assert_eq!(wallet_info2.synced_height(), 12345); + + // Update again - verify subsequent updates work + manager.update_height(20000); + assert_eq!(manager.current_height(), 20000); + + for wallet_info in manager.get_all_wallet_infos().values() { + assert_eq!(wallet_info.synced_height(), 20000); + } + + // Update wallets individually to different heights + let wallet_info1 = manager.get_wallet_info_mut(&wallet_id1).unwrap(); + wallet_info1.update_synced_height(30000); + + let wallet_info2 = manager.get_wallet_info_mut(&wallet_id2).unwrap(); + wallet_info2.update_synced_height(25000); + + // Verify each wallet has its own synced_height + let wallet_info1 = manager.get_wallet_info(&wallet_id1).unwrap(); + let wallet_info2 = manager.get_wallet_info(&wallet_id2).unwrap(); + assert_eq!(wallet_info1.synced_height(), 30000); + assert_eq!(wallet_info2.synced_height(), 25000); + + // Manager update_height still syncs all wallets + manager.update_height(40000); + let wallet_info1 = manager.get_wallet_info(&wallet_id1).unwrap(); + let wallet_info2 = manager.get_wallet_info(&wallet_id2).unwrap(); + assert_eq!(wallet_info1.synced_height(), 40000); + assert_eq!(wallet_info2.synced_height(), 40000); } diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index 52ef3f148..8ce9d6b23 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -827,7 +827,7 @@ mod tests { // Now advance the chain height past maturity (100 blocks) let mature_height = block_height + 100; - managed_wallet.update_chain_height(mature_height); + managed_wallet.update_synced_height(mature_height); // Verify transaction moved from immature to regular let managed_account = diff --git a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs index baee130b2..556891168 100644 --- a/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs +++ b/key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs @@ -61,6 +61,9 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount /// Update last synced timestamp fn update_last_synced(&mut self, timestamp: u64); + /// Get the synced height + fn synced_height(&self) -> CoreBlockHeight; + /// Get all monitored addresses fn monitored_addresses(&self) -> Vec; @@ -111,7 +114,7 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount /// Update chain state and process any matured transactions /// This should be called when the chain tip advances to a new height - fn update_chain_height(&mut self, current_height: u32); + fn update_synced_height(&mut self, current_height: u32); } /// Default implementation for ManagedWalletInfo @@ -156,6 +159,10 @@ impl WalletInfoInterface for ManagedWalletInfo { self.metadata.birth_height = height; } + fn synced_height(&self) -> CoreBlockHeight { + self.metadata.synced_height + } + fn first_loaded_at(&self) -> u64 { self.metadata.first_loaded_at } @@ -322,7 +329,9 @@ impl WalletInfoInterface for ManagedWalletInfo { ) } - fn update_chain_height(&mut self, current_height: u32) { + fn update_synced_height(&mut self, current_height: u32) { + self.metadata.synced_height = current_height; + let matured = self.process_matured_transactions(current_height); if !matured.is_empty() { diff --git a/key-wallet/src/wallet/metadata.rs b/key-wallet/src/wallet/metadata.rs index 937f836f8..5ecd34371 100644 --- a/key-wallet/src/wallet/metadata.rs +++ b/key-wallet/src/wallet/metadata.rs @@ -16,6 +16,8 @@ pub struct WalletMetadata { pub first_loaded_at: u64, /// Birth height (when wallet was created/restored) - 0 (genesis) if unknown pub birth_height: CoreBlockHeight, + /// Synced to block height + pub synced_height: CoreBlockHeight, /// Last sync timestamp pub last_synced: Option, /// Total transactions