From d4d0ddf849f86e7a8bbb9112eda1c98536d9983c Mon Sep 17 00:00:00 2001 From: Triangles Dev Date: Tue, 30 Jun 2026 01:25:10 -0700 Subject: [PATCH] init: skip post-migration Berkeley verify on SQLite wallet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Live-validation of the CWalletDB rebase (preserved snapshot wallet from DNS2's 2026-04-23 recovery, copied to /tmp/cpp20-validate, run with the new build) revealed that after MaybeMigrateBerkeleyWalletToSQLite() converts wallet.dat from Berkeley DB to SQLite, the next block of init code still calls bitdb.Verify() on the now-SQLite file. BDB cannot open a SQLite file, so Verify returns RECOVER_FAIL, which init treats as a fatal InitError ("wallet.dat corrupt, salvage failed"). The migration itself succeeds and produces a valid SQLite wallet.dat + a wallet.dat.bdb.bak copy of the original, but the daemon refuses to continue past Step 5. Fix: snapshot IsSQLiteFile(walletPath) AFTER the migration hook, then gate the entire BDB-only Step 5 path (bitdb.Open, salvagewallet, zapwallettxes, bitdb.Verify) on the snapshot. Once the wallet is SQLite — whether by migration or because it was born that way — the BDB verify/salvage steps are skipped; SQLiteDatabase::Open() already runs PRAGMA integrity_check during connection setup, so the wallet is validated against the SQLite schema before any handle is constructed. The verify_db performance log now includes wallet_is_sqlite=N for observability. Validation: full migration cycle completed in the test datadir, RPC getwalletinfo/getaddressesbyaccount returned valid data matching the pre-migration BDB wallet exactly (5 unique HD-derived addresses, keypoolsize=101, keypoololdest=1781772890, walletversion=60000). --- src/init.cpp | 51 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 39b06fe..9ddf52d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -971,6 +971,21 @@ bool AppInit2() uiInterface.InitMessage(_("Verifying database integrity...")); nStart = GetTimeMillis(); + // The pre-rebase Berkeley-only paths (salvagewallet, zapwallettxes, + // bitdb.Verify, and the Berkeley→SQLite migration hook itself) only + // apply to a wallet.dat that is still a Berkeley DB file. Once the + // migration has run — or if the user is starting with a wallet that was + // already SQLite — those steps would either no-op or (worse) misinterpret + // the SQLite file as a corrupt Berkeley file and abort startup. + // + // The SQLite backend runs its own PRAGMA integrity_check in + // SQLiteDatabase::Open(), so the wallet is validated against the SQLite + // schema before the wallet handle is ever constructed downstream. + // + // Note: the snapshot is taken AFTER any migration hook below, so that + // post-migration the verify/salvage paths are skipped automatically. + bool walletIsSqlite = false; + if (!bitdb.Open(GetDataDir())) { string msg = strprintf(_("Error initializing database environment %s!" @@ -1007,23 +1022,37 @@ bool AppInit2() std::string migErr; if (!MaybeMigrateBerkeleyWalletToSQLite(GetDataDir() / strWalletFileName, migErr)) return InitError(_("Wallet migration failed: ") + migErr); + // Snapshot AFTER migration so the post-migration verify step below + // is skipped automatically when the wallet is now SQLite. + walletIsSqlite = + fs::exists(GetDataDir() / strWalletFileName) && + IsSQLiteFile(GetDataDir() / strWalletFileName); + } + else + { + walletIsSqlite = + fs::exists(GetDataDir() / strWalletFileName) && + IsSQLiteFile(GetDataDir() / strWalletFileName); } - if (fs::exists(GetDataDir() / strWalletFileName)) + if (!walletIsSqlite) { - CDBEnv::VerifyResult r = bitdb.Verify(strWalletFileName, BerkeleyRecoverWallet); - if (r == CDBEnv::RECOVER_OK) + if (fs::exists(GetDataDir() / strWalletFileName)) { - string msg = strprintf(_("Warning: wallet.dat corrupt, data salvaged!" - " Original wallet.dat saved as wallet.{timestamp}.bak in %s; if" - " your balance or transactions are incorrect you should" - " restore from a backup."), strDataDir.c_str()); - uiInterface.ThreadSafeMessageBox(msg, _("Triangles"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + CDBEnv::VerifyResult r = bitdb.Verify(strWalletFileName, BerkeleyRecoverWallet); + if (r == CDBEnv::RECOVER_OK) + { + string msg = strprintf(_("Warning: wallet.dat corrupt, data salvaged!" + " Original wallet.dat saved as wallet.{timestamp}.bak in %s; if" + " your balance or transactions are incorrect you should" + " restore from a backup."), strDataDir.c_str()); + uiInterface.ThreadSafeMessageBox(msg, _("Triangles"), CClientUIInterface::OK | CClientUIInterface::ICON_EXCLAMATION | CClientUIInterface::MODAL); + } + if (r == CDBEnv::RECOVER_FAIL) + return InitError(_("wallet.dat corrupt, salvage failed")); } - if (r == CDBEnv::RECOVER_FAIL) - return InitError(_("wallet.dat corrupt, salvage failed")); } - StartupPerfLog("verify_db", GetTimeMillis() - nStart, strprintf("wallet=%s", strWalletFileName.c_str())); + StartupPerfLog("verify_db", GetTimeMillis() - nStart, strprintf("wallet=%s wallet_is_sqlite=%d", strWalletFileName.c_str(), (int)walletIsSqlite)); // ********************************************************* Step 6: network initialization nStart = GetTimeMillis();