fix(wallet-switch): close gaps found verifying clusters A/B/C
Adversarial verification of the audit fixes found real gaps in them: - HIGH throw-safety: the switch worker and the encryption-restart worker set daemon_restarting_=true but reset it only on their normal/early-return paths — a throw from stop/startEmbeddedDaemon left the flag stuck true, wedging reconnect and every future switch/rescan/encryption. Both now reset it on all paths (try/catch); a throw during a switch is treated as a failed switch and triggers the revert. - HIGH residual chat leak: resetChatSession() cleared the flags but an already- posted z_exportmnemonic worker job still held wallet A's secret, and its completion callback (guarded only by isLocked(), false for an unencrypted wallet) would provision A's identity under B. Add a chat_session_generation_ epoch bumped on every wallet change; the fetch captures it and its callback discards the (previous-wallet) secret if the epoch no longer matches. - LOW vault-scope collision: the per-wallet vault tag was a lossy char-substitution (two distinct files could map to one vault). Append an 8-hex FNV-1a of the raw filename so distinct wallets never share a vault. +unit test. - LOW seed-adopt: removeVault() on adopt so the legacy wallet's PIN passphrase isn't left associated with the new seed wallet (same file name). - LOW: clear lock_unlock_in_progress_ on switch too. Deferred (documented): the 1.5s start grace can't catch a wallet that fails LATE in daemon init (the existing crash-wedge detection still applies); beginShutdown's join of the switch task can briefly freeze the UI during quit (necessary to avoid orphaning the daemon). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -247,12 +247,16 @@ void App::restartDaemonAfterEncryption(const char* taskName, bool announceRestar
|
||||
// Give daemon a moment to shut down, then restart
|
||||
// (do this off the main thread to avoid stalling the UI)
|
||||
async_tasks_.submit(taskName, [this](const util::AsyncTaskManager::Token& token) {
|
||||
for (int i = 0; i < 20 && !token.cancelled() && !shutting_down_; ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (token.cancelled() || shutting_down_) { daemon_restarting_ = false; return; }
|
||||
stopEmbeddedDaemon();
|
||||
if (token.cancelled() || shutting_down_) { daemon_restarting_ = false; return; }
|
||||
startEmbeddedDaemon();
|
||||
// daemon_restarting_ MUST be cleared on every exit (incl. an early-out or a throw), else it
|
||||
// stays stuck true and wedges reconnect + all future switch/rescan/encryption operations.
|
||||
try {
|
||||
for (int i = 0; i < 20 && !token.cancelled() && !shutting_down_; ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (!token.cancelled() && !shutting_down_) {
|
||||
stopEmbeddedDaemon();
|
||||
if (!token.cancelled() && !shutting_down_) startEmbeddedDaemon();
|
||||
}
|
||||
} catch (...) {}
|
||||
daemon_restarting_ = false; // re-arm reconnect (tryConnect runs from the update loop)
|
||||
});
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user