fix(node): restore the LARGEST salvage backup, not the newest (salvage cascade)

The "Restore original wallet" action picked the newest wallet.<ts>.bak — but the
daemon auto-salvages on every failed BDB verify, and each round SHRINKS the wallet
(salvage keeps only readable records + drops the dead-page bloat). In a cascade the
newest .bak is the most-degraded (seen in the wild as "Salvage found no records")
while the original is the oldest and by far the largest.

Pick by file SIZE instead: add largestWalletSalvageBak((name,size) pairs) — the
largest wallet.<digits>.bak is the least-salvaged, i.e. the pristine original (an
emptied salvage is tiny; a real wallet is large); ties break to the newest ts.
Factor the shared parse into parseWalletSalvageBakTs(). restoreOriginalWallet()
now gathers file sizes and uses it (still verifies the pick is a valid BDB before
swapping). newestWalletSalvageBak kept for reference.

Adds a cascade regression test (a 40KB emptied newest .bak must NOT win over the
194MB original). 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:52:41 -05:00
parent 384d64ea5d
commit 975650f11b
3 changed files with 59 additions and 16 deletions

View File

@@ -4575,14 +4575,21 @@ void App::restoreOriginalWallet()
std::string err, warn;
try {
const std::string datadir = util::Platform::getDragonXDataDir();
// 1. Find the newest salvage backup (offline — no daemon needed).
std::vector<std::string> files;
// 1. Find the LARGEST salvage backup (offline — no daemon needed). Largest = least-salvaged =
// the original: a salvage cascade shrinks the wallet each round, so the newest .bak can be
// empty ("Salvage found no records") while the original is untouched and huge.
std::vector<std::pair<std::string, unsigned long long>> files;
{
std::error_code lec;
for (const auto& e : fs::directory_iterator(datadir, lec))
if (!lec) files.push_back(e.path().filename().string());
for (const auto& e : fs::directory_iterator(datadir, lec)) {
if (lec) break;
std::error_code se;
const auto sz = fs::is_regular_file(e, se) ? fs::file_size(e, se) : 0;
files.emplace_back(e.path().filename().string(),
se ? 0ull : static_cast<unsigned long long>(sz));
}
}
const std::string bak = daemon::newestWalletSalvageBak(files);
const std::string bak = daemon::largestWalletSalvageBak(files);
if (bak.empty()) {
err = TR("wallet_restore_no_backup");
} else if (!util::probeWalletFile(datadir + "/" + bak).isBerkeleyDB) {