fix(storage): acquire wallet lock before load and before CloseWallet

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.
This commit is contained in:
2026-06-13 20:50:17 +02:00
parent 008e9c395a
commit 58645bd7a7
2 changed files with 30 additions and 23 deletions
+27 -21
View File
@@ -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
/// <summary>Apertura di un file wallet qualunque (menu File → Apri, multi-wallet §8).</summary>
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;
+3 -2
View File
@@ -16,10 +16,11 @@ public static class CoinAmount
/// <summary>(satoshi per unità, decimali mostrati) di ciascuna unità.</summary>
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 = "") =>