Skip to content

Commit 3201b31

Browse files
shumkovclaude
andcommitted
feat(context): re-key AppContext.wallets map from seed_hash to wallet_id
Commit 4 of the seed_hash → wallet_id refactor. This is the behavioral change — the wallets map key is now wallet_id bytes instead of seed_hash bytes. AppContext::new (DB load): Map key = wallet.wallet_id() if populated (v40+ wallets that have been opened), else wallet.seed_hash() as fallback for locked wallets that haven't been unlocked since v40. register_wallet: Uses wallet.wallet_id().unwrap_or(seed_hash) — newly created wallets always have wallet_id (seed is open at creation time). register_with_platform_wallet_manager: After computing wallet_id from the seed, re-keys the map entry from seed_hash to wallet_id if the wallet was inserted before wallet_id was available (password-protected wallet's first unlock since v40). Uses write lock + remove + insert pattern. Also sets managed.wallet_id = Some(wallet_id) (real wallet_id bytes, no longer seed_hash as transition placeholder). handle_wallet_locked: Uses wallet.wallet_id() for identification (with seed_hash fallback). Does NOT remove the map entry — locked wallets stay visible in the UI. get_platform_wallet / require_platform_wallet: Parameter renamed seed_hash → wallet_key for clarity. The underlying logic is unchanged (map lookup by key). Post this commit, code that passes wallet_id bytes to get_platform_wallet will find the correct entry. Code that still passes seed_hash bytes will fail for wallets that have been re-keyed — but all identity/contact discovery code flows through platform-wallet which populates wallet_id bytes, so post-v40 (all identities re-discovered) the lookup values are correct. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 4a8b2a3 commit 3201b31

File tree

2 files changed

+51
-22
lines changed

2 files changed

+51
-22
lines changed

src/context/mod.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,13 @@ impl AppContext {
325325
}
326326
}
327327
.into_iter()
328-
.map(|w| (w.seed_hash(), Arc::new(RwLock::new(w))))
328+
.map(|w| {
329+
// Use wallet_id as the map key when available (v40+ wallets
330+
// that have been opened at least once). Fall back to
331+
// seed_hash for wallets that haven't been unlocked since v40.
332+
let key = w.wallet_id().unwrap_or_else(|| w.seed_hash());
333+
(key, Arc::new(RwLock::new(w)))
334+
})
329335
.collect();
330336

331337
let single_key_wallets: BTreeMap<_, _> = match db.get_single_key_wallets(network) {

src/context/wallet_lifecycle.rs

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -63,17 +63,18 @@ impl AppContext {
6363
Ok(())
6464
}
6565

66-
/// Get a `PlatformWallet` by `WalletId`.
66+
/// Get a `PlatformWallet` by wallet map key.
6767
///
68-
/// Returns `None` if the wallet doesn't exist or is locked (no platform_wallet).
68+
/// The map is keyed by `WalletId` (post-v40). Returns `None` if the
69+
/// wallet doesn't exist or is locked (no platform_wallet).
6970
pub(crate) fn get_platform_wallet(
7071
&self,
71-
seed_hash: &WalletId,
72+
wallet_key: &WalletId,
7273
) -> Option<Arc<PlatformWallet>> {
7374
self.wallets
7475
.read()
7576
.ok()
76-
.and_then(|wallets| wallets.get(seed_hash).cloned())
77+
.and_then(|wallets| wallets.get(wallet_key).cloned())
7778
.and_then(|w| w.read().ok().and_then(|g| g.platform_wallet.clone()))
7879
}
7980

@@ -90,14 +91,14 @@ impl AppContext {
9091
})
9192
}
9293

93-
/// Get a `PlatformWallet` by seed hash, or return `TaskError::WalletNotFound`.
94+
/// Get a `PlatformWallet` by wallet map key, or return `TaskError::WalletNotFound`.
9495
///
9596
/// Convenience wrapper for backend tasks that need the platform wallet.
9697
pub(crate) fn require_platform_wallet(
9798
&self,
98-
seed_hash: &WalletId,
99+
wallet_key: &WalletId,
99100
) -> Result<Arc<PlatformWallet>, TaskError> {
100-
self.get_platform_wallet(seed_hash)
101+
self.get_platform_wallet(wallet_key)
101102
.ok_or(TaskError::WalletNotFound)
102103
}
103104

@@ -225,19 +226,23 @@ impl AppContext {
225226
}
226227
})?;
227228

228-
let seed_hash = wallet.seed_hash();
229+
// Use wallet_id as the map key (wallet_id is always available
230+
// for newly-created wallets because the seed is open).
231+
let map_key = wallet
232+
.wallet_id()
233+
.unwrap_or_else(|| wallet.seed_hash());
229234

