fix(wallet-switch): cluster B — daemon-switch robustness

From the wallet-switching audit:

- Revert on failure (HIGH): switchToWallet persisted active_wallet_file before
  confirming the new daemon started, so a missing/corrupt wallet wedged across
  restarts. The switch worker now confirms dragonxd survives a grace period; on
  failure it flags the main thread (processWalletSwitchRevert), which restores
  the previous wallet file + re-scopes the vault + re-arms reconnect (settings
  writes stay on the main thread).
- Cross-guard concurrent lifecycle ops (HIGH): switchToWallet now refuses during
  a rescan/repair (state_.sync.rescanning) or seed migration; rescan/repair now
  refuse while daemon_restarting_; and restartDaemonAfterEncryption now sets
  daemon_restarting_ (which also fixes a latent reconnect-to-a-stopped-daemon
  race during the encryption restart). So the switch/adopt/restart/rescan/repair/
  encryption ops are mutually exclusive.
- Quit during switch (MED): beginShutdown now joins the "Switch wallet" task (like
  the adopt task) so quitting can't orphan a freshly-started dragonxd.
- Reset the daemon crash count on switch (LOW) so a prior crash-wedge can't block
  reconnecting to the new wallet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 12:37:11 -05:00
parent f546d3e2b1
commit fc509a9340
4 changed files with 76 additions and 5 deletions

View File

@@ -475,6 +475,8 @@ public:
// (rescan only if it was never synced in this datadir). Per-wallet data follows automatically
// via the identity-scoped caches (P1). No-op if that wallet is already active.
void switchToWallet(const std::string& walletFile);
// Main-thread continuation: if a wallet switch's daemon failed to start, revert active_wallet_file.
void processWalletSwitchRevert();
// Wallet encryption helpers
void encryptWalletWithPassphrase(const std::string& passphrase);
@@ -729,6 +731,13 @@ private:
// Daemon restart (e.g. after changing debug log categories)
std::atomic<bool> daemon_restarting_{false};
// Wallet-switch failure recovery: the switch worker sets wallet_switch_failed_ when the new
// wallet's daemon won't start/stay up; the main loop then reverts active_wallet_file to
// wallet_switch_prev_file_ (settings writes stay on the main thread) so a broken wallet isn't
// persisted across restarts. daemon_restarting_ stays set until the revert re-arms the reconnect.
std::atomic<bool> wallet_switch_failed_{false};
std::string wallet_switch_prev_file_;
// Set by the deleteBlockchainData worker (item count); the main loop surfaces a completion toast
// and resets it to -1. Atomic because the worker thread writes it and the UI thread reads/clears it.
std::atomic<int> pending_delete_result_{-1};