From d9911a9bc92aabb7f08f3006e5355cb6dd861529 Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 15 Jul 2026 23:40:33 -0500 Subject: [PATCH] feat(wallet): detect a corrupt target wallet on switch + offer -salvagewallet repair MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a switch fails, the failure modal now distinguishes a CORRUPT target wallet from other failures and offers a one-click repair: - The switch worker watermarks the node's captured console output before the start and, if the node dies in init, scans this start's output for a corruption signature ("Failed to rename … .bak" / "salvage failed" / "wallet.dat corrupt" / "Error loading wallet") → sets switch_wallet_corrupt_. - The Failed modal then shows an accurate "this wallet appears corrupt" message (instead of the generic "Couldn't open that wallet") plus a "Try to repair (salvage)" button that retries the switch with the target node started under -salvagewallet (recovers readable keypairs; implies -rescan). - EmbeddedDaemon::setSalvageOnNextStart (one-shot, precedence salvage > zap > rescan) + controller forwarder; switchToWallet gains a salvage arg. Salvage operates only on the corrupt target (never the good wallet, no fund movement). Adversarially verified: output isolation, one-shot lifecycle, state handling, re-entrancy, no success-path regression. i18n (EN + 8 languages) + 2 new CJK glyphs baked into the subset font. Co-Authored-By: Claude Opus 4.8 (1M context) --- res/fonts/NotoSansCJK-Subset.ttf | Bin 636016 -> 636692 bytes res/lang/de.json | 2 ++ res/lang/es.json | 2 ++ res/lang/fr.json | 2 ++ res/lang/ja.json | 2 ++ res/lang/ko.json | 2 ++ res/lang/pt.json | 2 ++ res/lang/ru.json | 2 ++ res/lang/zh.json | 2 ++ src/app.cpp | 9 ++++++ src/app.h | 8 ++++-- src/app_network.cpp | 46 +++++++++++++++++++++++++------ src/daemon/daemon_controller.cpp | 5 ++++ src/daemon/daemon_controller.h | 1 + src/daemon/embedded_daemon.cpp | 13 +++++++-- src/daemon/embedded_daemon.h | 6 ++++ src/util/i18n.cpp | 2 ++ 17 files changed, 92 insertions(+), 14 deletions(-) diff --git a/res/fonts/NotoSansCJK-Subset.ttf b/res/fonts/NotoSansCJK-Subset.ttf index a4c876b7aced6828a02089939061d3148579c1b2..dacbc33067bfc1f231383410288f90bd87a3d52f 100644 GIT binary patch delta 23741 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 Z+7zn@-@_fUbEb7#E3{+8EbCp4{{!Pi|4aY? delta 23107 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 LE&ELCdFKBExkpQi diff --git a/res/lang/de.json b/res/lang/de.json index 7858a20..bb6e95e 100644 --- a/res/lang/de.json +++ b/res/lang/de.json @@ -1294,6 +1294,8 @@ "sweep_to": "Gefegt an:", "sweep_toggle": "In meine Wallet fegen (Schlüssel nicht behalten)", "sweep_tx": "Transaktion:", + "switch_corrupt_body": "Diese Wallet scheint beschädigt zu sein – der Knoten konnte sie nicht öffnen. Aus einem Backup wiederherstellen, neu erstellen oder eine Reparatur versuchen.", + "switch_corrupt_repair": "Reparatur versuchen (Salvage)", "switch_progress_background": "Im Hintergrund fortsetzen", "switch_progress_default_wallet": "Standard-Wallet", "switch_progress_elapsed": "Vergangen", diff --git a/res/lang/es.json b/res/lang/es.json index 46d924a..8a8762b 100644 --- a/res/lang/es.json +++ b/res/lang/es.json @@ -1294,6 +1294,8 @@ "sweep_to": "Barrido a:", "sweep_toggle": "Barrer a mi monedero (no conservar la clave)", "sweep_tx": "Transacción:", + "switch_corrupt_body": "Esta cartera parece dañada: el nodo no pudo abrirla. Restáurala desde una copia de seguridad, vuelve a crearla o intenta repararla.", + "switch_corrupt_repair": "Intentar reparar (salvage)", "switch_progress_background": "Continuar en segundo plano", "switch_progress_default_wallet": "Cartera predeterminada", "switch_progress_elapsed": "Transcurrido", diff --git a/res/lang/fr.json b/res/lang/fr.json index 865c409..3097e50 100644 --- a/res/lang/fr.json +++ b/res/lang/fr.json @@ -1294,6 +1294,8 @@ "sweep_to": "Balayé vers :", "sweep_toggle": "Balayer vers mon portefeuille (ne pas conserver la clé)", "sweep_tx": "Transaction :", + "switch_corrupt_body": "Ce portefeuille semble corrompu — le nœud n'a pas pu l'ouvrir. Restaurez-le depuis une sauvegarde, recréez-le ou tentez de le réparer.", + "switch_corrupt_repair": "Tenter une réparation (salvage)", "switch_progress_background": "Continuer en arrière-plan", "switch_progress_default_wallet": "Portefeuille par défaut", "switch_progress_elapsed": "Écoulé", diff --git a/res/lang/ja.json b/res/lang/ja.json index 131a97e..cd6f9ba 100644 --- a/res/lang/ja.json +++ b/res/lang/ja.json @@ -1294,6 +1294,8 @@ "sweep_to": "集約先:", "sweep_toggle": "ウォレットに集約(鍵は保持しない)", "sweep_tx": "取引:", + "switch_corrupt_body": "このウォレットは破損しているようです。ノードが開けませんでした。バックアップから復元するか、作り直すか、修復を試してください。", + "switch_corrupt_repair": "修復を試す(salvage)", "switch_progress_background": "バックグラウンドで続行", "switch_progress_default_wallet": "既定のウォレット", "switch_progress_elapsed": "経過", diff --git a/res/lang/ko.json b/res/lang/ko.json index 9d1dadb..fb3f94f 100644 --- a/res/lang/ko.json +++ b/res/lang/ko.json @@ -1294,6 +1294,8 @@ "sweep_to": "쓸어담은 주소:", "sweep_toggle": "내 지갑으로 쓸어담기 (키 보관 안 함)", "sweep_tx": "거래:", + "switch_corrupt_body": "이 지갑이 손상된 것 같습니다. 노드가 열 수 없습니다. 백업에서 복원하거나 다시 만들거나 복구를 시도하세요.", + "switch_corrupt_repair": "복구 시도(salvage)", "switch_progress_background": "백그라운드에서 계속", "switch_progress_default_wallet": "기본 지갑", "switch_progress_elapsed": "경과", diff --git a/res/lang/pt.json b/res/lang/pt.json index c0b1986..ed13eb3 100644 --- a/res/lang/pt.json +++ b/res/lang/pt.json @@ -1294,6 +1294,8 @@ "sweep_to": "Varrido para:", "sweep_toggle": "Varrer para minha carteira (não manter a chave)", "sweep_tx": "Transação:", + "switch_corrupt_body": "Esta carteira parece corrompida — o nó não conseguiu abri-la. Restaure de um backup, recrie-a ou tente repará-la.", + "switch_corrupt_repair": "Tentar reparar (salvage)", "switch_progress_background": "Continuar em segundo plano", "switch_progress_default_wallet": "Carteira padrão", "switch_progress_elapsed": "Decorrido", diff --git a/res/lang/ru.json b/res/lang/ru.json index e6ec3f2..62d0fc7 100644 --- a/res/lang/ru.json +++ b/res/lang/ru.json @@ -1294,6 +1294,8 @@ "sweep_to": "Переведено на:", "sweep_toggle": "Перевести в мой кошелёк (не сохранять ключ)", "sweep_tx": "Транзакция:", + "switch_corrupt_body": "Похоже, этот кошелёк повреждён — узел не смог его открыть. Восстановите из резервной копии, создайте заново или попробуйте восстановить.", + "switch_corrupt_repair": "Попробовать восстановить (salvage)", "switch_progress_background": "Продолжить в фоне", "switch_progress_default_wallet": "Кошелёк по умолчанию", "switch_progress_elapsed": "Прошло", diff --git a/res/lang/zh.json b/res/lang/zh.json index cbfa78a..b28ec38 100644 --- a/res/lang/zh.json +++ b/res/lang/zh.json @@ -1294,6 +1294,8 @@ "sweep_to": "归集到:", "sweep_toggle": "归集到我的钱包(不保留密钥)", "sweep_tx": "交易:", + "switch_corrupt_body": "此钱包似乎已损坏——节点无法打开它。请从备份恢复、重新创建,或尝试修复。", + "switch_corrupt_repair": "尝试修复(salvage)", "switch_progress_background": "在后台继续", "switch_progress_default_wallet": "默认钱包", "switch_progress_elapsed": "已用时", diff --git a/src/app.cpp b/src/app.cpp index 0ff27fb..2031c7a 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -3919,6 +3919,15 @@ void App::renderSwitchStopDaemonDialog() // rather than repeating the title. ui::material::DialogWarningHeader(wallet_switch_error_.c_str()); ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd())); + // If the node reported the target wallet as corrupt, offer a one-click -salvagewallet repair + // (retries the switch to that wallet with recovery enabled). + if (switch_wallet_corrupt_.load()) { + if (ui::material::TactileButton(TR("switch_corrupt_repair"), ImVec2(240.0f * dp, 0))) { + const std::string target = wallet_switch_target_file_; + switchToWallet(target, /*stopDaemonConfirmed=*/true, /*salvage=*/true); // reopens the progress modal + } + ImGui::SameLine(); + } if (ui::material::TactileButton(TR("close"), ImVec2(110.0f * dp, 0))) { wallet_switch_dialog_open_.store(false); wallet_switch_phase_.store(static_cast(WalletSwitchPhase::None)); diff --git a/src/app.h b/src/app.h index 7e7a788..58065c9 100644 --- a/src/app.h +++ b/src/app.h @@ -492,8 +492,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. // stopDaemonConfirmed: skip the "stop the running node?" confirmation (set true when re-entered from - // that dialog). The confirmation only appears when the node to restart was ADOPTED (externally running). - void switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed = false); + // that dialog). salvage: start the target node with -salvagewallet (repair a corrupt wallet). + void switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed = false, bool salvage = false); // Main-thread continuation: if a wallet switch's daemon failed to start, revert active_wallet_file. void processWalletSwitchRevert(); @@ -780,6 +780,10 @@ private: std::atomic wallet_switch_dialog_open_{false}; std::string wallet_switch_error_; double wallet_switch_started_time_ = 0.0; // ImGui::GetTime() captured on first progress frame (elapsed) + // Set when a failed switch's node output indicates the target wallet is CORRUPT — the Failed modal then + // offers a one-click "-salvagewallet" repair, retrying the switch to wallet_switch_target_file_. + std::atomic switch_wallet_corrupt_{false}; + std::string wallet_switch_target_file_; // the wallet we were switching TO (for a salvage retry) // 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. diff --git a/src/app_network.cpp b/src/app_network.cpp index 823b4c3..35ca3b0 100644 --- a/src/app_network.cpp +++ b/src/app_network.cpp @@ -189,6 +189,17 @@ static WarmupText translateWarmup(const std::string& raw) return {raw.c_str(), ""}; } +// A node told to open a wallet it can't prints one of these to its console: the BDB corrupt-wallet +// recovery ("Failed to rename … .bak", "wallet.dat corrupt, salvage failed") or a generic load error. +// Used to offer a -salvagewallet repair when a switch fails because the target wallet is corrupt. +static bool walletOutputLooksCorrupt(const std::string& out) +{ + return out.find("Failed to rename") != std::string::npos + || out.find("salvage failed") != std::string::npos + || out.find("wallet.dat corrupt") != std::string::npos + || out.find("Error loading wallet") != std::string::npos; +} + // Phrases dragonxd prints to its console while initializing, in the order translateWarmup() // understands them. The most recent matching console line tells us which stage the node is in // even when the RPC probe just times out (no -28 reply to read). @@ -1029,7 +1040,7 @@ void App::updateWalletIndexForActiveWallet(bool markOpened) if (wallet_index_.upsert(e)) wallet_index_.save(); } -void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed) +void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed, bool salvage) { if (!supportsFullNodeLifecycleActions()) { ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build"); @@ -1079,8 +1090,10 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed const std::string prevWallet = settings_->getActiveWalletFile(); // for revert if the new one fails wallet_switch_prev_file_ = prevWallet; + wallet_switch_target_file_ = walletFile; // remembered for a "salvage" retry from the failure modal wallet_switch_failed_.store(false); switch_stop_failed_.store(false); // reason latch for the revert message; a stale one must not leak + switch_wallet_corrupt_.store(false); wallet_switch_pending_confirm_.store(true); // cleared on a successful connect (onConnected) settings_->setActiveWalletFile(walletFile); settings_->save(); @@ -1117,8 +1130,12 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed wallet_switch_phase_.store(static_cast(WalletSwitchPhase::Stopping)); wallet_switch_dialog_open_.store(true); - async_tasks_.submit("Switch wallet", [this, needRescan](const util::AsyncTaskManager::Token&) { + async_tasks_.submit("Switch wallet", [this, needRescan, salvage](const util::AsyncTaskManager::Token&) { bool ok = false; + // Watermark the node's captured console output so we scan only THIS start's lines for a + // corruption signature (below), not stale output from a previous daemon. + std::size_t out_off = 0; + if (daemon_controller_) daemon_controller_->outputSince(out_off); try { // Stop the node so it releases the datadir .lock + RPC port before we relaunch. For an adopted // (external) daemon this sends a graceful RPC "stop" — stopEmbeddedDaemon()'s policy would leave @@ -1137,7 +1154,8 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed // as owned (stop/isRunning/exit behave normally for the next switch and app exit). wallet_switch_phase_.store(static_cast(WalletSwitchPhase::Starting)); if (daemon_controller_) daemon_controller_->clearExternalDaemonDetected(); - if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); + if (salvage && daemon_controller_) daemon_controller_->setSalvageOnNextStart(true); // repair a corrupt wallet + else if (needRescan && daemon_controller_) daemon_controller_->setRescanOnNextStart(true); // (salvage implies rescan) // Start ONCE. Do NOT retry-spawn: a second start while the first is still shutting down leaves // two dragonxd holding wallet.dat against each other (BDB "Failed to rename … Error"). The // stopDaemonForWalletSwitch() wait already ensured the old node's process is gone, so a valid @@ -1149,6 +1167,13 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed std::this_thread::sleep_for(std::chrono::milliseconds(1500)); if (!isEmbeddedDaemonRunning()) ok = false; } + // If it died in init, scan this start's captured output for a corrupt-wallet signature so the + // failure modal can offer a repair. Give the dying node a moment to flush its final lines. + if (!ok && !shutting_down_ && daemon_controller_) { + for (int i = 0; i < 12 && !shutting_down_; ++i) std::this_thread::sleep_for(std::chrono::milliseconds(100)); + if (walletOutputLooksCorrupt(daemon_controller_->outputSince(out_off))) + switch_wallet_corrupt_.store(true); + } // Process is up and staying up — now waiting for RPC to answer (onConnected closes the modal). if (ok) wallet_switch_phase_.store(static_cast(WalletSwitchPhase::Reconnecting)); } @@ -1181,12 +1206,15 @@ void App::processWalletSwitchRevert() } // Clear the crash-wedge so the (good) previous wallet's daemon is allowed to start again. if (daemon_controller_) daemon_controller_->resetCrashCount(); - // Accurate reason: "port wouldn't free" (the running node kept the connection) vs. a genuinely bad - // target wallet. The former is the adopted-external-daemon case; the latter a daemon that exited in init. - const std::string reason = switch_stop_failed_.exchange(false) - ? std::string("Couldn't switch wallets — the running node didn't release its connection in time. " - "It's still on the previous wallet.") - : std::string("Couldn't open that wallet — reverted to the previous one."); + // Accurate reason: "port wouldn't free" (the running node kept the connection); "wallet is corrupt" + // (the node's recovery couldn't open it); else a generic bad-wallet failure. + const std::string reason = + switch_stop_failed_.exchange(false) + ? std::string("Couldn't switch wallets — the running node didn't release its connection in time. " + "It's still on the previous wallet.") + : switch_wallet_corrupt_.load() + ? std::string(TR("switch_corrupt_body")) + : std::string("Couldn't open that wallet — reverted to the previous one."); // Surface it in the progress modal if it's still up (keep it open on Failed until the user closes); // otherwise (the user chose "continue in background") fall back to a toast. if (wallet_switch_dialog_open_.load()) { diff --git a/src/daemon/daemon_controller.cpp b/src/daemon/daemon_controller.cpp index 927745d..6f27c48 100644 --- a/src/daemon/daemon_controller.cpp +++ b/src/daemon/daemon_controller.cpp @@ -121,6 +121,11 @@ void DaemonController::setZapOnNextStart(bool enabled) daemon_->setZapOnNextStart(enabled); } +void DaemonController::setSalvageOnNextStart(bool enabled) +{ + daemon_->setSalvageOnNextStart(enabled); +} + bool DaemonController::zapOnNextStart() const { return daemon_->zapOnNextStart(); diff --git a/src/daemon/daemon_controller.h b/src/daemon/daemon_controller.h index 8e67ec1..5f2fe25 100644 --- a/src/daemon/daemon_controller.h +++ b/src/daemon/daemon_controller.h @@ -107,6 +107,7 @@ public: bool rescanOnNextStart() const; void setZapOnNextStart(bool enabled); bool zapOnNextStart() const; + void setSalvageOnNextStart(bool enabled); static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon, bool externalDaemonDetected, diff --git a/src/daemon/embedded_daemon.cpp b/src/daemon/embedded_daemon.cpp index b334e99..2e67fd6 100644 --- a/src/daemon/embedded_daemon.cpp +++ b/src/daemon/embedded_daemon.cpp @@ -524,9 +524,16 @@ bool EmbeddedDaemon::start(const std::string& binary_path) args.push_back("-wallet=" + wallet_file_); } - // Add wallet-repair flag if requested (one-shot). -zapwallettxes=2 wipes all wallet tx/note - // records and rebuilds them from the chain; it implies -rescan, so don't also pass -rescan. - if (zap_on_next_start_.exchange(false)) { + // Add wallet-repair flag if requested (one-shot). Precedence: salvage > zap > rescan; each implies a + // rescan in the daemon, so we don't stack them. + if (salvage_on_next_start_.exchange(false)) { + // -salvagewallet recovers readable keypairs from a corrupt wallet.dat; the daemon then implies -rescan. + DEBUG_LOGF("[INFO] Adding -salvagewallet flag to recover a corrupt wallet\n"); + args.push_back("-salvagewallet"); + zap_on_next_start_.store(false); + rescan_on_next_start_.store(false); + } else if (zap_on_next_start_.exchange(false)) { + // -zapwallettxes=2 wipes all wallet tx/note records and rebuilds them from the chain (implies -rescan). DEBUG_LOGF("[INFO] Adding -zapwallettxes=2 flag for wallet repair (zap & rebuild)\n"); args.push_back("-zapwallettxes=2"); rescan_on_next_start_.store(false); // implied by zap; avoid redundant -rescan diff --git a/src/daemon/embedded_daemon.h b/src/daemon/embedded_daemon.h index 682ed8d..4021cab 100644 --- a/src/daemon/embedded_daemon.h +++ b/src/daemon/embedded_daemon.h @@ -201,6 +201,11 @@ public: void setZapOnNextStart(bool v) { zap_on_next_start_ = v; } bool zapOnNextStart() const { return zap_on_next_start_.load(); } + // -salvagewallet: attempt to recover keys from a corrupt wallet.dat on startup (implies -rescan in + // the daemon). One-shot, consumed on the next start. Used to repair a wallet a switch flagged corrupt. + void setSalvageOnNextStart(bool v) { salvage_on_next_start_ = v; } + bool salvageOnNextStart() const { return salvage_on_next_start_.load(); } + /** * @brief One-shot isolated-datadir override for the NEXT start(): run the daemon against a * different datadir (with its own DRAGONX.conf) plus the given extra args. Used by the @@ -274,6 +279,7 @@ private: std::atomic crash_count_{0}; // consecutive crash counter std::atomic rescan_on_next_start_{false}; // -rescan flag for next start std::atomic zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start + std::atomic salvage_on_next_start_{false}; // -salvagewallet flag for next start std::string override_datadir_; // one-shot: -datadir for the next start std::vector override_extra_args_; // one-shot: extra args for the next start bool skip_port_check_ = false; // isolated instance on a non-default port diff --git a/src/util/i18n.cpp b/src/util/i18n.cpp index 9672ac5..d93cd01 100644 --- a/src/util/i18n.cpp +++ b/src/util/i18n.cpp @@ -1057,6 +1057,8 @@ void I18n::loadBuiltinEnglish() strings_["switch_progress_elapsed"] = "Elapsed"; strings_["switch_progress_default_wallet"] = "Default wallet"; strings_["switch_progress_external_wallet"] = "External wallet"; + strings_["switch_corrupt_body"] = "This wallet appears corrupt — the node couldn't open it. Restore it from a backup, re-create it, or try to repair it."; + strings_["switch_corrupt_repair"] = "Try to repair (salvage)"; // Receive Tab strings_["receiving_addresses"] = "Your Receiving Addresses";