feat(node): warn when the daemon auto-recovers (salvages) wallet.dat

dragonxd auto-recovers a wallet.dat that fails BDB verification on startup — no
flag needed (CWallet::Verify → CDBEnv::Verify(walletFile, CWalletDB::Recover)):
it moves the original to wallet.<timestamp>.bak, salvages readable keys into a
fresh wallet.dat, and keeps running. The salvage can be incomplete (or the whole
thing a FALSE POSITIVE from stale/cross-platform BDB env state — __db.* / the
database/ dir carried between machines), so the node silently comes up on a
possibly-empty wallet. To the user that reads as fund loss, with no warning.

Detect it and warn loudly instead:
- daemon/daemon_startup_diagnosis.h: pure walletAutoRecovered() (the salvage /
  "Original wallet.dat saved as wallet.<ts>.bak" markers) + newestWalletSalvageBak()
  (picks the wallet.<unixtime>.bak the recovery just made).
- onConnected() scans the node's captured output once per session; on a match it
  shows a warning dialog + notification: the ORIGINAL is safe in wallet.<ts>.bak,
  the shown balance may be incomplete, and here are the exact steps to restore it
  (rename the .bak back + delete the stale database/ + __db.* env). One-click
  "Open data folder" jumps straight there. Full-node only; lite-safe.

Deliberately does NOT auto-swap the wallet files (untested per-platform file
manipulation on a real wallet is not worth the risk) — it informs + guides.

Adds walletAutoRecovered / newestWalletSalvageBak coverage to
testBlockDbOutputDiagnosis. Suite green (1/1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 00:30:11 -05:00
parent d136916e80
commit 3b7423f3a1
6 changed files with 111 additions and 0 deletions

View File

@@ -8,6 +8,7 @@
#pragma once
#include <string>
#include <vector>
namespace dragonx {
namespace daemon {
@@ -26,5 +27,38 @@ inline bool blockDbOutputLooksBroken(const std::string& out)
|| out.find("LoadBlockIndex()") != std::string::npos; // "... : failed to read value"
}
// True when dragonxd AUTO-RECOVERED the wallet on startup: on any BDB-verify failure it moves the
// original wallet.dat to "wallet.{timestamp}.bak", salvages readable keys into a fresh wallet.dat, and
// keeps running — no flag required (CWallet::Verify → CDBEnv::Verify(walletFile, CWalletDB::Recover)).
// The salvage can be incomplete (or a false positive from stale/cross-platform BDB env state), so the
// node silently comes up on a possibly-empty wallet — which reads as fund loss unless we surface it.
inline bool walletAutoRecovered(const std::string& out)
{
return out.find("wallet.dat corrupt, data salvaged") != std::string::npos // RECOVER_OK warning
|| out.find("Original wallet.dat saved as wallet.") != std::string::npos // the rename-aside notice
|| out.find("wallet.dat corrupt, salvage failed") != std::string::npos; // RECOVER_FAIL
}
// From a list of datadir filenames, pick the most recent daemon salvage backup — the "wallet.<unixtime>.bak"
// the auto-recovery just created (highest timestamp). Returns "" if none present. Pure, so it's testable.
inline std::string newestWalletSalvageBak(const std::vector<std::string>& filenames)
{
long long best = -1;
std::string bestName;
for (const auto& f : filenames) {
if (f.rfind("wallet.", 0) != 0) continue; // must start "wallet."
if (f.size() < 12 || f.compare(f.size() - 4, 4, ".bak") != 0) continue; // ...and end ".bak"
const std::string mid = f.substr(7, f.size() - 7 - 4); // digits between the dots
if (mid.empty() || mid.size() > 18) continue;
bool allDigits = true;
for (char c : mid) if (c < '0' || c > '9') { allDigits = false; break; }
if (!allDigits) continue;
long long ts = 0;
for (char c : mid) ts = ts * 10 + (c - '0');
if (ts > best) { best = ts; bestName = f; }
}
return bestName;
}
} // namespace daemon
} // namespace dragonx