From ecc3eee4d70921d6d04f391afaa18cb40589476d Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 28 Jul 2026 14:53:31 +0300 Subject: [PATCH 01/10] wallet: make updater apis public --- libwallet/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index a73cdb4c1..7c7314830 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -72,6 +72,7 @@ pub use api_impl::types::{ }; pub use backend::{WalletBackend, WalletBatch}; pub use internal::scan::scan; +pub use internal::updater; pub use slate_versions::ser as dalek_ser; pub use types::{ AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData, From e93cf6bd1a40fd8b5a5da68ea98d5dfaba96a451 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Tue, 4 Aug 2026 19:50:40 +0300 Subject: [PATCH 02/10] updater: made only useful functions public, remove public for unused functions --- libwallet/src/internal/updater.rs | 4 ++-- libwallet/src/lib.rs | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 3383d9e06..e2839ddc7 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -505,7 +505,7 @@ where } /// Apply refreshed API output data to the wallet -pub fn apply_api_outputs( +fn apply_api_outputs( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, wallet_outputs: &HashMap, Option, bool)>, @@ -876,7 +876,7 @@ where //TODO: Split up the output creation and the wallet insertion /// Build a coinbase output and the corresponding kernel -pub fn receive_coinbase( +fn receive_coinbase( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, block_fees: &BlockFees, diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 7c7314830..357e68d1b 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -72,7 +72,10 @@ pub use api_impl::types::{ }; pub use backend::{WalletBackend, WalletBatch}; pub use internal::scan::scan; -pub use internal::updater; +pub use internal::updater::{ + apply_advanced_tx_list_filtering, cancel_tx_and_outputs, map_wallet_outputs, refresh_outputs, + retrieve_info, retrieve_outputs, retrieve_txs, +}; pub use slate_versions::ser as dalek_ser; pub use types::{ AcctPathMapping, BlockIdentifier, CbData, Context, NodeClient, NodeVersionInfo, OutputData, From 19c046924a2068419584458b9b5e908a4f948bd7 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 5 Aug 2026 14:52:27 +0300 Subject: [PATCH 03/10] updater: comment map wallet outputs, remove unused public functions --- libwallet/src/internal/updater.rs | 12 ++++++++---- libwallet/src/lib.rs | 3 +-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index e2839ddc7..e4bf8dc1d 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -91,7 +91,7 @@ where } /// Apply advanced filtering to resultset from retrieve_txs below -pub fn apply_advanced_tx_list_filtering( +fn apply_advanced_tx_list_filtering( wallet: &mut WalletBackend, parent_key_id: Option<&Identifier>, query_args: &RetrieveTxQueryArgs, @@ -417,8 +417,12 @@ where Ok(()) } -/// build a local map of wallet outputs keyed by commit -/// and a list of outputs we want to query the node for +/// Build a local map of wallet outputs keyed by commit +/// and a list of outputs we want to query the node for. +/// If `update_all` equals `false` we will select outputs +/// that are actually involved in an outstanding transaction. +/// Returns mapping of output commit to tuple of derived key for output, +/// PMMR index, tx entry log identifier and check if output is unspent pub fn map_wallet_outputs( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, @@ -469,7 +473,7 @@ where } /// Cancel transaction and associated outputs -pub fn cancel_tx_and_outputs( +fn cancel_tx_and_outputs( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, mut tx: TxLogEntry, diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 357e68d1b..a21283674 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -73,8 +73,7 @@ pub use api_impl::types::{ pub use backend::{WalletBackend, WalletBatch}; pub use internal::scan::scan; pub use internal::updater::{ - apply_advanced_tx_list_filtering, cancel_tx_and_outputs, map_wallet_outputs, refresh_outputs, - retrieve_info, retrieve_outputs, retrieve_txs, + map_wallet_outputs, refresh_outputs, retrieve_info, retrieve_outputs, retrieve_txs, }; pub use slate_versions::ser as dalek_ser; pub use types::{ From f90d2d76845da21881d4d29be2820c82aa858f91 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Wed, 5 Aug 2026 14:53:18 +0300 Subject: [PATCH 04/10] fix: make cancel tx public for internal usage --- libwallet/src/internal/updater.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index e4bf8dc1d..a4c0566ec 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -473,7 +473,7 @@ where } /// Cancel transaction and associated outputs -fn cancel_tx_and_outputs( +pub fn cancel_tx_and_outputs( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>, mut tx: TxLogEntry, From fe53bf9e0a1ddbb50b6c3dabf874c15a66712596 Mon Sep 17 00:00:00 2001 From: ardocrat Date: Fri, 7 Aug 2026 15:26:29 +0300 Subject: [PATCH 05/10] fix: provide parent key to get last confirmed height for right account --- libwallet/src/api_impl/foreign.rs | 2 +- libwallet/src/api_impl/owner.rs | 2 +- libwallet/src/backend.rs | 14 +++++++++++--- libwallet/src/internal/updater.rs | 6 +++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index 036027aa2..db5d82885 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -94,7 +94,7 @@ where ret_slate.tx = Some(Slate::empty_transaction()); - let height = w.last_confirmed_height()?; + let height = w.last_confirmed_height(Some(&parent_key_id))?; let keychain = w.keychain(keychain_mask)?; let context = tx::add_output_to_slate( diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index a7a6691ae..ca89d0db4 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -1177,7 +1177,7 @@ where K: Keychain, { // Refuse if TTL is expired - let last_confirmed_height = w.last_confirmed_height()?; + let last_confirmed_height = w.last_confirmed_height(None)?; if slate.ttl_cutoff_height != 0 { if last_confirmed_height >= slate.ttl_cutoff_height { return Err(Error::TransactionExpired); diff --git a/libwallet/src/backend.rs b/libwallet/src/backend.rs index 2fa0a08aa..b1d4407eb 100644 --- a/libwallet/src/backend.rs +++ b/libwallet/src/backend.rs @@ -482,13 +482,21 @@ where Ok(Identifier::from_path(&return_path)) } - /// Last verified height of outputs directly descending from the given parent key. - pub fn last_confirmed_height(&mut self) -> Result { + /// Last verified height of outputs directly descending from the given parent key, + /// will use current parent key if not provided. + pub fn last_confirmed_height( + &mut self, + parent_key_id: Option<&Identifier>, + ) -> Result { let batch = self.db.batch()?; + let parent_key_id = match parent_key_id { + Some(id) => id, + None => &self.parent_key_id, + }; let last_confirmed_height = batch .get_ser( Some(CONFIRMED_HEIGHT_PREFIX), - &self.parent_key_id.to_bytes(), + &parent_key_id.to_bytes(), None, )? .unwrap_or_else(|| 0); diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index a4c0566ec..104c03ac4 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -420,7 +420,7 @@ where /// Build a local map of wallet outputs keyed by commit /// and a list of outputs we want to query the node for. /// If `update_all` equals `false` we will select outputs -/// that are actually involved in an outstanding transaction. +/// that are actually involved in existing transactions for account. /// Returns mapping of output commit to tuple of derived key for output, /// PMMR index, tx entry log identifier and check if output is unspent pub fn map_wallet_outputs( @@ -526,7 +526,7 @@ where // api output (if it exists) and refresh it in-place in the wallet. // Note: minimizing the time we spend holding the wallet lock. { - let last_confirmed_height = wallet.last_confirmed_height()?; + let last_confirmed_height = wallet.last_confirmed_height(Some(parent_key_id))?; // If the server height is less than our confirmed height, don't apply // these changes as the chain is syncing, incorrect or forking if height < last_confirmed_height { @@ -803,7 +803,7 @@ where C: NodeClient, K: Keychain, { - let current_height = wallet.last_confirmed_height()?; + let current_height = wallet.last_confirmed_height(Some(parent_key_id))?; let outputs = wallet .iter()? .filter(|out| out.root_key_id == *parent_key_id); From 4055765ee8c5a6c1ae21debcd862bdad6e8b2f07 Mon Sep 17 00:00:00 2001 From: wiesche Date: Fri, 7 Aug 2026 19:09:34 +0200 Subject: [PATCH 06/10] use account height for max sendable --- libwallet/src/api_impl/owner.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index c5b0d7b8f..d914b9453 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -322,7 +322,7 @@ where let parent_key_id = w.parent_key_id(); let wallet_info = updater::retrieve_info(w, &parent_key_id, minimum_confirmations)?; - let current_height = w.last_confirmed_height()?; + let current_height = w.last_confirmed_height(Some(&parent_key_id))?; let max_outputs = 500; let change_outputs = 1; let (amount, fee, input_count) = match selection::select_coins_and_fee( From 0f8364df94438b5cc22f800380f81597d5e02dc8 Mon Sep 17 00:00:00 2001 From: wiesche Date: Fri, 7 Aug 2026 19:16:11 +0200 Subject: [PATCH 07/10] docs: clarify wallet output mapping --- libwallet/src/internal/updater.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 104c03ac4..fd5f63f2e 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -417,10 +417,10 @@ where Ok(()) } -/// Build a local map of wallet outputs keyed by commit -/// and a list of outputs we want to query the node for. -/// If `update_all` equals `false` we will select outputs -/// that are actually involved in existing transactions for account. +/// Build a local map of wallet outputs keyed by commit. +/// The map keys identify outputs to query from the node. +/// If `update_all` is `false`, select outputs involved in outstanding +/// transactions for the account and outputs without a transaction log entry. /// Returns mapping of output commit to tuple of derived key for output, /// PMMR index, tx entry log identifier and check if output is unspent pub fn map_wallet_outputs( From 75acf6211b3a8f001cfc67b7086401b446a7a77b Mon Sep 17 00:00:00 2001 From: wiesche Date: Fri, 7 Aug 2026 19:22:02 +0200 Subject: [PATCH 08/10] preserve confirmed height api compatibility --- libwallet/src/api_impl/foreign.rs | 2 +- libwallet/src/api_impl/owner.rs | 4 ++-- libwallet/src/backend.rs | 17 +++++++++-------- libwallet/src/internal/updater.rs | 4 ++-- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/libwallet/src/api_impl/foreign.rs b/libwallet/src/api_impl/foreign.rs index db5d82885..76b60147d 100644 --- a/libwallet/src/api_impl/foreign.rs +++ b/libwallet/src/api_impl/foreign.rs @@ -94,7 +94,7 @@ where ret_slate.tx = Some(Slate::empty_transaction()); - let height = w.last_confirmed_height(Some(&parent_key_id))?; + let height = w.last_confirmed_height_for_parent(&parent_key_id)?; let keychain = w.keychain(keychain_mask)?; let context = tx::add_output_to_slate( diff --git a/libwallet/src/api_impl/owner.rs b/libwallet/src/api_impl/owner.rs index d914b9453..10a8d4a64 100644 --- a/libwallet/src/api_impl/owner.rs +++ b/libwallet/src/api_impl/owner.rs @@ -322,7 +322,7 @@ where let parent_key_id = w.parent_key_id(); let wallet_info = updater::retrieve_info(w, &parent_key_id, minimum_confirmations)?; - let current_height = w.last_confirmed_height(Some(&parent_key_id))?; + let current_height = w.last_confirmed_height_for_parent(&parent_key_id)?; let max_outputs = 500; let change_outputs = 1; let (amount, fee, input_count) = match selection::select_coins_and_fee( @@ -1240,7 +1240,7 @@ where K: Keychain, { // Refuse if TTL is expired - let last_confirmed_height = w.last_confirmed_height(None)?; + let last_confirmed_height = w.last_confirmed_height()?; if slate.ttl_cutoff_height != 0 { if last_confirmed_height >= slate.ttl_cutoff_height { return Err(Error::TransactionExpired); diff --git a/libwallet/src/backend.rs b/libwallet/src/backend.rs index b1d4407eb..1fdd3101f 100644 --- a/libwallet/src/backend.rs +++ b/libwallet/src/backend.rs @@ -482,17 +482,18 @@ where Ok(Identifier::from_path(&return_path)) } - /// Last verified height of outputs directly descending from the given parent key, - /// will use current parent key if not provided. - pub fn last_confirmed_height( + /// Last verified height of outputs directly descending from the current parent key. + pub fn last_confirmed_height(&mut self) -> Result { + let parent_key_id = self.parent_key_id.clone(); + self.last_confirmed_height_for_parent(&parent_key_id) + } + + /// Last verified height of outputs directly descending from the given parent key. + pub(crate) fn last_confirmed_height_for_parent( &mut self, - parent_key_id: Option<&Identifier>, + parent_key_id: &Identifier, ) -> Result { let batch = self.db.batch()?; - let parent_key_id = match parent_key_id { - Some(id) => id, - None => &self.parent_key_id, - }; let last_confirmed_height = batch .get_ser( Some(CONFIRMED_HEIGHT_PREFIX), diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index fd5f63f2e..76a2e6901 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -526,7 +526,7 @@ where // api output (if it exists) and refresh it in-place in the wallet. // Note: minimizing the time we spend holding the wallet lock. { - let last_confirmed_height = wallet.last_confirmed_height(Some(parent_key_id))?; + let last_confirmed_height = wallet.last_confirmed_height_for_parent(parent_key_id)?; // If the server height is less than our confirmed height, don't apply // these changes as the chain is syncing, incorrect or forking if height < last_confirmed_height { @@ -803,7 +803,7 @@ where C: NodeClient, K: Keychain, { - let current_height = wallet.last_confirmed_height(Some(parent_key_id))?; + let current_height = wallet.last_confirmed_height_for_parent(parent_key_id)?; let outputs = wallet .iter()? .filter(|out| out.root_key_id == *parent_key_id); From 434a0753130e0f39cb0bcee3afc863f4840cb8c4 Mon Sep 17 00:00:00 2001 From: wiesche Date: Fri, 7 Aug 2026 19:26:59 +0200 Subject: [PATCH 09/10] test account specific confirmed height --- controller/tests/accounts.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/controller/tests/accounts.rs b/controller/tests/accounts.rs index 35413bdb6..9a47e41f0 100644 --- a/controller/tests/accounts.rs +++ b/controller/tests/accounts.rs @@ -256,6 +256,15 @@ fn accounts_test_impl(test_dir: &'static str) -> Result<(), libwallet::Error> { }, )?; + // Use the requested account's stored height + { + wallet_inst!(wallet1, w); + assert_eq!(w.parent_key_id(), ExtKeychain::derive_key_id(2, 1, 0, 0, 0)); + let account2 = ExtKeychain::derive_key_id(2, 2, 0, 0, 0); + let account2_info = libwallet::retrieve_info(w, &account2, 1)?; + assert_eq!(account2_info.last_confirmed_height, 12); + } + // other account should be untouched { wallet_inst!(wallet1, w); From d8cfb985382a7bced8bccf14c3cc75115a763076 Mon Sep 17 00:00:00 2001 From: wiesche Date: Fri, 7 Aug 2026 19:46:29 +0200 Subject: [PATCH 10/10] clarify refresh output cleanup --- libwallet/src/internal/updater.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libwallet/src/internal/updater.rs b/libwallet/src/internal/updater.rs index 76a2e6901..9590f23d9 100644 --- a/libwallet/src/internal/updater.rs +++ b/libwallet/src/internal/updater.rs @@ -402,6 +402,7 @@ where /// Refreshes the outputs in a wallet with the latest information /// from a node +/// Also removes stale unconfirmed coinbase outputs across all accounts pub fn refresh_outputs( wallet: &mut WalletBackend, keychain_mask: Option<&SecretKey>,