Skip to content

Commit 0ff104d

Browse files
shumkovclaude
andcommitted
feat(key-wallet-manager): switch to single-map WalletManager with Arc<RwLock<T>> per wallet
PR-30 Phase 1: Refactor WalletManager to use a single map with per-wallet Arc<RwLock<T>> instead of two separate maps (wallets + wallet_infos). Key changes: - New ManagedWalletState struct bundling Wallet + ManagedWalletInfo + Persister - New WalletPersistence trait (store returns Result, NoPersistence default) - WalletInfoInterface gains wallet()/wallet_mut() methods - WalletTransactionChecker::check_core_transaction no longer takes wallet param - ManagedWalletInfo gets check_core_transaction_with_wallet helper - WalletManager uses BTreeMap<WalletId, Arc<RwLock<T>>> for shared state - insert_wallet_state() for external callers to inject pre-built state - Sync methods (monitored_addresses, watched_outpoints, monitor_revision, update_synced_height, process_instant_send_lock) made async for proper lock acquisition; synced_height/filter_committed_height stay sync - ManagedWalletState fields pub(crate) with public accessor methods Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 82278b6 commit 0ff104d

36 files changed

Lines changed: 2490 additions & 1351 deletions

dash-spv/src/client/lifecycle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ impl<W: WalletInterface, N: NetworkManager, S: StorageManager, H: EventHandler>
128128
// Create mempool state and build mempool manager if tracking is enabled
129129
let mempool_state = Arc::new(RwLock::new(MempoolState::default()));
130130
if config.enable_mempool_tracking {
131-
let initial_revision = wallet.read().await.monitor_revision();
131+
let initial_revision = wallet.read().await.monitor_revision().await;
132132
managers.mempool = Some(MempoolManager::new(
133133
wallet.clone(),
134134
mempool_state.clone(),

dash-spv/src/sync/filters/manager.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ impl<H: BlockHeaderStorage, FH: FilterHeaderStorage, F: FilterStorage, W: Wallet
500500
let end = batch.end_height();
501501
if end > self.progress.committed_height() {
502502
self.progress.update_committed_height(end);
503-
self.wallet.write().await.update_filter_committed_height(end);
503+
self.wallet.write().await.update_filter_committed_height(end).await;
504504
}
505505
self.processing_height = end + 1;
506506

@@ -675,7 +675,7 @@ impl<H: BlockHeaderStorage, FH: FilterHeaderStorage, F: FilterStorage, W: Wallet
675675

676676
// Match against wallet's current addresses
677677
let wallet = self.wallet.read().await;
678-
let addresses = wallet.monitored_addresses();
678+
let addresses = wallet.monitored_addresses().await;
679679
let matches = check_compact_filters_for_addresses(batch.filters(), addresses);
680680
drop(wallet);
681681

@@ -821,7 +821,7 @@ mod tests {
821821

822822
// Set wallet committed height via synced_height (MockWallet default delegates)
823823
let mut wallet = MockWallet::new();
824-
wallet.update_synced_height(50);
824+
wallet.update_synced_height(50).await;
825825
let wallet = Arc::new(RwLock::new(wallet));
826826

827827
// Pre-populate filter storage with filters at heights 1..=100
@@ -1049,7 +1049,7 @@ mod tests {
10491049
assert_eq!(manager.state(), SyncState::WaitForEvents);
10501050

10511051
// Wallet committed to height 100, so scan_start will be 101
1052-
manager.wallet.write().await.update_synced_height(100);
1052+
manager.wallet.write().await.update_synced_height(100).await;
10531053
// Filter headers only reached 50, so its below scan_start
10541054
manager.progress.update_filter_header_tip_height(50);
10551055
// Chain tip higher so the Synced early-return is not taken
@@ -1126,7 +1126,7 @@ mod tests {
11261126
// Simulate restart where everything is already synced but state is WaitForEvents.
11271127
// committed == stored == filter_header_tip — start_download detects synced state.
11281128
manager.set_state(SyncState::WaitForEvents);
1129-
manager.wallet.write().await.update_synced_height(100);
1129+
manager.wallet.write().await.update_synced_height(100).await;
11301130
manager.progress.update_committed_height(100);
11311131
manager.progress.update_stored_height(100);
11321132
manager.progress.update_filter_header_tip_height(100);

dash-spv/src/sync/mempool/manager.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,8 @@ impl<W: WalletInterface> MempoolManager<W> {
135135
requests: &RequestSender,
136136
) -> SyncResult<()> {
137137
let wallet = self.wallet.read().await;
138-
let addresses = wallet.monitored_addresses();
139-
let outpoints = wallet.watched_outpoints();
138+
let addresses = wallet.monitored_addresses().await;
139+
let outpoints = wallet.watched_outpoints().await;
140140
drop(wallet);
141141

142142
if addresses.is_empty() && outpoints.is_empty() {
@@ -379,7 +379,7 @@ impl<W: WalletInterface> MempoolManager<W> {
379379
drop(state);
380380
if marked {
381381
let mut wallet = self.wallet.write().await;
382-
wallet.process_instant_send_lock(*txid);
382+
wallet.process_instant_send_lock(*txid).await;
383383
}
384384
}
385385

dash-spv/src/sync/mempool/sync_manager.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl<W: WalletInterface + 'static> SyncManager for MempoolManager<W> {
127127
// shared `SyncManager::run` loop or a `WalletEvent` bridge — complexity
128128
// that isn't justified given the 100ms tick latency is negligible for bloom
129129
// filter rebuilds and the read lock is non-contending.
130-
let current_revision = self.wallet.read().await.monitor_revision();
130+
let current_revision = self.wallet.read().await.monitor_revision().await;
131131
if current_revision != self.last_monitor_revision {
132132
tracing::info!("Wallet monitor revision changed, rebuilding bloom filter");
133133
self.rebuild_filter(requests).await?;
@@ -621,7 +621,7 @@ mod tests {
621621

622622
let mut mock = MockWallet::new();
623623
mock.set_addresses(vec![addr.clone()]);
624-
let initial_revision = mock.monitor_revision();
624+
let initial_revision = mock.monitor_revision().await;
625625
let wallet = Arc::new(RwLock::new(mock));
626626
let mempool_state = Arc::new(RwLock::new(MempoolState::default()));
627627
let (tx, mut rx) = mpsc::unbounded_channel::<NetworkRequest>();
@@ -734,7 +734,7 @@ mod tests {
734734

735735
let mut mock = MockWallet::new();
736736
mock.set_addresses(vec![addr]);
737-
let initial_revision = mock.monitor_revision();
737+
let initial_revision = mock.monitor_revision().await;
738738
let wallet = Arc::new(RwLock::new(mock));
739739
let mempool_state = Arc::new(RwLock::new(MempoolState::default()));
740740
let (tx, mut rx) = mpsc::unbounded_channel::<NetworkRequest>();
@@ -785,7 +785,7 @@ mod tests {
785785
]);
786786
let addr = dashcore::Address::from_script(&script, dashcore::Network::Testnet).unwrap();
787787
mock.set_addresses(vec![addr]);
788-
let initial_revision = mock.monitor_revision();
788+
let initial_revision = mock.monitor_revision().await;
789789
let wallet = Arc::new(RwLock::new(mock));
790790
let mempool_state = Arc::new(RwLock::new(MempoolState::default()));
791791
let (tx_chan, mut rx) = mpsc::unbounded_channel::<NetworkRequest>();

key-wallet-ffi/src/wallet_manager_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ mod tests {
459459
let manager_ref = &*manager;
460460
manager_ref.runtime.block_on(async {
461461
let mut manager_guard = manager_ref.manager.write().await;
462-
manager_guard.update_synced_height(new_height);
462+
manager_guard.update_synced_height(new_height).await;
463463
});
464464
}
465465

key-wallet-manager/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,15 @@ default = []
1212
parallel-filters = ["dep:rayon"]
1313
bincode = ["key-wallet/bincode", "dep:bincode"]
1414
test-utils = ["key-wallet/test-utils"]
15+
bls = ["key-wallet/bls"]
16+
eddsa = ["key-wallet/eddsa"]
1517

1618
[dependencies]
1719
key-wallet = { path = "../key-wallet", default-features = false }
1820
dashcore = { path = "../dash" }
1921
async-trait = "0.1"
2022
tokio = { version = "1", features = ["macros", "rt", "sync"] }
23+
tracing = "0.1"
2124
zeroize = { version = "1.8", features = ["derive"] }
2225
rayon = { version = "1.11", optional = true }
2326
bincode = { version = "2.0.1", optional = true }

key-wallet-manager/examples/wallet_creation.rs

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,29 @@
88
use key_wallet::account::StandardAccountType;
99
use key_wallet::wallet::initialization::WalletAccountCreationOptions;
1010
use key_wallet::wallet::managed_wallet_info::transaction_building::AccountTypePreference;
11-
use key_wallet::wallet::managed_wallet_info::ManagedWalletInfo;
1211
use key_wallet::{AccountType, Network};
13-
use key_wallet_manager::WalletInterface;
14-
use key_wallet_manager::WalletManager;
12+
use key_wallet_manager::{ManagedWalletState, WalletInterface, WalletManager};
1513

16-
fn main() {
14+
#[tokio::main]
15+
async fn main() {
1716
println!("=== Wallet Creation Example ===\n");
1817

1918
// Example 1: Basic wallet creation with WalletManager
2019
println!("1. Creating a basic wallet with WalletManager...");
2120

22-
let mut manager = WalletManager::<ManagedWalletInfo>::new(Network::Testnet);
21+
let mut manager = WalletManager::<ManagedWalletState>::new(Network::Testnet);
2322

2423
let result = manager.create_wallet_with_random_mnemonic(WalletAccountCreationOptions::Default);
2524

2625
let wallet_id = match result {
2726
Ok(wallet_id) => {
28-
println!("Wallet created successfully!");
27+
println!("Wallet created successfully!");
2928
println!(" Wallet ID: {}", hex::encode(wallet_id));
3029
println!(" Total wallets: {}", manager.wallet_count());
3130
wallet_id
3231
}
3332
Err(e) => {
34-
println!("Failed to create wallet: {:?}", e);
33+
println!("Failed to create wallet: {:?}", e);
3534
return;
3635
}
3736
};
@@ -50,12 +49,12 @@ fn main() {
5049

5150
let wallet_id2 = match result {
5251
Ok(wallet_id2) => {
53-
println!("Wallet created from mnemonic!");
52+
println!("Wallet created from mnemonic!");
5453
println!(" Wallet ID: {}", hex::encode(wallet_id2));
5554
wallet_id2
5655
}
5756
Err(e) => {
58-
println!("Failed to create wallet from mnemonic: {:?}", e);
57+
println!("Failed to create wallet from mnemonic: {:?}", e);
5958
return;
6059
}
6160
};
@@ -64,26 +63,28 @@ fn main() {
6463
println!("\n3. Managing wallet accounts...");
6564

6665
// Add a new account to the first wallet
67-
let account_result = manager.create_account(
68-
&wallet_id, // Account index 1 (0 is created by default)
69-
AccountType::Standard {
70-
index: 1,
71-
standard_account_type: StandardAccountType::BIP44Account,
72-
},
73-
None,
74-
);
66+
let account_result = manager
67+
.create_account(
68+
&wallet_id, // Account index 1 (0 is created by default)
69+
AccountType::Standard {
70+
index: 1,
71+
standard_account_type: StandardAccountType::BIP44Account,
72+
},
73+
None,
74+
)
75+
.await;
7576

7677
match account_result {
7778
Ok(_) => {
78-
println!("Account created successfully!");
79+
println!("Account created successfully!");
7980

8081
// Get all accounts
81-
if let Ok(accounts) = manager.get_accounts(&wallet_id) {
82+
if let Ok(accounts) = manager.get_accounts(&wallet_id).await {
8283
println!(" Total accounts: {}", accounts.len());
8384
}
8485
}
8586
Err(e) => {
86-
println!("Failed to create account: {:?}", e);
87+
println!("Failed to create account: {:?}", e);
8788
}
8889
}
8990

@@ -92,26 +93,28 @@ fn main() {
9293

9394
// Note: This might fail with InvalidNetwork error if the account collection
9495
// isn't properly initialized in the managed wallet info
95-
let address_result = manager.get_receive_address(
96-
&wallet_id,
97-
0, // Account index
98-
AccountTypePreference::BIP44,
99-
false, // Don't advance index
100-
);
96+
let address_result = manager
97+
.get_receive_address(
98+
&wallet_id,
99+
0, // Account index
100+
AccountTypePreference::BIP44,
101+
false, // Don't advance index
102+
)
103+
.await;
101104

102105
match address_result {
103106
Ok(result) => {
104107
if let Some(address) = result.address {
105-
println!("Receive address: {}", address);
108+
println!("Receive address: {}", address);
106109
if let Some(account_type) = result.account_type_used {
107110
println!(" Account type used: {:?}", account_type);
108111
}
109112
} else {
110-
println!("⚠️ No address generated");
113+
println!("No address generated");
111114
}
112115
}
113116
Err(e) => {
114-
println!("⚠️ Could not get address: {:?}", e);
117+
println!("Could not get address: {:?}", e);
115118
println!(" (This is expected with the current implementation)");
116119
}
117120
}
@@ -125,8 +128,8 @@ fn main() {
125128
// Example 6: Getting wallet balance
126129
println!("\n6. Checking wallet balances...");
127130

128-
for (i, wallet_id) in [wallet_id, wallet_id2].iter().enumerate() {
129-
match manager.get_wallet_balance(wallet_id) {
131+
for (i, wid) in [wallet_id, wallet_id2].iter().enumerate() {
132+
match manager.get_wallet_balance(wid).await {
130133
Ok(balance) => {
131134
println!(" Wallet {}: {} satoshis", i + 1, balance.total());
132135
}
@@ -142,13 +145,13 @@ fn main() {
142145
// Example 7: Block height tracking
143146
println!("\n7. Block height tracking...");
144147

145-
println!(" Current height (Testnet): {:?}", manager.synced_height());
148+
println!(" Current height (Testnet): {:?}", WalletInterface::synced_height(&manager));
146149

147150
// Update height
148-
manager.update_synced_height(850_000);
149-
println!(" Updated height to: {:?}", manager.synced_height());
151+
WalletInterface::update_synced_height(&mut manager, 850_000).await;
152+
println!(" Updated height to: {:?}", WalletInterface::synced_height(&manager));
150153

151154
println!("\n=== Summary ===");
152155
println!("Total wallets created: {}", manager.wallet_count());
153-
println!("Example completed successfully!");
156+
println!("Example completed successfully!");
154157
}

0 commit comments

Comments
 (0)