What changed, and why it matters
This commit changes how the Cake Wallet app tracks Zcash (ZEC) balance data and migration readiness. It adds a lock (mutex) around database access to prevent multiple operations from interfering with each other, and replaces a live balance-checking reaction with a simpler computed property. The changes appear aimed at fixing race-condition or consistency bugs rather than introducing a security vulnerability.
Treat as a routine bug-fix/refactor commit. Reviewers may want to verify that the mutex now covers all database open/setAccount operations and that `_orchardMigratable` is reliably updated on every balance change. No immediate security response is indicated by the diff alone.
Security signals we found
Mutex acquisition moved earlier in `runWithCoin`, which may fix a race condition in database access
Async network-dependent balance check replaced with cached synchronous observable
Removal of reactive side-effect in dashboard view model reduces risk of inconsistent UI state
No new input handling, parsing, cryptography, or network calls added
Evidence from the diff
The patch modifies Zcash wallet handling in Cake Wallet. Key changes: (1) runWithCoin now acquires runWithCoinMutex before opening the database, instead of after, tightening synchronization around coin operations. (2) hasOrchardMigratableBalance is changed from an async method that queried network state and recalculated balances into a synchronous getter returning a cached _orchardMigratable observable. (3) The dashboard view model removes a MobX reaction that repeatedly called the async balance check and instead exposes isMigratingToIronwood as a @computed value. (4) _orchardMigratable is updated inside a runInAction block during balance updates. These are defensive/refactoring changes; no obvious exploitable weakness is introduced.
Changed components
cw_zcash/lib/src/zcash_wallet.dartlib/view_model/dashboard/dashboard_view_model.dartlib/zcash/cw_zcash.darttool/configure.dartInspect captured patch +19 / −47
diff --git a/cw_zcash/lib/src/zcash_wallet.dart b/cw_zcash/lib/src/zcash_wallet.dart
index 14cdf8f6..0a6e192d 100644
--- a/cw_zcash/lib/src/zcash_wallet.dart
+++ b/cw_zcash/lib/src/zcash_wallet.dart
@@ -81,6 +81,9 @@ abstract class ZcashWalletBase
CryptoCurrency.zec: ZcashBalance.zero(),
});
+ @observable
+ bool _orchardMigratable = false;
+
static const int _autoShieldMinSweep = 30000;
// zkool's migrate::MIN_SD.
@@ -1251,22 +1254,7 @@ abstract class ZcashWalletBase
return migratable;
}
- Future<bool> hasOrchardMigratableBalance() async {
-
- final (active, migratableOrchard) = await runWithCoin(
- accountId: accountId,
- func: (final coin) async => (
- await zkool_network.isIronwoodActive(c: coin),
- await _migratableOrchardTotal(coin),
- ),
- );
-
- if(!active) {
- return false;
- }
-
- return migratableOrchard > BigInt.zero;
- }
+ bool hasOrchardMigratableBalance() => _orchardMigratable;
Future<void> _$autoShield() async {
if (syncStatus is! SyncedSyncStatus) {
@@ -1449,11 +1437,15 @@ abstract class ZcashWalletBase
unavailableAmount = sweepable <= BigInt.from(minSweep) ? BigInt.zero : sweepable;
}
- balance[currency] = ZcashBalance(
- Money(availableAmount, currency),
- Money(unavailableAmount, currency),
- frozen: Money.zero(currency),
- );
+ runInAction(() {
+ _orchardMigratable =
+ ironwoodActive == true && migratableOrchard > BigInt.zero;
+ balance[currency] = ZcashBalance(
+ Money(availableAmount, currency),
+ Money(unavailableAmount, currency),
+ frozen: Money.zero(currency),
+ );
+ });
} catch (e, stackTrace) {
printV("Balance update error: $e");
printV("Stack trace: $stackTrace");
@@ -1784,6 +1776,7 @@ abstract class ZcashWalletBase
required final int accountId,
required final FutureOr<T> Function(zkool_coin.Coin c) func,
}) async {
+ await runWithCoinMutex.acquire();
var newC = zkool_coin.Coin();
newC = await newC.openDatabase(dbFilepath: c.dbFilepath);
newC = await newC.setAccount(account: accountId);
@@ -1792,7 +1785,6 @@ abstract class ZcashWalletBase
runWithCoinCount++;
printV("run with coin: $runWithCoinCount");
- await runWithCoinMutex.acquire();
try {
newC = await newC.setAccount(account: accountId);
return await func(newC);
diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart
index e74771b1..994121a9 100644
--- a/lib/view_model/dashboard/dashboard_view_model.dart
+++ b/lib/view_model/dashboard/dashboard_view_model.dart
@@ -96,7 +96,6 @@ abstract class DashboardViewModelBase with Store {
isShowFirstYatIntroduction = false,
isShowSecondYatIntroduction = false,
isShowThirdYatIntroduction = false,
- isMigratingToIronwood = false,
filterItems = [],
exchangeFilterItems = [],
subname = '',
@@ -251,8 +250,6 @@ abstract class DashboardViewModelBase with Store {
reaction((_) => tradesStore.trades, (_) => tradeMonitor.monitorActiveTrades(wallet.id));
- addZcashMigrationReaction();
-
tradeMonitor.monitorActiveTrades(wallet.id);
}
@@ -377,21 +374,9 @@ abstract class DashboardViewModelBase with Store {
);
}
- @observable
- ReactionDisposer? zcashMigrationReactionDisposer;
-
- @action
- void addZcashMigrationReaction() {
- zcashMigrationReactionDisposer?.reaction.dispose();
- zcashMigrationReactionDisposer = null;
-
- if (wallet.type == WalletType.zcash) {
- zcashMigrationReactionDisposer = reaction((_) => wallet.balance.values.first, (_) async {
- isMigratingToIronwood =
- wallet.type == WalletType.zcash && await zcash!.hasOrchardMigratableBalance(wallet);
- });
- }
- }
+ @computed
+ bool get isMigratingToIronwood =>
+ wallet.type == WalletType.zcash && (zcash?.hasOrchardMigratableBalance(wallet) ?? false);
@computed
bool get isSyncHeavy {
@@ -1289,8 +1274,6 @@ abstract class DashboardViewModelBase with Store {
name = wallet.name;
loadFilterItems();
- addZcashMigrationReaction();
-
if (wallet.type == WalletType.monero) {
subname = monero!.getCurrentAccount(wallet).label;
@@ -1572,9 +1555,6 @@ abstract class DashboardViewModelBase with Store {
}
}
- @observable
- bool isMigratingToIronwood;
-
String getTransactionType(TransactionInfo tx) {
if (wallet.type == WalletType.bitcoin) {
if (tx.isReplaced == true) return ' (replaced)';
diff --git a/lib/zcash/cw_zcash.dart b/lib/zcash/cw_zcash.dart
index 8abef07d..525dac19 100644
--- a/lib/zcash/cw_zcash.dart
+++ b/lib/zcash/cw_zcash.dart
@@ -223,7 +223,7 @@ class CWZcash extends Zcash {
}
@override
- Future<bool> hasOrchardMigratableBalance(WalletBase wallet) {
+ bool hasOrchardMigratableBalance(WalletBase wallet) {
return (wallet as ZcashWallet).hasOrchardMigratableBalance();
}
}
diff --git a/tool/configure.dart b/tool/configure.dart
index e3f5f385..a741c202 100644
--- a/tool/configure.dart
+++ b/tool/configure.dart
@@ -1777,7 +1777,7 @@ abstract class Zcash {
bool showMissingFundsCard(WalletBase wallet);
Future<void> rescanInternalChange(WalletBase wallet);
bool ironwoodActive(WalletAddresses walletAddresses);
- Future<bool> hasOrchardMigratableBalance(WalletBase wallet);
+ bool hasOrchardMigratableBalance(WalletBase wallet);
}
""";
Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.