init: skip post-migration Berkeley verify on SQLite wallet
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).
This commit is contained in:
+40
-11
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user