wallet/db: keep the BDB logs when the shutdown flush fails

CDBEnv::Flush(true) removed database/ (log_archive DB_ARCH_REMOVE, then
remove_all) once mapFileUseCount was empty. It did that even when the
checkpoint or lsn_reset before it had failed, or the environment had
panicked (BDB0060). In those states wallet.dat can be missing committed
changes that only the logs hold:
- a page write failed (EBADF/ESPIPE through an fd closed behind BDB's
  back, the wolfSSL CloseSocket bug), so the page stayed dirty in the
  mpool cache and the checkpoint never completed;
- pages written after the last lsn_reset carry real LSNs that point
  into those logs.
Deleting the logs turned a wallet that the next start's DB_RECOVER would
repair into one that silently lost those changes. A std::terminate kept
the logs; the clean shutdown that ThreadNotifyWallets now does on a
wallet DB error deleted them.

Remember a failed txn_checkpoint or lsn_reset (in Flush and in
CheckpointLSN, which the periodic flush and backupwallet use). Before
removing anything, also check the environment for a panic. get_flags
reports it, and itself fails with DB_RUNRECOVERY once the environment
has panicked. In any of those cases keep database/ and log why. If the
kept logs are unusable, the next start already moves database/ aside
and retries (CWallet::Verify).

WalletDbEnv tests:
- a clean shutdown still removes the logs;
- a panicked environment (DB_PANIC_ENVIRONMENT) keeps them;
- a failed lsn_reset keeps them.
With the old db.cpp only the first passes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-16 21:23:01 -05:00
co-authored by Claude Opus 5.5
parent 5b36d7ad24
commit 70b35ac11a
4 changed files with 96 additions and 10 deletions
+32 -9
View File
@@ -61,6 +61,7 @@ void CDBEnv::Reset()
dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS);
fDbEnvInit = false;
fMockDb = false;
fKeepLogs = false;
}
CDBEnv::CDBEnv() : dbenv(NULL)
@@ -276,10 +277,12 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C
void CDBEnv::CheckpointLSN(const std::string& strFile)
{
dbenv->txn_checkpoint(0, 0, 0);
if (dbenv->txn_checkpoint(0, 0, 0) != 0)
fKeepLogs = true;
if (fMockDb)
return;
dbenv->lsn_reset(strFile.c_str(), 0);
if (dbenv->lsn_reset(strFile.c_str(), 0) != 0)
fKeepLogs = true;
}
@@ -498,10 +501,16 @@ void CDBEnv::Flush(bool fShutdown)
// Move log data to the dat file
CloseDb(strFile);
LogPrint("db", "CDBEnv::Flush: %s checkpoint\n", strFile);
dbenv->txn_checkpoint(0, 0, 0);
int ret = dbenv->txn_checkpoint(0, 0, 0);
if (ret != 0) {
LogPrintf("CDBEnv::Flush: %s checkpoint failed: %s\n", strFile, DbEnv::strerror(ret));
fKeepLogs = true;
}
LogPrint("db", "CDBEnv::Flush: %s detach\n", strFile);
if (!fMockDb)
dbenv->lsn_reset(strFile.c_str(), 0);
if (!fMockDb && (ret = dbenv->lsn_reset(strFile.c_str(), 0)) != 0) {
LogPrintf("CDBEnv::Flush: %s lsn_reset failed: %s\n", strFile, DbEnv::strerror(ret));
fKeepLogs = true;
}
LogPrint("db", "CDBEnv::Flush: %s closed\n", strFile);
mapFileUseCount.erase(mi++);
} else
@@ -511,10 +520,24 @@ void CDBEnv::Flush(bool fShutdown)
if (fShutdown) {
char** listp;
if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb)
boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
// After a failed flush, or once the environment has panicked (BDB0060, e.g. after a page
// write failed), wallet.dat can be missing committed changes that only the logs hold.
// Removing them would turn a wallet the next start recovers (DB_RECOVER) into one that
// silently lost them. If the logs are unusable, the next start moves database/ aside.
// (get_flags itself fails with DB_RUNRECOVERY once the environment has panicked.)
u_int32_t envFlags = 0;
if (dbenv->get_flags(&envFlags) != 0 || (envFlags & DB_PANIC_ENVIRONMENT))
fKeepLogs = true;
if (fKeepLogs && !fMockDb) {
LogPrintf("CDBEnv::Flush: the wallet database was not flushed cleanly; keeping %s for recovery at the next start\n",
(boost::filesystem::path(strPath) / "database").string());
Close();
} else {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb)
boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
}
}
}
}