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:
2026-07-15 23:40:33 -05:00
parent faf77de9ce
commit d9911a9bc9
17 changed files with 92 additions and 14 deletions

Binary file not shown.

View File

@@ -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",

View File

@@ -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",

View File

@@ -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é",

View File

@@ -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": "経過",

View File

@@ -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": "경과",

View File

@@ -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",

View File

@@ -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": "Прошло",

View File

@@ -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": "已用时",

View File

@@ -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));

View File

@@ -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.

View File

@@ -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()) {

View File

@@ -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();

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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";