Skip to content

Commit 659a6d5

Browse files
authored
refactor(key-wallet-manager): track wallet heights per wallet (#689)
* refactor(key-wallet-manager): track wallet heights per wallet Move committed sync height into wallet metadata and expose it through `WalletInfoInterface` so `WalletManager` no longer relies on global cached heights. Compute manager-level synced_height and tip_height as the minimum across wallet infos, propagate committed sync updates per wallet, and keep tip_height as per-wallet applied chain state. Update tests and example messaging to match the new aggregation semantics. * enforce `Send + Sync + 'static` on generic type of the wallet manager
1 parent 9960262 commit 659a6d5

8 files changed

Lines changed: 62 additions & 46 deletions

File tree

key-wallet-ffi/src/wallet_manager_tests.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ mod tests {
442442
let height = unsafe { wallet_manager::wallet_manager_current_height(manager, error) };
443443
assert_eq!(height, 0);
444444

445-
// Update height
445+
// Updating last-processed height without wallets is a no-op
446446
let new_height = 12345;
447447
unsafe {
448448
let manager_ref = &*manager;
@@ -455,7 +455,7 @@ mod tests {
455455
// Get updated height
456456
let current_height =
457457
unsafe { wallet_manager::wallet_manager_current_height(manager, error) };
458-
assert_eq!(current_height, new_height);
458+
assert_eq!(current_height, 0);
459459

460460
// Clean up
461461
unsafe {

key-wallet-manager/examples/wallet_creation.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,11 @@ fn main() {
142142
// Example 7: Block height tracking
143143
println!("\n7. Block height tracking...");
144144

145-
println!(" Current height (Testnet): {:?}", manager.last_processed_height());
145+
println!(" Current last-processed height (Testnet): {:?}", manager.last_processed_height());
146146

147-
// Update height
147+
// Update last-processed height across all managed wallets
148148
manager.update_last_processed_height(850_000);
149-
println!(" Updated height to: {:?}", manager.last_processed_height());
149+
println!(" Updated last-processed height to: {:?}", manager.last_processed_height());
150150

151151
println!("\n=== Summary ===");
152152
println!("Total wallets created: {}", manager.wallet_count());

key-wallet-manager/src/accessors.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use key_wallet::{Account, Address, Network, Utxo, Wallet};
99
use std::collections::{BTreeMap, BTreeSet};
1010
use tokio::sync::broadcast;
1111

12-
impl<T: WalletInfoInterface> WalletManager<T> {
12+
impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
1313
/// Get a wallet by ID
1414
pub fn get_wallet(&self, wallet_id: &WalletId) -> Option<&Wallet> {
1515
self.wallets.get(wallet_id)

key-wallet-manager/src/lib.rs

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,9 @@ pub struct CheckTransactionsResult {
8787
/// Each wallet can contain multiple accounts following BIP44 standard.
8888
/// This is the main entry point for wallet operations.
8989
#[derive(Debug)]
90-
pub struct WalletManager<T: WalletInfoInterface = ManagedWalletInfo> {
90+
pub struct WalletManager<T: WalletInfoInterface + Send + Sync + 'static = ManagedWalletInfo> {
9191
/// Network the managed wallets are used for
9292
network: Network,
93-
/// Last fully processed block height.
94-
last_processed_height: CoreBlockHeight,
95-
/// Height at which filter scanning was last committed.
96-
synced_height: CoreBlockHeight,
9793
/// Immutable wallets indexed by wallet ID
9894
wallets: BTreeMap<WalletId, Wallet>,
9995
/// Mutable wallet info indexed by wallet ID
@@ -106,13 +102,11 @@ pub struct WalletManager<T: WalletInfoInterface = ManagedWalletInfo> {
106102
event_sender: broadcast::Sender<WalletEvent>,
107103
}
108104

109-
impl<T: WalletInfoInterface> WalletManager<T> {
105+
impl<T: WalletInfoInterface + Send + Sync + 'static> WalletManager<T> {
110106
/// Create a new wallet manager
111107
pub fn new(network: Network) -> Self {
112108
Self {
113109
network,
114-
last_processed_height: 0,
115-
synced_height: 0,
116110
wallets: BTreeMap::new(),
117111
wallet_infos: BTreeMap::new(),
118112
structural_revision: 0,
@@ -304,7 +298,7 @@ impl<T: WalletInfoInterface> WalletManager<T> {
304298

305299
// Create managed wallet info
306300
let mut managed_info = T::from_wallet(&wallet);
307-
managed_info.set_birth_height(self.last_processed_height);
301+
managed_info.set_birth_height(self.last_processed_height());
308302
managed_info.set_first_loaded_at(current_timestamp());
309303

310304
self.wallets.insert(wallet_id, wallet);
@@ -345,7 +339,7 @@ impl<T: WalletInfoInterface> WalletManager<T> {
345339

346340
// Create managed wallet info
347341
let mut managed_info = T::from_wallet(&wallet);
348-
managed_info.set_birth_height(self.last_processed_height);
342+
managed_info.set_birth_height(self.last_processed_height());
349343
managed_info.set_first_loaded_at(current_timestamp());
350344

351345
self.wallets.insert(wallet_id, wallet);
@@ -393,7 +387,7 @@ impl<T: WalletInfoInterface> WalletManager<T> {
393387

394388
// Create managed wallet info
395389
let mut managed_info = T::from_wallet(&wallet);
396-
managed_info.set_birth_height(self.last_processed_height);
390+
managed_info.set_birth_height(self.last_processed_height());
397391
managed_info.set_first_loaded_at(current_timestamp());
398392

399393
self.wallets.insert(wallet_id, wallet);
@@ -438,7 +432,7 @@ impl<T: WalletInfoInterface> WalletManager<T> {
438432
let mut managed_info = T::from_wallet(&wallet);
439433

440434
// Use the current height as the birth height since we don't know when it was originally created
441-
managed_info.set_birth_height(self.last_processed_height);
435+
managed_info.set_birth_height(self.last_processed_height());
442436
managed_info.set_first_loaded_at(current_timestamp());
443437

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

key-wallet-manager/src/process_block.rs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,10 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletInterface for WalletM
9898
}
9999

100100
fn last_processed_height(&self) -> CoreBlockHeight {
101-
self.last_processed_height
101+
self.wallet_infos.values().map(|info| info.last_processed_height()).max().unwrap_or(0)
102102
}
103103

104104
fn update_last_processed_height(&mut self, height: CoreBlockHeight) {
105-
self.last_processed_height = height;
106-
107105
let snapshot = self.snapshot_balances();
108106

109107
for (_wallet_id, info) in self.wallet_infos.iter_mut() {
@@ -114,13 +112,12 @@ impl<T: WalletInfoInterface + Send + Sync + 'static> WalletInterface for WalletM
114112
}
115113

116114
fn synced_height(&self) -> CoreBlockHeight {
117-
self.synced_height
115+
self.wallet_infos.values().map(|info| info.synced_height()).min().unwrap_or(0)
118116
}
119117

120118
fn update_synced_height(&mut self, height: CoreBlockHeight) {
121-
self.synced_height = height;
122-
if height > self.last_processed_height {
123-
self.update_last_processed_height(height);
119+
for (_wallet_id, info) in self.wallet_infos.iter_mut() {
120+
info.update_synced_height(height);
124121
}
125122
}
126123

@@ -220,15 +217,14 @@ mod tests {
220217
let mut manager: WalletManager<ManagedWalletInfo> = WalletManager::new(Network::Testnet);
221218
// Initial state
222219
assert_eq!(manager.last_processed_height(), 0);
223-
// Increase last processed height
220+
// Updating last-processed height without wallets is a no-op
224221
manager.update_last_processed_height(1000);
225-
assert_eq!(manager.last_processed_height(), 1000);
226-
// Increase last processed height again
222+
assert_eq!(manager.last_processed_height(), 0);
223+
// Still a no-op without wallets
227224
manager.update_last_processed_height(5000);
228-
assert_eq!(manager.last_processed_height(), 5000);
229-
// Decrease last processed height
225+
assert_eq!(manager.last_processed_height(), 0);
230226
manager.update_last_processed_height(10);
231-
assert_eq!(manager.last_processed_height(), 10);
227+
assert_eq!(manager.last_processed_height(), 0);
232228
}
233229

234230
#[tokio::test]

key-wallet-manager/tests/integration_test.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,13 @@ fn test_block_height_tracking() {
164164

165165
// Initial state
166166
assert_eq!(manager.last_processed_height(), 0);
167+
assert_eq!(manager.synced_height(), 0);
167168

168-
// Set height before adding wallets
169+
// Updating heights before adding wallets is a no-op
169170
manager.update_last_processed_height(1000);
170-
assert_eq!(manager.last_processed_height(), 1000);
171+
manager.update_synced_height(500);
172+
assert_eq!(manager.last_processed_height(), 0);
173+
assert_eq!(manager.synced_height(), 0);
171174

172175
let mnemonic1 = Mnemonic::generate(12, Language::English).unwrap();
173176
let wallet_id1 = manager
@@ -191,46 +194,53 @@ fn test_block_height_tracking() {
191194

192195
assert_eq!(manager.wallet_count(), 2);
193196

194-
// Verify both wallets have last_processed_height of 0 initially
197+
// Verify both wallets have last_processed_height and synced_height of 0 initially
195198
for wallet_info in manager.get_all_wallet_infos().values() {
196199
assert_eq!(wallet_info.last_processed_height(), 0);
200+
assert_eq!(wallet_info.synced_height(), 0);
197201
}
198202

199-
// Update height - should propagate to all wallets
203+
// Update last-processed height - should propagate to all wallets
200204
manager.update_last_processed_height(12345);
201205
assert_eq!(manager.last_processed_height(), 12345);
202206

203-
// Verify all wallets got updated
207+
// Verify all wallets got updated while synced_height stays at 0
204208
let wallet_info1 = manager.get_wallet_info(&wallet_id1).unwrap();
205209
let wallet_info2 = manager.get_wallet_info(&wallet_id2).unwrap();
206210
assert_eq!(wallet_info1.last_processed_height(), 12345);
207211
assert_eq!(wallet_info2.last_processed_height(), 12345);
212+
assert_eq!(wallet_info1.synced_height(), 0);
213+
assert_eq!(wallet_info2.synced_height(), 0);
208214

209-
// Update again - verify subsequent updates work
210-
manager.update_last_processed_height(20000);
211-
assert_eq!(manager.last_processed_height(), 20000);
215+
// Update synced height - should propagate to all wallets without touching last_processed_height
216+
manager.update_synced_height(20000);
217+
assert_eq!(manager.synced_height(), 20000);
212218

213219
for wallet_info in manager.get_all_wallet_infos().values() {
214-
assert_eq!(wallet_info.last_processed_height(), 20000);
220+
assert_eq!(wallet_info.last_processed_height(), 12345);
221+
assert_eq!(wallet_info.synced_height(), 20000);
215222
}
216223

217-
// Update wallets individually to different heights
224+
// Update wallets individually to different last-processed heights
218225
let wallet_info1 = manager.get_wallet_info_mut(&wallet_id1).unwrap();
219226
wallet_info1.update_last_processed_height(30000);
220227

221228
let wallet_info2 = manager.get_wallet_info_mut(&wallet_id2).unwrap();
222229
wallet_info2.update_last_processed_height(25000);
223230

224-
// Verify each wallet has its own last_processed_height
231+
// Verify each wallet has its own last_processed_height and manager reports the max
225232
let wallet_info1 = manager.get_wallet_info(&wallet_id1).unwrap();
226233
let wallet_info2 = manager.get_wallet_info(&wallet_id2).unwrap();
227234
assert_eq!(wallet_info1.last_processed_height(), 30000);
228235
assert_eq!(wallet_info2.last_processed_height(), 25000);
236+
assert_eq!(manager.last_processed_height(), 30000);
229237

230-
// Manager update_height still syncs all wallets
231-
manager.update_last_processed_height(40000);
238+
// Manager synced-height update syncs across all wallets
239+
manager.update_synced_height(40000);
232240
let wallet_info1 = manager.get_wallet_info(&wallet_id1).unwrap();
233241
let wallet_info2 = manager.get_wallet_info(&wallet_id2).unwrap();
234-
assert_eq!(wallet_info1.last_processed_height(), 40000);
235-
assert_eq!(wallet_info2.last_processed_height(), 40000);
242+
assert_eq!(wallet_info1.last_processed_height(), 30000);
243+
assert_eq!(wallet_info2.last_processed_height(), 25000);
244+
assert_eq!(wallet_info1.synced_height(), 40000);
245+
assert_eq!(wallet_info2.synced_height(), 40000);
236246
}

key-wallet/src/wallet/managed_wallet_info/wallet_info_interface.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,16 @@ pub trait WalletInfoInterface: Sized + WalletTransactionChecker + ManagedAccount
8989
/// Return the last fully processed height of the wallet.
9090
fn last_processed_height(&self) -> CoreBlockHeight;
9191

92+
/// Return the durable wallet sync checkpoint height.
93+
fn synced_height(&self) -> CoreBlockHeight;
94+
9295
/// Update chain state and process any matured transactions
9396
/// This should be called when the chain tip advances to a new height
9497
fn update_last_processed_height(&mut self, current_height: u32);
9598

99+
/// Record that the durable wallet sync checkpoint has advanced to `current_height`.
100+
fn update_synced_height(&mut self, current_height: u32);
101+
96102
/// Mark UTXOs for a transaction as InstantSend-locked across all accounts
97103
/// and update the corresponding transaction record context.
98104
/// Returns `true` if any UTXO was newly marked.
@@ -151,6 +157,10 @@ impl WalletInfoInterface for ManagedWalletInfo {
151157
self.metadata.last_processed_height
152158
}
153159

160+
fn synced_height(&self) -> CoreBlockHeight {
161+
self.metadata.synced_height
162+
}
163+
154164
fn first_loaded_at(&self) -> u64 {
155165
self.metadata.first_loaded_at
156166
}
@@ -245,6 +255,10 @@ impl WalletInfoInterface for ManagedWalletInfo {
245255
self.update_balance();
246256
}
247257

258+
fn update_synced_height(&mut self, current_height: u32) {
259+
self.metadata.synced_height = current_height;
260+
}
261+
248262
fn mark_instant_send_utxos(&mut self, txid: &Txid, lock: &InstantLock) -> bool {
249263
if !self.instant_send_locks.insert(*txid) {
250264
return false;

key-wallet/src/wallet/metadata.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub struct WalletMetadata {
1818
pub birth_height: CoreBlockHeight,
1919
/// Last processed block height
2020
pub last_processed_height: CoreBlockHeight,
21+
/// Sync checkpoint height
22+
pub synced_height: CoreBlockHeight,
2123
/// Last sync timestamp
2224
pub last_synced: Option<u64>,
2325
/// Total transactions

0 commit comments

Comments
 (0)