From 7e83425f57cbef1b4202a1f8d696f1bec83f42c5 Mon Sep 17 00:00:00 2001 From: sneurlax Date: Mon, 2 Nov 2026 23:22:16 -0600 Subject: [PATCH] Release the cryptonote refresh lock on every exit refresh() in the Monero, Wownero and Salvium wallets acquires refreshMutex and released it only on the success path. Anything thrown after the acquire (the Tor and clearnet mismatch check, or wallet being null because open() failed) left the lock held for the rest of the session. Later refreshes returned early on the isLocked check and recover()'s refreshMutex.protect waited forever, which is the stuck rescan dialog in stack-wallet #103. The body now runs under refreshMutex.protect, which releases in a finally and rethrows, the same way the other wallets and recover() in these files already use it. That makes the release-at-100% workaround in onSyncingUpdate redundant, so it is gone from all three wallets. In the Monero wallet highestPercentCached is reset on both sides of rescanBlockchain: before it, so a throw leaves the guard at zero, and after it, so a progress reading from before the rescan cannot be ordered after the reset. Wownero and Salvium call rescanBlockchain unawaited, so the second reset would change nothing there and they keep their order. This is the wallet-layer part of the closed PR #1490, taken on its own. --- .../intermediate/lib_monero_wallet.dart | 75 ++++++++----------- .../intermediate/lib_salvium_wallet.dart | 26 +++---- .../intermediate/lib_wownero_wallet.dart | 26 +++---- 3 files changed, 50 insertions(+), 77 deletions(-) diff --git a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart index afc68c318c..280b2cdca1 100644 --- a/lib/wallets/wallet/intermediate/lib_monero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_monero_wallet.dart @@ -394,6 +394,10 @@ abstract class LibMoneroWallet highestPercentCached = 0; await csMonero.rescanBlockchain(wallet!); + // Reset again once the worker has accepted the rescan. A progress + // reading from before it can land in between and pin the guard near + // 100%, which hides every later reading until the rescan passes it. + highestPercentCached = 0; await csMonero.startSyncing(wallet!); unawaited(save()); }); @@ -881,10 +885,6 @@ abstract class LibMoneroWallet final _syncStatus = syncStatus; if (_syncStatus != null) { - if (_syncStatus.progress() == 1 && refreshMutex.isLocked) { - refreshMutex.release(); - } - WalletSyncStatus? status; xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true); @@ -1119,51 +1119,40 @@ abstract class LibMoneroWallet Logging.instance.t( "$runtimeType refresh() refreshMutex.acquire() waiting...", ); - await refreshMutex.acquire(); - Logging.instance.t( - "$runtimeType refresh() refreshMutex.acquire() acquired!", - ); - - Logging.instance.t("$runtimeType refresh() final node = getCurrentNode();"); - final node = getCurrentNode(); - - Logging.instance.i( - "$runtimeType refresh() await _torNodeMismatchGuard(node)", - ); - if (await _torNodeMismatchGuard(node)) { - throw Exception("TOR – clearnet mismatch"); - } + await refreshMutex.protect(() async { + Logging.instance.t( + "$runtimeType refresh() final node = getCurrentNode();", + ); + final node = getCurrentNode(); - Logging.instance.t( - "$runtimeType refresh() it csMonero.startSyncing(wallet!);", - ); - await csMonero.startSyncing(wallet!); - Logging.instance.t( - "$runtimeType refresh() _setSyncStatus(lib_monero_compat.StartingSyncStatus());", - ); - _setSyncStatus(lib_monero_compat.StartingSyncStatus()); + Logging.instance.i( + "$runtimeType refresh() await _torNodeMismatchGuard(node)", + ); + if (await _torNodeMismatchGuard(node)) { + throw Exception("TOR – clearnet mismatch"); + } - Logging.instance.t("$runtimeType refresh() await updateTransactions();"); - await updateTransactions(); - Logging.instance.t("$runtimeType refresh() await updateBalance();"); - await updateBalance(); + Logging.instance.t( + "$runtimeType refresh() it csMonero.startSyncing(wallet!);", + ); + await csMonero.startSyncing(wallet!); + Logging.instance.t( + "$runtimeType refresh() _setSyncStatus(lib_monero_compat.StartingSyncStatus());", + ); + _setSyncStatus(lib_monero_compat.StartingSyncStatus()); - Logging.instance.t( - "$runtimeType refresh() await checkReceivingAddressForTransactions();", - ); - if (info.otherData[WalletInfoKeys.reuseAddress] != true) { - await checkReceivingAddressForTransactions(); - } + Logging.instance.t("$runtimeType refresh() await updateTransactions();"); + await updateTransactions(); + Logging.instance.t("$runtimeType refresh() await updateBalance();"); + await updateBalance(); - Logging.instance.t( - "$runtimeType refresh() refreshMutex.isLocked=${refreshMutex.isLocked} pre release.", - ); - if (refreshMutex.isLocked) { - refreshMutex.release(); Logging.instance.t( - "$runtimeType refresh() refreshMutex.isLocked manually released.", + "$runtimeType refresh() await checkReceivingAddressForTransactions();", ); - } + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await checkReceivingAddressForTransactions(); + } + }); Logging.instance.t( "$runtimeType refresh() wallet != null && await csMonero.isSynced(wallet!)", diff --git a/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart b/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart index 0d4c56bf18..e3bf79da11 100644 --- a/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_salvium_wallet.dart @@ -862,10 +862,6 @@ abstract class LibSalviumWallet final _syncStatus = syncStatus; if (_syncStatus != null) { - if (_syncStatus.progress() == 1 && refreshMutex.isLocked) { - refreshMutex.release(); - } - WalletSyncStatus? status; xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true); @@ -1099,21 +1095,17 @@ abstract class LibSalviumWallet // this acquire should be almost instant due to above check. // Slight possibility of race but should be irrelevant - await refreshMutex.acquire(); - - csSalvium.startSyncing(wallet!); - _setSyncStatus(StartingSyncStatus()); - - await updateTransactions(); - await updateBalance(); + await refreshMutex.protect(() async { + csSalvium.startSyncing(wallet!); + _setSyncStatus(StartingSyncStatus()); - if (info.otherData[WalletInfoKeys.reuseAddress] != true) { - await checkReceivingAddressForTransactions(); - } + await updateTransactions(); + await updateBalance(); - if (refreshMutex.isLocked) { - refreshMutex.release(); - } + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await checkReceivingAddressForTransactions(); + } + }); final synced = wallet != null && await csSalvium.isSynced(wallet!); diff --git a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart index e568b2656a..079db7b10e 100644 --- a/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart +++ b/lib/wallets/wallet/intermediate/lib_wownero_wallet.dart @@ -898,10 +898,6 @@ abstract class LibWowneroWallet final _syncStatus = syncStatus; if (_syncStatus != null) { - if (_syncStatus.progress() == 1 && refreshMutex.isLocked) { - refreshMutex.release(); - } - WalletSyncStatus? status; xmrAndWowSyncSpecificFunctionThatShouldBeGottenRidOfInTheFuture(true); @@ -1136,21 +1132,17 @@ abstract class LibWowneroWallet // this acquire should be almost instant due to above check. // Slight possibility of race but should be irrelevant - await refreshMutex.acquire(); - - csWownero.startSyncing(wallet!); - _setSyncStatus(lib_monero_compat.StartingSyncStatus()); - - await updateTransactions(); - await updateBalance(); + await refreshMutex.protect(() async { + csWownero.startSyncing(wallet!); + _setSyncStatus(lib_monero_compat.StartingSyncStatus()); - if (info.otherData[WalletInfoKeys.reuseAddress] != true) { - await checkReceivingAddressForTransactions(); - } + await updateTransactions(); + await updateBalance(); - if (refreshMutex.isLocked) { - refreshMutex.release(); - } + if (info.otherData[WalletInfoKeys.reuseAddress] != true) { + await checkReceivingAddressForTransactions(); + } + }); final synced = wallet != null && await csWownero.isSynced(wallet!);