// dragonx-wallet-rebuild — offline recovery helper for a BDB-inconsistent wallet.dat. // // Some wallet.dat files are valid Berkeley DB btrees whose EXTENT metadata is stale (the "main" // subdatabase metapage records a low last_pgno while its live data spans thousands of pages further // in the file). A tolerant page-walk reads every record, but the daemon's Berkeley DB `verify` // rejects the file and auto-salvages it — which finds nothing and, on each restart, shrinks the // wallet to empty (the "salvage cascade" that looks like fund loss). The keys are intact; only the // DB envelope is broken. // // This tool fixes it the way it must be fixed — offline, on the file, before any daemon touches it: // 1) read all (key,value) records with the tolerant walker (util/wallet_file_probe.h), then // 2) write them VERBATIM into a fresh, consistent Berkeley DB "main" btree via real libdb put()s, // so libdb computes correct extent/metapage bookkeeping itself (sidestepping the whole defect). // Records are copied byte-for-byte: encrypted key material (ckey/csapzkey/mkey) passes through as // opaque ciphertext — no passphrase, no decryption, no key material ever interpreted. Only large `tx` // history values (which live in BDB overflow pages) are skipped; a wallet rescan rebuilds those. // // Usage: dragonx-wallet-rebuild // Output: a single JSON line on stdout; exit 0 on success, non-zero on failure. Never touches the // source (opens it read-only); refuses to overwrite an existing output (DB_EXCL). #include "util/wallet_file_probe.h" #include #include #include #include int main(int argc, char** argv) { if (argc < 3) { std::fprintf(stderr, "usage: dragonx-wallet-rebuild \n"); return 2; } const char* src = argv[1]; const char* dst = argv[2]; // --- 1) tolerant read (no libdb; reads records the daemon's BDB can't) --- const auto rec = dragonx::util::extractWalletBtreeRecords(src); if (!rec.parsed) { std::printf("{\"ok\":false,\"error\":\"source is not a readable Berkeley DB btree wallet\"}\n"); return 3; } if (rec.keyRecords == 0) { // Refuse to produce a keyless wallet — nothing to recover, and installing it would look like loss. std::printf("{\"ok\":false,\"error\":\"no key records found in source\",\"read\":%zu}\n", rec.records.size()); return 4; } // --- 2) write a fresh, consistent BDB "main" btree (what CWalletDB expects) --- DB* db = nullptr; int r = db_create(&db, nullptr, 0); if (r != 0) { std::printf("{\"ok\":false,\"error\":\"db_create: %s\"}\n", db_strerror(r)); return 5; } // DB_EXCL: never clobber an existing file — the caller passes a fresh path. r = db->open(db, nullptr, dst, "main", DB_BTREE, DB_CREATE | DB_EXCL, 0600); if (r != 0) { std::printf("{\"ok\":false,\"error\":\"open output: %s\"}\n", db_strerror(r)); db->close(db, 0); return 6; } long wrote = 0; for (const auto& kv : rec.records) { DBT k, v; std::memset(&k, 0, sizeof k); std::memset(&v, 0, sizeof v); k.data = const_cast(kv.first.data()); k.size = static_cast(kv.first.size()); v.data = const_cast(kv.second.data()); v.size = static_cast(kv.second.size()); r = db->put(db, nullptr, &k, &v, 0); if (r != 0) { std::printf("{\"ok\":false,\"error\":\"put failed: %s\",\"wrote\":%ld}\n", db_strerror(r), wrote); db->close(db, 0); return 7; } ++wrote; } r = db->close(db, 0); // close flushes correct metadata if (r != 0) { std::printf("{\"ok\":false,\"error\":\"close: %s\",\"wrote\":%ld}\n", db_strerror(r), wrote); return 8; } std::printf("{\"ok\":true,\"read\":%zu,\"keyRecords\":%d,\"skippedOverflow\":%d,\"wrote\":%ld,\"complete\":%s}\n", rec.records.size(), rec.keyRecords, rec.skippedOverflow, wrote, rec.complete ? "true" : "false"); return 0; }