From 58645bd7a75f733a4abed8986de2ab6fe7c3c900 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Sat, 13 Jun 2026 20:50:17 +0200 Subject: [PATCH] fix(storage): acquire wallet lock before load and before CloseWallet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three improvements to WalletLock integration: 1. OpenExisting acquires the lock before decrypting the file — no point attempting a potentially expensive PBKDF2 derivation for a wallet already held by another instance. 2. OpenFromPath acquires the lock before CloseWallet — if the new wallet is unavailable, the current session stays intact instead of being silently destroyed. 3. OpenLoaded now receives an already-acquired WalletLock from the caller instead of acquiring it internally; TryAcquireWalletLock is removed. Callers that fail to open (wrong password, I/O error) dispose the lock in their catch blocks. Also fix CoinAmount.Of() to throw ArgumentException for unknown units instead of silently falling back to PLM. --- src/App/ViewModels/MainWindowViewModel.cs | 48 +++++++++++++---------- src/Core/Wallet/CoinAmount.cs | 5 ++- 2 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/App/ViewModels/MainWindowViewModel.cs b/src/App/ViewModels/MainWindowViewModel.cs index 4842736..e32cd33 100644 --- a/src/App/ViewModels/MainWindowViewModel.cs +++ b/src/App/ViewModels/MainWindowViewModel.cs @@ -874,7 +874,9 @@ public partial class MainWindowViewModel : ViewModelBase for (var n = 2; WalletStore.Exists(path); n++) path = Path.Combine(AppPaths.WalletsDir(Net), $"wallet-{n}.wallet.json"); WalletStore.Save(doc, path, password); - OpenLoaded(doc, account, path, password); + var newLock = WalletLock.TryAcquire(path); + if (newLock is null) { StatusMessage = Loc.Tr("msg.wallet.locked"); return; } + OpenLoaded(doc, account, path, password, newLock); } catch (Exception ex) { @@ -885,24 +887,33 @@ public partial class MainWindowViewModel : ViewModelBase [RelayCommand] private void OpenExisting() { + var path = _pendingOpenPath ?? AppPaths.DefaultWalletPath(Net); + var password = string.IsNullOrEmpty(PasswordInput) ? null : PasswordInput; + + // Il lock viene acquisito prima del caricamento: se il wallet è già aperto + // altrove, non ha senso nemmeno tentare la decifrazione. + var newLock = WalletLock.TryAcquire(path); + if (newLock is null) { StatusMessage = Loc.Tr("msg.wallet.locked"); return; } + try { - var path = _pendingOpenPath ?? AppPaths.DefaultWalletPath(Net); - var password = string.IsNullOrEmpty(PasswordInput) ? null : PasswordInput; var doc = WalletStore.Load(path, password); _pendingOpenPath = null; - OpenLoaded(doc, WalletLoader.ToAccount(doc), path, password); + OpenLoaded(doc, WalletLoader.ToAccount(doc), path, password, newLock); } catch (WrongPasswordException) { + newLock.Dispose(); StatusMessage = Loc.Tr("msg.wrongpassword"); } catch (UnauthorizedAccessException) { + newLock.Dispose(); StatusMessage = Loc.Tr("msg.wallet.noaccess"); } catch (Exception ex) { + newLock.Dispose(); StatusMessage = $"Errore: {ex.Message}"; } } @@ -910,16 +921,22 @@ public partial class MainWindowViewModel : ViewModelBase /// Apertura di un file wallet qualunque (menu File → Apri, multi-wallet §8). public void OpenFromPath(string path) { + // Il lock viene acquisito prima di chiudere l'eventuale wallet aperto: + // se il nuovo wallet non è disponibile, la sessione corrente resta intatta. + var newLock = WalletLock.TryAcquire(path); + if (newLock is null) { StatusMessage = Loc.Tr("msg.wallet.locked"); return; } + try { + var doc = WalletStore.Load(path); if (IsWalletOpen) CloseWallet(); - var doc = WalletStore.Load(path); - OpenLoaded(doc, WalletLoader.ToAccount(doc), path, password: null); + OpenLoaded(doc, WalletLoader.ToAccount(doc), path, password: null, newLock); } catch (WrongPasswordException) { // Cifrato: si chiede la password nel passo di apertura del wizard. + newLock.Dispose(); if (IsWalletOpen) CloseWallet(); _pendingOpenPath = path; @@ -930,10 +947,12 @@ public partial class MainWindowViewModel : ViewModelBase } catch (UnauthorizedAccessException) { + newLock.Dispose(); StatusMessage = Loc.Tr("msg.wallet.noaccess"); } catch (Exception ex) { + newLock.Dispose(); StatusMessage = $"Errore: {ex.Message}"; } } @@ -948,23 +967,10 @@ public partial class MainWindowViewModel : ViewModelBase StatusMessage = ""; } - private bool TryAcquireWalletLock(string path) + private void OpenLoaded(WalletDocument doc, HdAccount account, string path, string? password, WalletLock walletLock) { - var acquired = WalletLock.TryAcquire(path); - if (acquired is null) - { - StatusMessage = Loc.Tr("msg.wallet.locked"); - return false; - } _walletLock?.Dispose(); - _walletLock = acquired; - return true; - } - - private void OpenLoaded(WalletDocument doc, HdAccount account, string path, string? password) - { - if (!TryAcquireWalletLock(path)) - return; + _walletLock = walletLock; // La rete del wallet comanda (registry, pin TLS, indirizzi). SelectedNetwork = doc.Network; diff --git a/src/Core/Wallet/CoinAmount.cs b/src/Core/Wallet/CoinAmount.cs index 8f98d3a..566b9a7 100644 --- a/src/Core/Wallet/CoinAmount.cs +++ b/src/Core/Wallet/CoinAmount.cs @@ -16,10 +16,11 @@ public static class CoinAmount /// (satoshi per unità, decimali mostrati) di ciascuna unità. private static (long Factor, int Decimals) Of(string unit) => unit switch { + "PLM" => (SatsPerCoin, 8), "mPLM" => (100_000, 5), "µPLM" => (100, 2), - "sat" => (1, 0), - _ => (SatsPerCoin, 8), // PLM + "sat" => (1, 0), + _ => throw new ArgumentException($"Unknown coin unit: {unit}", nameof(unit)), }; public static string Format(long sats, string unit = "") =>