diff --git a/dash-spv/src/sync/blocks/manager.rs b/dash-spv/src/sync/blocks/manager.rs index 8ea4e2f59..45ffed61b 100644 --- a/dash-spv/src/sync/blocks/manager.rs +++ b/dash-spv/src/sync/blocks/manager.rs @@ -48,10 +48,10 @@ impl BlocksManager>, block_storage: Arc>, ) -> Self { - let synced_height = wallet.read().await.synced_height(); + let last_processed_height = wallet.read().await.last_processed_height(); let mut initial_progress = BlocksProgress::default(); - initial_progress.update_last_processed(synced_height); + initial_progress.update_last_processed(last_processed_height); Self { progress: initial_progress, diff --git a/dash-spv/src/sync/filters/manager.rs b/dash-spv/src/sync/filters/manager.rs index 15dcbcee3..d800d0f95 100644 --- a/dash-spv/src/sync/filters/manager.rs +++ b/dash-spv/src/sync/filters/manager.rs @@ -83,7 +83,7 @@ impl>, filter_storage: Arc>, ) -> Self { - let committed_height = wallet.read().await.filter_committed_height(); + let committed_height = wallet.read().await.synced_height(); let stored_height = filter_storage.read().await.filter_tip_height().await.unwrap_or(0); let target_height = header_storage.read().await.get_tip().await.map(|t| t.height()).unwrap_or(0); @@ -158,11 +158,11 @@ impl SyncResult> { debug_assert!(self.is_idle(), "manager should have no in-flight state on start"); - // Use filter_committed_height for restart recovery instead of - // synced_height, which advances per-block and may exceed committed scan progress. + // Use synced_height for restart recovery instead of + // last_processed_height, which advances per-block and may exceed committed scan progress. let (wallet_birth_height, wallet_committed_height) = { let wallet = self.wallet.read().await; - (wallet.earliest_required_height().await, wallet.filter_committed_height()) + (wallet.earliest_required_height().await, wallet.synced_height()) }; // Get stored filters tip @@ -495,7 +495,7 @@ impl self.progress.committed_height() { self.progress.update_committed_height(end); - self.wallet.write().await.update_filter_committed_height(end); + self.wallet.write().await.update_synced_height(end); } self.processing_height = end + 1; @@ -814,9 +814,9 @@ mod tests { async fn test_filters_manager_new_restores_from_storage() { let storage = DiskStorageManager::with_temp_dir().await.unwrap(); - // Set wallet committed height via synced_height (MockWallet default delegates) + // Set wallet committed height via last_processed_height (MockWallet default delegates) let mut wallet = MockWallet::new(); - wallet.update_synced_height(50); + wallet.update_last_processed_height(50); let wallet = Arc::new(RwLock::new(wallet)); // Pre-populate filter storage with filters at heights 1..=100 @@ -1044,7 +1044,7 @@ mod tests { assert_eq!(manager.state(), SyncState::WaitForEvents); // Wallet committed to height 100, so scan_start will be 101 - manager.wallet.write().await.update_synced_height(100); + manager.wallet.write().await.update_last_processed_height(100); // Filter headers only reached 50, so its below scan_start manager.progress.update_filter_header_tip_height(50); // Chain tip higher so the Synced early-return is not taken @@ -1121,7 +1121,7 @@ mod tests { // Simulate restart where everything is already synced but state is WaitForEvents. // committed == stored == filter_header_tip — start_download detects synced state. manager.set_state(SyncState::WaitForEvents); - manager.wallet.write().await.update_synced_height(100); + manager.wallet.write().await.update_last_processed_height(100); manager.progress.update_committed_height(100); manager.progress.update_stored_height(100); manager.progress.update_filter_header_tip_height(100); diff --git a/key-wallet-ffi/FFI_API.md b/key-wallet-ffi/FFI_API.md index fd57ee77e..934797af0 100644 --- a/key-wallet-ffi/FFI_API.md +++ b/key-wallet-ffi/FFI_API.md @@ -99,9 +99,9 @@ Functions: 64 | `managed_wallet_get_top_up_account_with_registration_index` | Get a managed IdentityTopUp account with a specific registration index This... | managed_account | | `managed_wallet_get_utxos` | Get all UTXOs from managed wallet info # Safety - `managed_info` must be a... | utxo | | `managed_wallet_info_free` | Free managed wallet info returned by wallet_manager_get_managed_wallet_info ... | managed_wallet | +| `managed_wallet_last_processed_height` | Get current last processed height from wallet info # Safety -... | 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 | @@ -1140,51 +1140,51 @@ Free managed wallet info returned by wallet_manager_get_managed_wallet_info # S --- -#### `managed_wallet_mark_address_used` +#### `managed_wallet_last_processed_height` ```c -managed_wallet_mark_address_used(managed_wallet: *mut FFIManagedWalletInfo, address: *const c_char, error: *mut FFIError,) -> bool +managed_wallet_last_processed_height(managed_wallet: *const FFIManagedWalletInfo, error: *mut FFIError,) -> c_uint ``` **Description:** -Mark an address as used in the pool This updates the pool's tracking of which addresses have been used, which is important for gap limit management and wallet recovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError +Get current last processed 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 - 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 - `address` must be a valid C string - `error` must be a valid pointer to an FFIError +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call -**Module:** `address_pool` +**Module:** `managed_wallet` --- -#### `managed_wallet_set_gap_limit` +#### `managed_wallet_mark_address_used` ```c -managed_wallet_set_gap_limit(managed_wallet: *mut FFIManagedWalletInfo, account_type: FFIAccountType, account_index: c_uint, pool_type: FFIAddressPoolType, gap_limit: c_uint, error: *mut FFIError,) -> bool +managed_wallet_mark_address_used(managed_wallet: *mut FFIManagedWalletInfo, address: *const c_char, error: *mut FFIError,) -> bool ``` **Description:** -Set the gap limit for an address pool The gap limit determines how many unused addresses to maintain at the end of the pool. This is important for wallet recovery and address discovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError +Mark an address as used in the pool This updates the pool's tracking of which addresses have been used, which is important for gap limit management and wallet recovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `address` must be a valid C string - `error` must be a valid pointer to an FFIError **Module:** `address_pool` --- -#### `managed_wallet_synced_height` +#### `managed_wallet_set_gap_limit` ```c -managed_wallet_synced_height(managed_wallet: *const FFIManagedWalletInfo, error: *mut FFIError,) -> c_uint +managed_wallet_set_gap_limit(managed_wallet: *mut FFIManagedWalletInfo, account_type: FFIAccountType, account_index: c_uint, pool_type: FFIAddressPoolType, gap_limit: c_uint, error: *mut FFIError,) -> bool ``` **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 - The caller must ensure all pointers remain valid for the duration of this call +Set the gap limit for an address pool The gap limit determines how many unused addresses to maintain at the end of the pool. This is important for wallet recovery and address discovery. # Safety - `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError **Safety:** -- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError structure - The caller must ensure all pointers remain valid for the duration of this call +- `managed_wallet` must be a valid pointer to an FFIManagedWalletInfo - `error` must be a valid pointer to an FFIError -**Module:** `managed_wallet` +**Module:** `address_pool` --- diff --git a/key-wallet-ffi/src/managed_wallet.rs b/key-wallet-ffi/src/managed_wallet.rs index 05e2c1062..47fc2dffb 100644 --- a/key-wallet-ffi/src/managed_wallet.rs +++ b/key-wallet-ffi/src/managed_wallet.rs @@ -453,7 +453,7 @@ pub unsafe extern "C" fn managed_wallet_get_balance( true } -/// Get current synced height from wallet info +/// Get current last processed height from wallet info /// /// # Safety /// @@ -461,12 +461,12 @@ pub unsafe extern "C" fn managed_wallet_get_balance( /// - `error` must be a valid pointer to an FFIError structure /// - 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( +pub unsafe extern "C" fn managed_wallet_last_processed_height( managed_wallet: *const FFIManagedWalletInfo, error: *mut FFIError, ) -> c_uint { let managed_wallet = deref_ptr!(managed_wallet, error); - managed_wallet.inner().synced_height() + managed_wallet.inner().last_processed_height() } /// Free managed wallet info diff --git a/key-wallet-ffi/src/transaction.rs b/key-wallet-ffi/src/transaction.rs index cd8917155..c58a743b5 100644 --- a/key-wallet-ffi/src/transaction.rs +++ b/key-wallet-ffi/src/transaction.rs @@ -246,7 +246,7 @@ pub unsafe extern "C" fn wallet_build_and_sign_transaction( let mut tx_builder_with_inputs = match tx_builder.select_inputs( &utxos, SelectionStrategy::BranchAndBound, - managed_wallet.synced_height(), + managed_wallet.last_processed_height(), |utxo| { // Look up the derivation path for this UTXO's address let path = address_to_path.get(&utxo.address)?; diff --git a/key-wallet-ffi/src/utxo.rs b/key-wallet-ffi/src/utxo.rs index 5150adb7a..3ecb1585b 100644 --- a/key-wallet-ffi/src/utxo.rs +++ b/key-wallet-ffi/src/utxo.rs @@ -117,7 +117,7 @@ pub unsafe extern "C" fn managed_wallet_get_utxos( // Get script bytes let script_bytes = utxo.txout.script_pubkey.as_bytes().to_vec(); - let current_height = managed_info.inner().synced_height(); + let current_height = managed_info.inner().last_processed_height(); let confirmations = utxo.confirmations(current_height); let ffi_utxo = FFIUTXO::new( diff --git a/key-wallet-ffi/src/utxo_tests.rs b/key-wallet-ffi/src/utxo_tests.rs index d6cba5c58..6b8fc35cc 100644 --- a/key-wallet-ffi/src/utxo_tests.rs +++ b/key-wallet-ffi/src/utxo_tests.rs @@ -233,7 +233,7 @@ mod utxo_tests { managed_info.accounts.insert(bip44_account).unwrap(); let ffi_managed_info = Box::into_raw(Box::new(FFIManagedWalletInfo::new(managed_info))); - unsafe { (*ffi_managed_info).inner_mut() }.update_synced_height(300); + unsafe { (*ffi_managed_info).inner_mut() }.update_last_processed_height(300); let result = unsafe { managed_wallet_get_utxos(&*ffi_managed_info, &mut utxos_out, &mut count_out, error) }; diff --git a/key-wallet-ffi/src/wallet_manager.rs b/key-wallet-ffi/src/wallet_manager.rs index 2cb4cbc92..adfcd5a55 100644 --- a/key-wallet-ffi/src/wallet_manager.rs +++ b/key-wallet-ffi/src/wallet_manager.rs @@ -570,7 +570,7 @@ pub unsafe extern "C" fn wallet_manager_current_height( let manager_ref = deref_ptr!(manager, error); manager_ref.runtime.block_on(async { let manager_guard = manager_ref.manager.read().await; - manager_guard.synced_height() + manager_guard.last_processed_height() }) } diff --git a/key-wallet-ffi/src/wallet_manager_tests.rs b/key-wallet-ffi/src/wallet_manager_tests.rs index 5c1ba66a5..b26eb2b3f 100644 --- a/key-wallet-ffi/src/wallet_manager_tests.rs +++ b/key-wallet-ffi/src/wallet_manager_tests.rs @@ -448,7 +448,7 @@ mod tests { let manager_ref = &*manager; manager_ref.runtime.block_on(async { let mut manager_guard = manager_ref.manager.write().await; - manager_guard.update_synced_height(new_height); + manager_guard.update_last_processed_height(new_height); }); } diff --git a/key-wallet-manager/examples/wallet_creation.rs b/key-wallet-manager/examples/wallet_creation.rs index 4529f5bb7..a80d3242e 100644 --- a/key-wallet-manager/examples/wallet_creation.rs +++ b/key-wallet-manager/examples/wallet_creation.rs @@ -142,11 +142,11 @@ fn main() { // Example 7: Block height tracking println!("\n7. Block height tracking..."); - println!(" Current height (Testnet): {:?}", manager.synced_height()); + println!(" Current height (Testnet): {:?}", manager.last_processed_height()); // Update height - manager.update_synced_height(850_000); - println!(" Updated height to: {:?}", manager.synced_height()); + manager.update_last_processed_height(850_000); + println!(" Updated height to: {:?}", manager.last_processed_height()); println!("\n=== Summary ==="); println!("Total wallets created: {}", manager.wallet_count()); diff --git a/key-wallet-manager/src/lib.rs b/key-wallet-manager/src/lib.rs index ccb293edd..f1a0ff118 100644 --- a/key-wallet-manager/src/lib.rs +++ b/key-wallet-manager/src/lib.rs @@ -91,9 +91,9 @@ pub struct WalletManager { /// Network the managed wallets are used for network: Network, /// Last fully processed block height. - synced_height: CoreBlockHeight, + last_processed_height: CoreBlockHeight, /// Height at which filter scanning was last committed. - filter_committed_height: CoreBlockHeight, + synced_height: CoreBlockHeight, /// Immutable wallets indexed by wallet ID wallets: BTreeMap, /// Mutable wallet info indexed by wallet ID @@ -111,8 +111,8 @@ impl WalletManager { pub fn new(network: Network) -> Self { Self { network, + last_processed_height: 0, synced_height: 0, - filter_committed_height: 0, wallets: BTreeMap::new(), wallet_infos: BTreeMap::new(), structural_revision: 0, @@ -304,7 +304,7 @@ impl WalletManager { // Create managed wallet info let mut managed_info = T::from_wallet(&wallet); - managed_info.set_birth_height(self.synced_height); + managed_info.set_birth_height(self.last_processed_height); managed_info.set_first_loaded_at(current_timestamp()); self.wallets.insert(wallet_id, wallet); @@ -345,7 +345,7 @@ impl WalletManager { // Create managed wallet info let mut managed_info = T::from_wallet(&wallet); - managed_info.set_birth_height(self.synced_height); + managed_info.set_birth_height(self.last_processed_height); managed_info.set_first_loaded_at(current_timestamp()); self.wallets.insert(wallet_id, wallet); @@ -393,7 +393,7 @@ impl WalletManager { // Create managed wallet info let mut managed_info = T::from_wallet(&wallet); - managed_info.set_birth_height(self.synced_height); + managed_info.set_birth_height(self.last_processed_height); managed_info.set_first_loaded_at(current_timestamp()); self.wallets.insert(wallet_id, wallet); @@ -438,7 +438,7 @@ impl WalletManager { let mut managed_info = T::from_wallet(&wallet); // Use the current height as the birth height since we don't know when it was originally created - managed_info.set_birth_height(self.synced_height); + managed_info.set_birth_height(self.last_processed_height); managed_info.set_first_loaded_at(current_timestamp()); self.wallets.insert(wallet_id, wallet); diff --git a/key-wallet-manager/src/process_block.rs b/key-wallet-manager/src/process_block.rs index 2e5d27cb2..112a57bc3 100644 --- a/key-wallet-manager/src/process_block.rs +++ b/key-wallet-manager/src/process_block.rs @@ -37,7 +37,7 @@ impl WalletInterface for WalletM result.new_addresses.extend(check_result.new_addresses); } - self.update_synced_height(height); + self.update_last_processed_height(height); result } @@ -97,30 +97,30 @@ impl WalletInterface for WalletM self.wallet_infos.values().map(|info| info.birth_height()).min().unwrap_or(0) } - fn synced_height(&self) -> CoreBlockHeight { - self.synced_height + fn last_processed_height(&self) -> CoreBlockHeight { + self.last_processed_height } - fn update_synced_height(&mut self, height: CoreBlockHeight) { - self.synced_height = height; + fn update_last_processed_height(&mut self, height: CoreBlockHeight) { + self.last_processed_height = height; let snapshot = self.snapshot_balances(); for (_wallet_id, info) in self.wallet_infos.iter_mut() { - info.update_synced_height(height); + info.update_last_processed_height(height); } self.emit_balance_changes(&snapshot); } - fn filter_committed_height(&self) -> CoreBlockHeight { - self.filter_committed_height + fn synced_height(&self) -> CoreBlockHeight { + self.synced_height } - fn update_filter_committed_height(&mut self, height: CoreBlockHeight) { - self.filter_committed_height = height; - if height > self.synced_height { - self.update_synced_height(height); + fn update_synced_height(&mut self, height: CoreBlockHeight) { + self.synced_height = height; + if height > self.last_processed_height { + self.update_last_processed_height(height); } } @@ -216,19 +216,19 @@ mod tests { } #[tokio::test] - async fn test_synced_height() { + async fn test_last_processed_height() { let mut manager: WalletManager = WalletManager::new(Network::Testnet); // Initial state - assert_eq!(manager.synced_height(), 0); - // Inrease synced height - manager.update_synced_height(1000); - assert_eq!(manager.synced_height(), 1000); - //Increase synced height again - manager.update_synced_height(5000); - assert_eq!(manager.synced_height(), 5000); - // Decrease synced height - manager.update_synced_height(10); - assert_eq!(manager.synced_height(), 10); + assert_eq!(manager.last_processed_height(), 0); + // Increase last processed height + manager.update_last_processed_height(1000); + assert_eq!(manager.last_processed_height(), 1000); + // Increase last processed height again + manager.update_last_processed_height(5000); + assert_eq!(manager.last_processed_height(), 5000); + // Decrease last processed height + manager.update_last_processed_height(10); + assert_eq!(manager.last_processed_height(), 10); } #[tokio::test] @@ -386,9 +386,9 @@ mod tests { assert_eq!(manager.monitor_revision(), expected_rev, "after get_change_address"); } - // update_synced_height does NOT bump - manager.update_synced_height(1000); - assert_eq!(manager.monitor_revision(), expected_rev, "after update_synced_height"); + // update_last_processed_height does NOT bump + manager.update_last_processed_height(1000); + assert_eq!(manager.monitor_revision(), expected_rev, "after update_last_processed_height"); // process_mempool_transaction bumps from UTXO changes and possibly // new addresses generated via gap limit maintenance diff --git a/key-wallet-manager/src/test_utils/mock_wallet.rs b/key-wallet-manager/src/test_utils/mock_wallet.rs index d17cb1ce3..180e064ac 100644 --- a/key-wallet-manager/src/test_utils/mock_wallet.rs +++ b/key-wallet-manager/src/test_utils/mock_wallet.rs @@ -12,7 +12,7 @@ type InstantLockCaptures = Arc)>>>; pub struct MockWallet { processed_blocks: Arc>>, processed_transactions: Arc>>, - synced_height: CoreBlockHeight, + last_processed_height: CoreBlockHeight, event_sender: broadcast::Sender, /// When true, process_mempool_transaction returns is_relevant=true. mempool_relevant: bool, @@ -47,7 +47,7 @@ impl MockWallet { Self { processed_blocks: Arc::new(Mutex::new(Vec::new())), processed_transactions: Arc::new(Mutex::new(Vec::new())), - synced_height: 0, + last_processed_height: 0, event_sender, mempool_relevant: false, addresses: Vec::new(), @@ -156,12 +156,12 @@ impl WalletInterface for MockWallet { self.outpoints.clone() } - fn synced_height(&self) -> CoreBlockHeight { - self.synced_height + fn last_processed_height(&self) -> CoreBlockHeight { + self.last_processed_height } - fn update_synced_height(&mut self, height: CoreBlockHeight) { - self.synced_height = height; + fn update_last_processed_height(&mut self, height: CoreBlockHeight) { + self.last_processed_height = height; } fn monitor_revision(&self) -> u64 { @@ -189,7 +189,7 @@ impl WalletInterface for MockWallet { /// Mock wallet that returns false for filter checks pub struct NonMatchingMockWallet { - synced_height: CoreBlockHeight, + last_processed_height: CoreBlockHeight, event_sender: broadcast::Sender, } @@ -203,7 +203,7 @@ impl NonMatchingMockWallet { pub fn new() -> Self { let (event_sender, _) = broadcast::channel(16); Self { - synced_height: 0, + last_processed_height: 0, event_sender, } } @@ -231,12 +231,12 @@ impl WalletInterface for NonMatchingMockWallet { Vec::new() } - fn synced_height(&self) -> CoreBlockHeight { - self.synced_height + fn last_processed_height(&self) -> CoreBlockHeight { + self.last_processed_height } - fn update_synced_height(&mut self, height: CoreBlockHeight) { - self.synced_height = height; + fn update_last_processed_height(&mut self, height: CoreBlockHeight) { + self.last_processed_height = height; } fn subscribe_events(&self) -> broadcast::Receiver { diff --git a/key-wallet-manager/src/wallet_interface.rs b/key-wallet-manager/src/wallet_interface.rs index 85a066368..90e01e80d 100644 --- a/key-wallet-manager/src/wallet_interface.rs +++ b/key-wallet-manager/src/wallet_interface.rs @@ -86,23 +86,23 @@ pub trait WalletInterface: Send + Sync + 'static { } /// Return the last fully processed height of the wallet. - fn synced_height(&self) -> CoreBlockHeight; + fn last_processed_height(&self) -> CoreBlockHeight; - /// Update the wallet's synced height. This also triggers balance updates. - fn update_synced_height(&mut self, height: CoreBlockHeight); + /// Update the wallet's last processed height. This also triggers balance updates. + fn update_last_processed_height(&mut self, height: CoreBlockHeight); /// Return the height at which filter scanning was last committed. - /// Defaults to `synced_height()` for implementations that don't separate these concepts. - // TODO: This can probably somehow be combined with synced_height(). - fn filter_committed_height(&self) -> CoreBlockHeight { - self.synced_height() + /// Defaults to `last_processed_height()` for implementations that don't separate these concepts. + // TODO: This can probably somehow be combined with last_processed_height(). + fn synced_height(&self) -> CoreBlockHeight { + self.last_processed_height() } - /// Update the filter committed height. Call when a height is fully processed + /// Update the committed synced height. Call when a height is fully processed /// (including any rescans for newly discovered addresses). - fn update_filter_committed_height(&mut self, height: CoreBlockHeight) { - if height > self.synced_height() { - self.update_synced_height(height); + fn update_synced_height(&mut self, height: CoreBlockHeight) { + if height > self.last_processed_height() { + self.update_last_processed_height(height); } } diff --git a/key-wallet-manager/tests/integration_test.rs b/key-wallet-manager/tests/integration_test.rs index e48674469..6a0360380 100644 --- a/key-wallet-manager/tests/integration_test.rs +++ b/key-wallet-manager/tests/integration_test.rs @@ -17,7 +17,7 @@ fn test_wallet_manager_creation() { let manager = WalletManager::::new(Network::Testnet); // WalletManager::new returns Self, not Result - assert_eq!(manager.synced_height(), 0); + assert_eq!(manager.last_processed_height(), 0); assert_eq!(manager.wallet_count(), 0); // No wallets created yet assert_eq!(manager.monitor_revision(), 0); } @@ -163,11 +163,11 @@ fn test_block_height_tracking() { let mut manager = WalletManager::::new(Network::Testnet); // Initial state - assert_eq!(manager.synced_height(), 0); + assert_eq!(manager.last_processed_height(), 0); // Set height before adding wallets - manager.update_synced_height(1000); - assert_eq!(manager.synced_height(), 1000); + manager.update_last_processed_height(1000); + assert_eq!(manager.last_processed_height(), 1000); let mnemonic1 = Mnemonic::generate(12, Language::English).unwrap(); let wallet_id1 = manager @@ -191,46 +191,46 @@ fn test_block_height_tracking() { assert_eq!(manager.wallet_count(), 2); - // Verify both wallets have synced_height of 0 initially + // Verify both wallets have last_processed_height of 0 initially for wallet_info in manager.get_all_wallet_infos().values() { - assert_eq!(wallet_info.synced_height(), 0); + assert_eq!(wallet_info.last_processed_height(), 0); } // Update height - should propagate to all wallets - manager.update_synced_height(12345); - assert_eq!(manager.synced_height(), 12345); + manager.update_last_processed_height(12345); + assert_eq!(manager.last_processed_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); + assert_eq!(wallet_info1.last_processed_height(), 12345); + assert_eq!(wallet_info2.last_processed_height(), 12345); // Update again - verify subsequent updates work - manager.update_synced_height(20000); - assert_eq!(manager.synced_height(), 20000); + manager.update_last_processed_height(20000); + assert_eq!(manager.last_processed_height(), 20000); for wallet_info in manager.get_all_wallet_infos().values() { - assert_eq!(wallet_info.synced_height(), 20000); + assert_eq!(wallet_info.last_processed_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); + wallet_info1.update_last_processed_height(30000); let wallet_info2 = manager.get_wallet_info_mut(&wallet_id2).unwrap(); - wallet_info2.update_synced_height(25000); + wallet_info2.update_last_processed_height(25000); - // Verify each wallet has its own synced_height + // Verify each wallet has its own last_processed_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); + assert_eq!(wallet_info1.last_processed_height(), 30000); + assert_eq!(wallet_info2.last_processed_height(), 25000); // Manager update_height still syncs all wallets - manager.update_synced_height(40000); + manager.update_last_processed_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); + assert_eq!(wallet_info1.last_processed_height(), 40000); + assert_eq!(wallet_info2.last_processed_height(), 40000); } diff --git a/key-wallet-manager/tests/spv_integration_tests.rs b/key-wallet-manager/tests/spv_integration_tests.rs index ddcffc29a..71b3bbfab 100644 --- a/key-wallet-manager/tests/spv_integration_tests.rs +++ b/key-wallet-manager/tests/spv_integration_tests.rs @@ -69,12 +69,17 @@ async fn test_block_processing_result_empty() { } fn assert_wallet_heights(manager: &WalletManager, expected_height: u32) { - assert_eq!(manager.synced_height(), expected_height, "height should be {}", expected_height); + assert_eq!( + manager.last_processed_height(), + expected_height, + "height should be {}", + expected_height + ); for wallet_info in manager.get_all_wallet_infos().values() { assert_eq!( - wallet_info.synced_height(), + wallet_info.last_processed_height(), expected_height, - "synced_height should be {}", + "last_processed_height should be {}", expected_height ); } diff --git a/key-wallet/src/managed_account/mod.rs b/key-wallet/src/managed_account/mod.rs index c8064dae1..2633cb0b0 100644 --- a/key-wallet/src/managed_account/mod.rs +++ b/key-wallet/src/managed_account/mod.rs @@ -555,12 +555,12 @@ impl ManagedCoreAccount { } /// Return the UTXOs of this account for which - /// [`Utxo::is_spendable`] holds at `synced_height`. See that method + /// [`Utxo::is_spendable`] holds at `last_processed_height`. See that method /// for the exact policy. Call this per-account rather than /// aggregating across the wallet, since spendability is /// account-type specific. - pub fn spendable_utxos(&self, synced_height: u32) -> BTreeSet<&Utxo> { - self.utxos.values().filter(|utxo| utxo.is_spendable(synced_height)).collect() + pub fn spendable_utxos(&self, last_processed_height: u32) -> BTreeSet<&Utxo> { + self.utxos.values().filter(|utxo| utxo.is_spendable(last_processed_height)).collect() } /// Update the account balance. @@ -569,7 +569,7 @@ impl ManagedCoreAccount { /// (in a block or InstantSend-locked) or the `unconfirmed` bucket /// (mempool only). Both are spendable per [`Utxo::is_spendable`]; /// the split is only for display. - pub fn update_balance(&mut self, synced_height: u32) { + pub fn update_balance(&mut self, last_processed_height: u32) { let mut confirmed = 0; let mut unconfirmed = 0; let mut immature = 0; @@ -578,7 +578,7 @@ impl ManagedCoreAccount { let value = utxo.txout.value; if utxo.is_locked { locked += value; - } else if !utxo.is_mature(synced_height) { + } else if !utxo.is_mature(last_processed_height) { immature += value; } else if utxo.is_confirmed || utxo.is_instantlocked { confirmed += value; diff --git a/key-wallet/src/tests/balance_tests.rs b/key-wallet/src/tests/balance_tests.rs index 4d6ed746c..05706879b 100644 --- a/key-wallet/src/tests/balance_tests.rs +++ b/key-wallet/src/tests/balance_tests.rs @@ -22,7 +22,7 @@ fn test_balance_with_mixed_utxo_types() { wallet_info.accounts.insert(account).unwrap(); assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); - wallet_info.update_synced_height(1100); + wallet_info.update_last_processed_height(1100); let expected = WalletCoreBalance::new(10_100_000, 0, 20_000_000, 0); assert_eq!(wallet_info.balance(), expected); } @@ -39,12 +39,12 @@ fn test_coinbase_maturity_boundary() { assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); // 99 confirmations: immature - wallet_info.update_synced_height(1099); + wallet_info.update_last_processed_height(1099); let expected_immature = WalletCoreBalance::new(0, 0, 50_000_000, 0); assert_eq!(wallet_info.balance(), expected_immature); // 100 confirmations: mature - wallet_info.update_synced_height(1100); + wallet_info.update_last_processed_height(1100); let expected_mature = WalletCoreBalance::new(50_000_000, 0, 0, 0); assert_eq!(wallet_info.balance(), expected_mature); } @@ -60,7 +60,7 @@ fn test_locked_utxos_in_locked_balance() { wallet_info.accounts.insert(account).unwrap(); assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); - wallet_info.update_synced_height(1100); + wallet_info.update_last_processed_height(1100); let expected = WalletCoreBalance::new(0, 0, 0, 100_000); assert_eq!(wallet_info.balance(), expected); } @@ -75,7 +75,7 @@ fn test_unconfirmed_utxos_in_unconfirmed_balance() { wallet_info.accounts.insert(account).unwrap(); assert_eq!(wallet_info.balance(), WalletCoreBalance::default()); - wallet_info.update_synced_height(1100); + wallet_info.update_last_processed_height(1100); let expected = WalletCoreBalance::new(0, 100_000, 0, 0); assert_eq!(wallet_info.balance(), expected); } diff --git a/key-wallet/src/transaction_checking/wallet_checker.rs b/key-wallet/src/transaction_checking/wallet_checker.rs index 05a8625a1..391df1ae6 100644 --- a/key-wallet/src/transaction_checking/wallet_checker.rs +++ b/key-wallet/src/transaction_checking/wallet_checker.rs @@ -26,7 +26,7 @@ pub trait WalletTransactionChecker { /// /// If `update_balance` is true, refreshes the cached wallet balance after mutations. /// Callers that batch multiple transactions (e.g. block processing) can pass `false` - /// and refresh once at the end via `update_synced_height`. + /// and refresh once at the end via `update_last_processed_height`. /// /// The context parameter indicates where the transaction comes from (mempool, block, etc.) /// @@ -400,8 +400,8 @@ mod tests { let result = managed_wallet .check_core_transaction(&coinbase_tx, context, &mut wallet, true, true) .await; - // Set synced_height to block where coinbase was received to trigger balance updates. - managed_wallet.update_synced_height(block_height); + // Set last_processed_height to block where coinbase was received to trigger balance updates. + managed_wallet.update_last_processed_height(block_height); // Should be relevant assert!(result.is_relevant); @@ -428,12 +428,12 @@ mod tests { assert_eq!(managed_wallet.balance().immature(), 5_000_000_000); // Spendable UTXOs should be empty (coinbase not mature) - let synced_height = managed_wallet.synced_height(); + let last_processed_height = managed_wallet.last_processed_height(); assert!( managed_wallet .first_bip44_managed_account() .expect("Should have managed account") - .spendable_utxos(synced_height) + .spendable_utxos(last_processed_height) .is_empty(), "Coinbase UTXO should not be spendable until mature" ); @@ -560,8 +560,8 @@ mod tests { let result = managed_wallet .check_core_transaction(&coinbase_tx, context, &mut wallet, true, true) .await; - // Set synced_height to block where coinbase was received to trigger balance updates. - managed_wallet.update_synced_height(block_height); + // Set last_processed_height to block where coinbase was received to trigger balance updates. + managed_wallet.update_last_processed_height(block_height); // Should be relevant assert!(result.is_relevant); @@ -587,19 +587,19 @@ mod tests { assert_eq!(managed_wallet.balance().immature(), 5_000_000_000); // Spendable UTXOs should be empty (coinbase not mature yet) - let synced_height = managed_wallet.synced_height(); + let last_processed_height = managed_wallet.last_processed_height(); assert!( managed_wallet .first_bip44_managed_account() .expect("Should have managed account") - .spendable_utxos(synced_height) + .spendable_utxos(last_processed_height) .is_empty(), "No spendable UTXOs while coinbase is immature" ); // Now advance the chain height past maturity (100 blocks) let mature_height = block_height + 100; - managed_wallet.update_synced_height(mature_height); + managed_wallet.update_last_processed_height(mature_height); let managed_account = managed_wallet.first_bip44_managed_account().expect("Should have managed account"); @@ -617,11 +617,11 @@ mod tests { assert_eq!(immature_balance, 0, "Immature balance should be zero after maturity"); // Spendable UTXOs should now contain the matured coinbase - let synced_height = managed_wallet.synced_height(); + let last_processed_height = managed_wallet.last_processed_height(); let spendable = managed_wallet .first_bip44_managed_account() .expect("Should have managed account") - .spendable_utxos(synced_height); + .spendable_utxos(last_processed_height); assert_eq!(spendable.len(), 1, "Should have one spendable UTXO after maturity"); } diff --git a/key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs b/key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs index 1ebeaabda..8dc0b1a9c 100644 --- a/key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs +++ b/key-wallet/src/wallet/managed_wallet_info/asset_lock_builder.rs @@ -238,7 +238,7 @@ impl ManagedWalletInfo { .and_then(|account| account.next_change_address(xpub.as_ref(), true).ok()) .ok_or(AssetLockError::NoChangeAddress)?; - let synced_height = self.synced_height(); + let last_processed_height = self.last_processed_height(); // Separate credit outputs from funding specs let credit_outputs: Vec = @@ -271,7 +271,7 @@ impl ManagedWalletInfo { let tx_builder_with_inputs = tx_builder.select_inputs( &utxos, SelectionStrategy::BranchAndBound, - synced_height, + last_processed_height, |utxo| { let path = address_to_path.get(&utxo.address)?; let root_ext_priv = root_xpriv.to_extended_priv_key(network); @@ -373,7 +373,7 @@ impl ManagedWalletInfo { .and_then(|account| account.next_change_address(xpub.as_ref(), true).ok()) .ok_or(AssetLockError::NoChangeAddress)?; - let synced_height = self.synced_height(); + let last_processed_height = self.last_processed_height(); let credit_outputs: Vec = credit_output_fundings.iter().map(|f| f.output.clone()).collect(); @@ -398,7 +398,7 @@ impl ManagedWalletInfo { let tx_builder_with_inputs = tx_builder.select_inputs( &utxos, SelectionStrategy::BranchAndBound, - synced_height, + last_processed_height, |_utxo| None, )?; @@ -789,7 +789,7 @@ mod tests { .unwrap() .utxos .insert(utxo.outpoint, utxo); - info.update_synced_height(1100); + info.update_last_processed_height(1100); let signer = InMemorySigner { root, 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 33f7aba6c..4ce77dcea 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 @@ -65,6 +65,9 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount /// Get all UTXOs for the wallet fn utxos(&self) -> BTreeSet<&Utxo>; + /// Get spendable UTXOs (confirmed and not locked) + fn get_spendable_utxos(&self) -> BTreeSet<&Utxo>; + /// Get the wallet balance fn balance(&self) -> WalletCoreBalance; @@ -84,11 +87,11 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount fn immature_transactions(&self) -> Vec; /// Return the last fully processed height of the wallet. - fn synced_height(&self) -> CoreBlockHeight; + fn last_processed_height(&self) -> CoreBlockHeight; /// Update chain state and process any matured transactions /// This should be called when the chain tip advances to a new height - fn update_synced_height(&mut self, current_height: u32); + fn update_last_processed_height(&mut self, current_height: u32); /// Mark UTXOs for a transaction as InstantSend-locked across all accounts /// and update the corresponding transaction record context. @@ -144,8 +147,8 @@ impl WalletInfoInterface for ManagedWalletInfo { self.metadata.birth_height = height; } - fn synced_height(&self) -> CoreBlockHeight { - self.metadata.synced_height + fn last_processed_height(&self) -> CoreBlockHeight { + self.metadata.last_processed_height } fn first_loaded_at(&self) -> u64 { @@ -175,6 +178,12 @@ impl WalletInfoInterface for ManagedWalletInfo { } utxos } + fn get_spendable_utxos(&self) -> BTreeSet<&Utxo> { + self.utxos() + .into_iter() + .filter(|utxo| utxo.is_spendable(self.last_processed_height())) + .collect() + } fn balance(&self) -> WalletCoreBalance { self.balance @@ -182,9 +191,9 @@ impl WalletInfoInterface for ManagedWalletInfo { fn update_balance(&mut self) { let mut balance = WalletCoreBalance::default(); - let synced_height = self.synced_height(); + let last_processed_height = self.last_processed_height(); for account in self.accounts.all_accounts_mut() { - account.update_balance(synced_height); + account.update_balance(last_processed_height); balance += *account.balance(); } self.balance = balance; @@ -212,7 +221,7 @@ impl WalletInfoInterface for ManagedWalletInfo { // Find txids of immature coinbase UTXOs for account in self.accounts.all_accounts() { for utxo in account.utxos.values() { - if utxo.is_coinbase && !utxo.is_mature(self.synced_height()) { + if utxo.is_coinbase && !utxo.is_mature(self.last_processed_height()) { immature_txids.insert(utxo.outpoint.txid); } } @@ -230,8 +239,8 @@ impl WalletInfoInterface for ManagedWalletInfo { transactions } - fn update_synced_height(&mut self, current_height: u32) { - self.metadata.synced_height = current_height; + fn update_last_processed_height(&mut self, current_height: u32) { + self.metadata.last_processed_height = current_height; // Update cached balance self.update_balance(); } diff --git a/key-wallet/src/wallet/metadata.rs b/key-wallet/src/wallet/metadata.rs index a2bd8025e..4f297b240 100644 --- a/key-wallet/src/wallet/metadata.rs +++ b/key-wallet/src/wallet/metadata.rs @@ -16,8 +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 processed block height + pub last_processed_height: CoreBlockHeight, /// Last sync timestamp pub last_synced: Option, /// Total transactions