feat(wallet): detect a corrupt target wallet on switch + offer -salvagewallet repair
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) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "Gefegt an:",
|
"sweep_to": "Gefegt an:",
|
||||||
"sweep_toggle": "In meine Wallet fegen (Schlüssel nicht behalten)",
|
"sweep_toggle": "In meine Wallet fegen (Schlüssel nicht behalten)",
|
||||||
"sweep_tx": "Transaktion:",
|
"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_background": "Im Hintergrund fortsetzen",
|
||||||
"switch_progress_default_wallet": "Standard-Wallet",
|
"switch_progress_default_wallet": "Standard-Wallet",
|
||||||
"switch_progress_elapsed": "Vergangen",
|
"switch_progress_elapsed": "Vergangen",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "Barrido a:",
|
"sweep_to": "Barrido a:",
|
||||||
"sweep_toggle": "Barrer a mi monedero (no conservar la clave)",
|
"sweep_toggle": "Barrer a mi monedero (no conservar la clave)",
|
||||||
"sweep_tx": "Transacción:",
|
"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_background": "Continuar en segundo plano",
|
||||||
"switch_progress_default_wallet": "Cartera predeterminada",
|
"switch_progress_default_wallet": "Cartera predeterminada",
|
||||||
"switch_progress_elapsed": "Transcurrido",
|
"switch_progress_elapsed": "Transcurrido",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "Balayé vers :",
|
"sweep_to": "Balayé vers :",
|
||||||
"sweep_toggle": "Balayer vers mon portefeuille (ne pas conserver la clé)",
|
"sweep_toggle": "Balayer vers mon portefeuille (ne pas conserver la clé)",
|
||||||
"sweep_tx": "Transaction :",
|
"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_background": "Continuer en arrière-plan",
|
||||||
"switch_progress_default_wallet": "Portefeuille par défaut",
|
"switch_progress_default_wallet": "Portefeuille par défaut",
|
||||||
"switch_progress_elapsed": "Écoulé",
|
"switch_progress_elapsed": "Écoulé",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "集約先:",
|
"sweep_to": "集約先:",
|
||||||
"sweep_toggle": "ウォレットに集約(鍵は保持しない)",
|
"sweep_toggle": "ウォレットに集約(鍵は保持しない)",
|
||||||
"sweep_tx": "取引:",
|
"sweep_tx": "取引:",
|
||||||
|
"switch_corrupt_body": "このウォレットは破損しているようです。ノードが開けませんでした。バックアップから復元するか、作り直すか、修復を試してください。",
|
||||||
|
"switch_corrupt_repair": "修復を試す(salvage)",
|
||||||
"switch_progress_background": "バックグラウンドで続行",
|
"switch_progress_background": "バックグラウンドで続行",
|
||||||
"switch_progress_default_wallet": "既定のウォレット",
|
"switch_progress_default_wallet": "既定のウォレット",
|
||||||
"switch_progress_elapsed": "経過",
|
"switch_progress_elapsed": "経過",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "쓸어담은 주소:",
|
"sweep_to": "쓸어담은 주소:",
|
||||||
"sweep_toggle": "내 지갑으로 쓸어담기 (키 보관 안 함)",
|
"sweep_toggle": "내 지갑으로 쓸어담기 (키 보관 안 함)",
|
||||||
"sweep_tx": "거래:",
|
"sweep_tx": "거래:",
|
||||||
|
"switch_corrupt_body": "이 지갑이 손상된 것 같습니다. 노드가 열 수 없습니다. 백업에서 복원하거나 다시 만들거나 복구를 시도하세요.",
|
||||||
|
"switch_corrupt_repair": "복구 시도(salvage)",
|
||||||
"switch_progress_background": "백그라운드에서 계속",
|
"switch_progress_background": "백그라운드에서 계속",
|
||||||
"switch_progress_default_wallet": "기본 지갑",
|
"switch_progress_default_wallet": "기본 지갑",
|
||||||
"switch_progress_elapsed": "경과",
|
"switch_progress_elapsed": "경과",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "Varrido para:",
|
"sweep_to": "Varrido para:",
|
||||||
"sweep_toggle": "Varrer para minha carteira (não manter a chave)",
|
"sweep_toggle": "Varrer para minha carteira (não manter a chave)",
|
||||||
"sweep_tx": "Transação:",
|
"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_background": "Continuar em segundo plano",
|
||||||
"switch_progress_default_wallet": "Carteira padrão",
|
"switch_progress_default_wallet": "Carteira padrão",
|
||||||
"switch_progress_elapsed": "Decorrido",
|
"switch_progress_elapsed": "Decorrido",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "Переведено на:",
|
"sweep_to": "Переведено на:",
|
||||||
"sweep_toggle": "Перевести в мой кошелёк (не сохранять ключ)",
|
"sweep_toggle": "Перевести в мой кошелёк (не сохранять ключ)",
|
||||||
"sweep_tx": "Транзакция:",
|
"sweep_tx": "Транзакция:",
|
||||||
|
"switch_corrupt_body": "Похоже, этот кошелёк повреждён — узел не смог его открыть. Восстановите из резервной копии, создайте заново или попробуйте восстановить.",
|
||||||
|
"switch_corrupt_repair": "Попробовать восстановить (salvage)",
|
||||||
"switch_progress_background": "Продолжить в фоне",
|
"switch_progress_background": "Продолжить в фоне",
|
||||||
"switch_progress_default_wallet": "Кошелёк по умолчанию",
|
"switch_progress_default_wallet": "Кошелёк по умолчанию",
|
||||||
"switch_progress_elapsed": "Прошло",
|
"switch_progress_elapsed": "Прошло",
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@
|
|||||||
"sweep_to": "归集到:",
|
"sweep_to": "归集到:",
|
||||||
"sweep_toggle": "归集到我的钱包(不保留密钥)",
|
"sweep_toggle": "归集到我的钱包(不保留密钥)",
|
||||||
"sweep_tx": "交易:",
|
"sweep_tx": "交易:",
|
||||||
|
"switch_corrupt_body": "此钱包似乎已损坏——节点无法打开它。请从备份恢复、重新创建,或尝试修复。",
|
||||||
|
"switch_corrupt_repair": "尝试修复(salvage)",
|
||||||
"switch_progress_background": "在后台继续",
|
"switch_progress_background": "在后台继续",
|
||||||
"switch_progress_default_wallet": "默认钱包",
|
"switch_progress_default_wallet": "默认钱包",
|
||||||
"switch_progress_elapsed": "已用时",
|
"switch_progress_elapsed": "已用时",
|
||||||
|
|||||||
@@ -3919,6 +3919,15 @@ void App::renderSwitchStopDaemonDialog()
|
|||||||
// rather than repeating the title.
|
// rather than repeating the title.
|
||||||
ui::material::DialogWarningHeader(wallet_switch_error_.c_str());
|
ui::material::DialogWarningHeader(wallet_switch_error_.c_str());
|
||||||
ImGui::Dummy(ImVec2(0, ui::Layout::spacingMd()));
|
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))) {
|
if (ui::material::TactileButton(TR("close"), ImVec2(110.0f * dp, 0))) {
|
||||||
wallet_switch_dialog_open_.store(false);
|
wallet_switch_dialog_open_.store(false);
|
||||||
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::None));
|
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::None));
|
||||||
|
|||||||
@@ -492,8 +492,8 @@ public:
|
|||||||
// (rescan only if it was never synced in this datadir). Per-wallet data follows automatically
|
// (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.
|
// 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
|
// 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).
|
// that dialog). salvage: start the target node with -salvagewallet (repair a corrupt wallet).
|
||||||
void switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed = false);
|
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.
|
// Main-thread continuation: if a wallet switch's daemon failed to start, revert active_wallet_file.
|
||||||
void processWalletSwitchRevert();
|
void processWalletSwitchRevert();
|
||||||
|
|
||||||
@@ -780,6 +780,10 @@ private:
|
|||||||
std::atomic<bool> wallet_switch_dialog_open_{false};
|
std::atomic<bool> wallet_switch_dialog_open_{false};
|
||||||
std::string wallet_switch_error_;
|
std::string wallet_switch_error_;
|
||||||
double wallet_switch_started_time_ = 0.0; // ImGui::GetTime() captured on first progress frame (elapsed)
|
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<bool> 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
|
// 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.
|
// and resets it to -1. Atomic because the worker thread writes it and the UI thread reads/clears it.
|
||||||
|
|||||||
@@ -189,6 +189,17 @@ static WarmupText translateWarmup(const std::string& raw)
|
|||||||
return {raw.c_str(), ""};
|
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()
|
// 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
|
// 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).
|
// 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();
|
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()) {
|
if (!supportsFullNodeLifecycleActions()) {
|
||||||
ui::Notifications::instance().warning("Full-node lifecycle actions are unavailable in lite build");
|
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
|
const std::string prevWallet = settings_->getActiveWalletFile(); // for revert if the new one fails
|
||||||
wallet_switch_prev_file_ = prevWallet;
|
wallet_switch_prev_file_ = prevWallet;
|
||||||
|
wallet_switch_target_file_ = walletFile; // remembered for a "salvage" retry from the failure modal
|
||||||
wallet_switch_failed_.store(false);
|
wallet_switch_failed_.store(false);
|
||||||
switch_stop_failed_.store(false); // reason latch for the revert message; a stale one must not leak
|
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)
|
wallet_switch_pending_confirm_.store(true); // cleared on a successful connect (onConnected)
|
||||||
settings_->setActiveWalletFile(walletFile);
|
settings_->setActiveWalletFile(walletFile);
|
||||||
settings_->save();
|
settings_->save();
|
||||||
@@ -1117,8 +1130,12 @@ void App::switchToWallet(const std::string& walletFile, bool stopDaemonConfirmed
|
|||||||
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Stopping));
|
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Stopping));
|
||||||
wallet_switch_dialog_open_.store(true);
|
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;
|
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 {
|
try {
|
||||||
// Stop the node so it releases the datadir .lock + RPC port before we relaunch. For an adopted
|
// 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
|
// (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).
|
// as owned (stop/isRunning/exit behave normally for the next switch and app exit).
|
||||||
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Starting));
|
wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Starting));
|
||||||
if (daemon_controller_) daemon_controller_->clearExternalDaemonDetected();
|
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
|
// 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
|
// 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
|
// 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));
|
std::this_thread::sleep_for(std::chrono::milliseconds(1500));
|
||||||
if (!isEmbeddedDaemonRunning()) ok = false;
|
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).
|
// Process is up and staying up — now waiting for RPC to answer (onConnected closes the modal).
|
||||||
if (ok) wallet_switch_phase_.store(static_cast<int>(WalletSwitchPhase::Reconnecting));
|
if (ok) wallet_switch_phase_.store(static_cast<int>(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.
|
// Clear the crash-wedge so the (good) previous wallet's daemon is allowed to start again.
|
||||||
if (daemon_controller_) daemon_controller_->resetCrashCount();
|
if (daemon_controller_) daemon_controller_->resetCrashCount();
|
||||||
// Accurate reason: "port wouldn't free" (the running node kept the connection) vs. a genuinely bad
|
// Accurate reason: "port wouldn't free" (the running node kept the connection); "wallet is corrupt"
|
||||||
// target wallet. The former is the adopted-external-daemon case; the latter a daemon that exited in init.
|
// (the node's recovery couldn't open it); else a generic bad-wallet failure.
|
||||||
const std::string reason = switch_stop_failed_.exchange(false)
|
const std::string reason =
|
||||||
? std::string("Couldn't switch wallets — the running node didn't release its connection in time. "
|
switch_stop_failed_.exchange(false)
|
||||||
"It's still on the previous wallet.")
|
? std::string("Couldn't switch wallets — the running node didn't release its connection in time. "
|
||||||
: std::string("Couldn't open that wallet — reverted to the previous one.");
|
"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);
|
// 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.
|
// otherwise (the user chose "continue in background") fall back to a toast.
|
||||||
if (wallet_switch_dialog_open_.load()) {
|
if (wallet_switch_dialog_open_.load()) {
|
||||||
|
|||||||
@@ -121,6 +121,11 @@ void DaemonController::setZapOnNextStart(bool enabled)
|
|||||||
daemon_->setZapOnNextStart(enabled);
|
daemon_->setZapOnNextStart(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DaemonController::setSalvageOnNextStart(bool enabled)
|
||||||
|
{
|
||||||
|
daemon_->setSalvageOnNextStart(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
bool DaemonController::zapOnNextStart() const
|
bool DaemonController::zapOnNextStart() const
|
||||||
{
|
{
|
||||||
return daemon_->zapOnNextStart();
|
return daemon_->zapOnNextStart();
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ public:
|
|||||||
bool rescanOnNextStart() const;
|
bool rescanOnNextStart() const;
|
||||||
void setZapOnNextStart(bool enabled);
|
void setZapOnNextStart(bool enabled);
|
||||||
bool zapOnNextStart() const;
|
bool zapOnNextStart() const;
|
||||||
|
void setSalvageOnNextStart(bool enabled);
|
||||||
|
|
||||||
static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon,
|
static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon,
|
||||||
bool externalDaemonDetected,
|
bool externalDaemonDetected,
|
||||||
|
|||||||
@@ -524,9 +524,16 @@ bool EmbeddedDaemon::start(const std::string& binary_path)
|
|||||||
args.push_back("-wallet=" + wallet_file_);
|
args.push_back("-wallet=" + wallet_file_);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add wallet-repair flag if requested (one-shot). -zapwallettxes=2 wipes all wallet tx/note
|
// Add wallet-repair flag if requested (one-shot). Precedence: salvage > zap > rescan; each implies a
|
||||||
// records and rebuilds them from the chain; it implies -rescan, so don't also pass -rescan.
|
// rescan in the daemon, so we don't stack them.
|
||||||
if (zap_on_next_start_.exchange(false)) {
|
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");
|
DEBUG_LOGF("[INFO] Adding -zapwallettxes=2 flag for wallet repair (zap & rebuild)\n");
|
||||||
args.push_back("-zapwallettxes=2");
|
args.push_back("-zapwallettxes=2");
|
||||||
rescan_on_next_start_.store(false); // implied by zap; avoid redundant -rescan
|
rescan_on_next_start_.store(false); // implied by zap; avoid redundant -rescan
|
||||||
|
|||||||
@@ -201,6 +201,11 @@ public:
|
|||||||
void setZapOnNextStart(bool v) { zap_on_next_start_ = v; }
|
void setZapOnNextStart(bool v) { zap_on_next_start_ = v; }
|
||||||
bool zapOnNextStart() const { return zap_on_next_start_.load(); }
|
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
|
* @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
|
* different datadir (with its own DRAGONX.conf) plus the given extra args. Used by the
|
||||||
@@ -274,6 +279,7 @@ private:
|
|||||||
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
std::atomic<int> crash_count_{0}; // consecutive crash counter
|
||||||
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
std::atomic<bool> rescan_on_next_start_{false}; // -rescan flag for next start
|
||||||
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
std::atomic<bool> zap_on_next_start_{false}; // -zapwallettxes=2 flag for next start
|
||||||
|
std::atomic<bool> salvage_on_next_start_{false}; // -salvagewallet flag for next start
|
||||||
std::string override_datadir_; // one-shot: -datadir for the next start
|
std::string override_datadir_; // one-shot: -datadir for the next start
|
||||||
std::vector<std::string> override_extra_args_; // one-shot: extra args for the next start
|
std::vector<std::string> override_extra_args_; // one-shot: extra args for the next start
|
||||||
bool skip_port_check_ = false; // isolated instance on a non-default port
|
bool skip_port_check_ = false; // isolated instance on a non-default port
|
||||||
|
|||||||
@@ -1057,6 +1057,8 @@ void I18n::loadBuiltinEnglish()
|
|||||||
strings_["switch_progress_elapsed"] = "Elapsed";
|
strings_["switch_progress_elapsed"] = "Elapsed";
|
||||||
strings_["switch_progress_default_wallet"] = "Default wallet";
|
strings_["switch_progress_default_wallet"] = "Default wallet";
|
||||||
strings_["switch_progress_external_wallet"] = "External 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
|
// Receive Tab
|
||||||
strings_["receiving_addresses"] = "Your Receiving Addresses";
|
strings_["receiving_addresses"] = "Your Receiving Addresses";
|
||||||
|
|||||||
Reference in New Issue
Block a user