230235
// 2. Register in-memory
231236
let wallet_arc = Arc::new(RwLock::new(wallet));
232237
let mut wallets = self.wallets.write()?;
233-
wallets.insert(seed_hash, wallet_arc.clone());
238+
wallets.insert(map_key, wallet_arc.clone());
234239
self.has_wallet.store(true, Ordering::Relaxed);
235240
drop(wallets);
236241

237242
// 3. Create PlatformWallet and load into SPV
238243
self.handle_wallet_unlocked(&wallet_arc);
239244

240-
Ok((seed_hash, wallet_arc))
245+
Ok((map_key, wallet_arc))
241246
}
242247

243248
pub fn bootstrap_wallet_addresses(&self, _wallet: &Arc<RwLock<Wallet>>) {
@@ -331,13 +336,24 @@ impl AppContext {
331336
);
332337
}
333338

334-
// Store on the Wallet struct itself
335-
if let Ok(wallets) = self.wallets.read() {
336-
if let Some(wallet_arc) = wallets.get(&seed_hash) {
339+
// Store platform_wallet + wallet_id on the Wallet struct,
340+
// and re-key the map entry from seed_hash to wallet_id if
341+
// it was inserted before wallet_id was computed (password-
342+
// protected wallets unlocked for the first time since v40).
343+
if let Ok(mut wallets) = self.wallets.write() {
344+
// Try wallet_id first (normal case), then seed_hash
345+
// (first-unlock-since-v40 case).
346+
let wallet_arc = wallets
347+
.get(&wallet_id)
348+
.cloned()
349+
.or_else(|| wallets.remove(&seed_hash));
350+
if let Some(wallet_arc) = wallet_arc {
337351
if let Ok(mut wallet) = wallet_arc.write() {
338352
wallet.platform_wallet = Some(Arc::clone(&platform_wallet));
339353
wallet.wallet_id = Some(wallet_id);
340354
}
355+
// Ensure the entry is keyed by wallet_id.
356+
wallets.insert(wallet_id, wallet_arc);
341357
}
342358
}
343359

@@ -358,8 +374,11 @@ impl AppContext {
358374
}
359375

360376
pub fn handle_wallet_locked(self: &Arc<Self>, wallet: &Arc<RwLock<Wallet>>) {
361-
let seed_hash = match wallet.read() {
362-
Ok(guard) => guard.seed_hash(),
377+
let (map_key, seed_hash) = match wallet.read() {
378+
Ok(guard) => {
379+
let key = guard.wallet_id().unwrap_or_else(|| guard.seed_hash());
380+
(key, guard.seed_hash())
381+
}
363382
Err(err) => {
364383
tracing::warn!(error = %err, "Unable to read wallet during lock handling");
365384
return;
@@ -371,9 +390,17 @@ impl AppContext {
371390
guard.platform_wallet = None;
372391
}
373392

393+
// Remove from wallet_id_mapping. The mapping is keyed by
394+
// seed_hash (the "old" key side of the bridge).
374395
if let Ok(mut mapping) = self.wallet_id_mapping.lock() {
375396
mapping.remove_by_seed_hash(&seed_hash);
376397
}
398+
399+
// Note: we do NOT remove the wallet from the AppContext.wallets
400+
// map here — locking a wallet keeps it visible in the UI, just
401+
// without platform_wallet access. The map entry stays at its
402+
// current key (wallet_id or seed_hash fallback).
403+
let _ = map_key; // suppress unused warning
377404
}
378405

379406
/// Queue async SyncNotes -> CheckNullifiers for an already-initialized
@@ -928,11 +955,7 @@ impl AppContext {
928955
managed.key_storage = mi_key_storage;
929956
managed.dpns_names = mi_dpns_names;
930957
managed.status = mi_status;
931-
// Note: `managed.wallet_id` receives `seed_hash` bytes during
932-
// the M2 transition period. Evo-tool's wallet maps are still
933-
// keyed by seed_hash; the M2 refactor commits 4-7 will switch
934-
// the value to the real wallet_id and re-key the maps.
935-
managed.wallet_id = Some(seed_hash);
958+
managed.wallet_id = Some(wallet_id);
936959
managed.top_ups = qualified_identity.top_ups.clone();
937960
managed.dashpay_profile = mi_dashpay_profile;
938961
managed.dashpay_payments = mi_dashpay_payments;
@@ -963,7 +986,7 @@ impl AppContext {
963986
managed.key_storage = mi_key_storage;
964987
managed.dpns_names = mi_dpns_names;
965988
managed.status = mi_status;
966-
managed.wallet_id = Some(seed_hash); // M2: seed_hash bytes during transition
989+
managed.wallet_id = Some(wallet_id);
967990
managed.top_ups = qualified_identity.top_ups.clone();
968991
managed.dashpay_profile = mi_dashpay_profile;
969992
managed.dashpay_payments = mi_dashpay_payments;

0 commit comments

Comments
 (0)