fix: self-heal a corrupt/torn notarizations DB instead of aborting startup

A torn or corrupt notarizations (dPoW) leveldb -- a 0-byte log left by a torn
snapshot, or a corrupt MANIFEST -- threw at open and was caught by the block-DB
load try/catch, aborting startup with a misleading Error-opening-block-database
message and forcing a full resync. The notarizations DB is non-essential and
node-regenerable, so on open failure move it aside (notarizations.corrupt,
preserving the data in case the error was transient) and regenerate a fresh one;
if the fresh recreate also fails it still propagates as fatal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 05:48:39 +02:00
parent dc45e7d904
commit 19e1ce6f00

View File

@@ -1087,8 +1087,8 @@ static const int64_t g_nMinCoinCacheMB = 256; // never thrash below this working
// Scheduled task: nudge nCoinCacheUsage toward "use all RAM except the reserve". If free RAM is below // Scheduled task: nudge nCoinCacheUsage toward "use all RAM except the reserve". If free RAM is below
// the reserve we shrink the target (the next per-block flush releases the excess); if there is spare // the reserve we shrink the target (the next per-block flush releases the excess); if there is spare
// RAM we grow it back toward the startup ceiling. Lock-free: it only reads system memory and writes // RAM we grow it back toward the startup ceiling. nCoinCacheUsage is std::atomic<size_t>, so this
// the aligned size_t threshold that the flush path reads. // cross-thread write (vs the cs_main-held reads in FlushStateToDisk/VerifyDB) is well-defined, no lock needed.
static void AdjustCoinCacheForMemoryPressure() static void AdjustCoinCacheForMemoryPressure()
{ {
if (g_nMaxCoinCacheUsage == 0) if (g_nMaxCoinCacheUsage == 0)
@@ -2072,7 +2072,25 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler)
pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex);
pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview);
pcoinsTip = new CCoinsViewCache(pcoinscatcher); pcoinsTip = new CCoinsViewCache(pcoinscatcher);
try {
pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex); pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex);
} catch (const std::exception& e) {
// The notarizations (dPoW) DB is non-essential and node-regenerable. It has been seen to
// snapshot/flush torn (0-byte log -> leveldb "Database I/O error" on reopen), which
// previously aborted startup with a spurious "Error opening block database" and forced a
// full resync. Wipe and recreate it instead of failing hard.
LogPrintf("%s: notarizations DB failed to open (%s); moving aside and regenerating (non-fatal)\n", __FUNCTION__, e.what());
// Move (do NOT delete) the old DB aside, so a transient open failure (fd
// exhaustion, disk full, permissions) cannot permanently destroy notarization
// history. If the recreate below also fails it propagates as fatal and the old
// data survives in notarizations.corrupt for recovery.
{
boost::filesystem::path ndir = GetDataDir() / "notarizations";
boost::filesystem::remove_all(ndir.string() + ".corrupt");
boost::filesystem::rename(ndir, ndir.string() + ".corrupt");
}
pnotarizations = new NotarizationDB(100*1024*1024, false, true);
}
if (fReindex) { if (fReindex) {