fix(wallet): don't raise the "needs a repair" dialog after a Compact

Compact runs the node with -salvagewallet to rewrite wallet.dat, which produces
the exact same salvage signature in the daemon output as a corruption recovery.
So detectWalletAutoRecovery mistook the compaction for a recovery and raised the
alarming "Your wallet file needs a quick repair" dialog — whose "Restore
original" would have UNDONE the compaction by restoring the larger pre-compact
backup.

Flag the salvage as expected when we arm it for a compaction
(compaction_salvage_expected_, set in both compactWallet and
compactViaEmbeddedSwitch, disarmed if the restart is refused). When the detector
sees the salvage it consumes the flag and shows a calm "Wallet compacted" toast
(compact_done) instead of the recovery dialog. A genuine, unexpected salvage
still surfaces the dialog as before.

English only for compact_done (8 locales fall back). Build + tests + hygiene green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-27 12:09:29 -05:00
co-authored by Claude Opus 4.8
parent 38461f084c
commit 00d298453e
3 changed files with 19 additions and 3 deletions
+4
View File
@@ -1396,6 +1396,10 @@ private:
// so a possibly-incomplete salvaged wallet isn't mistaken for fund loss. Warned once per session.
bool wallet_auto_recovered_ = false; // a salvage happened this session
bool wallet_auto_recovered_warned_ = false; // guard: only surface it once per session
// We armed -salvagewallet ourselves to COMPACT a healthy wallet (not to recover a corrupt one), so the
// resulting salvage must NOT raise the "needs a quick repair" dialog — detectWalletAutoRecovery consumes
// this and shows a calm "compacted" toast instead. Atomic: compaction can be armed off the UI thread.
std::atomic<bool> compaction_salvage_expected_{false};
std::string last_address_error_; // daemon reason the last z_getnewaddress/getnewaddress failed ("" = ok)
bool wallet_degraded_ = false; // v1.3.0+ opened the wallet in DEGRADED mode (no new HD keys)
bool wallet_degraded_warned_ = false; // guard: surface the degraded-mode notice once per session
+14 -3
View File
@@ -241,8 +241,15 @@ void App::detectWalletAutoRecovery()
if (wallet_auto_recovered_warned_) return;
if (!isUsingEmbeddedDaemon() || !daemon_controller_ || !daemon_controller_->daemon()) return;
if (!daemon::walletAutoRecovered(daemon_controller_->daemon()->getOutput())) return;
wallet_auto_recovered_warned_ = true; // consume the signature either way — only surfaced once per session
// Our own Compact armed this salvage to shrink a HEALTHY wallet — it's not a corruption recovery, so don't
// raise the alarming "needs a quick repair" dialog (whose "Restore original" would undo the compaction).
if (compaction_salvage_expected_.exchange(false)) {
ui::Notifications::instance().success(TR("compact_done"), 12.0f);
VERBOSE_LOGF("[recovery] Salvage was our intentional compaction — suppressing the recovery dialog\n");
return;
}
wallet_auto_recovered_ = true;
wallet_auto_recovered_warned_ = true;
show_wallet_recovered_dialog_ = true;
ui::Notifications::instance().info(TR("wallet_recovered_notify"), 30.0f); // calm, not red — coins are safe
VERBOSE_LOGF("[recovery] Daemon auto-recovered/salvaged wallet.dat — surfacing the recovery dialog\n");
@@ -6518,11 +6525,14 @@ void App::compactWallet()
// next start rewrites wallet.dat into a fresh, compact file (and keeps the old one as a timestamped
// .bak). It implies a full rescan, so restartDaemon() takes the node down and back up re-scanning.
daemon_controller_->setSalvageOnNextStart(true);
compaction_salvage_expected_.store(true); // this salvage is a compaction, not a recovery — don't alarm
restartDaemon(); // does its own full-node / busy / lifecycle guards and will refuse if the node is busy
if (daemon_restarting_.load())
if (daemon_restarting_.load()) {
n.info(TR("compact_started"));
else
} else {
daemon_controller_->setSalvageOnNextStart(false); // the restart was refused — disarm the stray salvage
compaction_salvage_expected_.store(false);
}
}
void App::compactViaEmbeddedSwitch()
@@ -6565,6 +6575,7 @@ void App::compactViaEmbeddedSwitch()
// after the switch our own process is the running node.)
setUseEmbeddedDaemon(true);
compact_on_embedded_start_.store(true);
compaction_salvage_expected_.store(true); // the coming salvage is a compaction — don't alarm
ui::Notifications::instance().info(TR("compact_started"));
startEmbeddedDaemon();
};
+1
View File
@@ -881,6 +881,7 @@ void I18n::loadBuiltinEnglish()
strings_["wallet_size_compact"] = "Compact wallet\xE2\x80\xA6";
strings_["tt_wallet_size_compact"] = "Reclaim the disk space freed by consolidating. Berkeley DB can't shrink the file in place, so this rewrites wallet.dat into a smaller file and restarts the node with a full rescan (can take a while). A backup of the current wallet is kept.";
strings_["compact_started"] = "Compacting wallet \xE2\x80\x94 the node is restarting and will re-scan the chain.";
strings_["compact_done"] = "Wallet compacted \xE2\x80\x94 the file was rewritten and the freed space reclaimed. The node is re-scanning to rebuild your transaction view.";
strings_["confirm_compact_wallet_title"] = "Compact wallet";
strings_["confirm_compact_wallet_msg"] = "Consolidating merged your notes, but the wallet file can't shrink on its own \xE2\x80\x94 the node has to rewrite it to reclaim the freed space. This restarts the node and re-scans the blockchain, which can take a while on a large wallet.";
strings_["confirm_compact_wallet_safe"] = "Your funds and balance are safe \xE2\x80\x94 a timestamped backup of the current wallet is kept.";