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

@@ -6156,6 +6156,18 @@ void testBlockDbOutputDiagnosis()
EXPECT_EQ(newestWalletSalvageBak({"wallet.dat", "wallet.dat.encrypted.bak", "notes.txt"}),
std::string("")); // no wallet.<digits>.bak present
EXPECT_EQ(newestWalletSalvageBak({}), std::string(""));
// Restore must pick the LARGEST (least-salvaged) backup, NOT the newest — a salvage cascade shrinks
// the wallet each round, so the newest .bak can be an emptied 40KB copy while the original is huge.
using dragonx::daemon::largestWalletSalvageBak;
EXPECT_EQ(largestWalletSalvageBak({
{"wallet.dat", 40000ull}, // current (salvaged, tiny) — not a .bak
{"wallet.1786200000.bak", 194174976ull}, // ORIGINAL — oldest ts, biggest
{"wallet.1786340620.bak", 40960ull}, // latest salvage — newest ts, empty
{"peers.dat", 999ull}}),
std::string("wallet.1786200000.bak")); // largest wins over newest
EXPECT_EQ(largestWalletSalvageBak({{"wallet.dat", 100ull}, {"notes.txt", 5ull}}), std::string(""));
EXPECT_EQ(largestWalletSalvageBak({}), std::string(""));
}
// Live probe of a real lite server (env-gated). Validates CONNECT_ONLY latency + IP capture.