From 19e1ce6f006ccd7dadddce72a24239eda3b8898a Mon Sep 17 00:00:00 2001 From: DanS Date: Wed, 8 Jul 2026 05:48:39 +0200 Subject: [PATCH] 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) --- src/init.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 9bd5e6616..1163f756e 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -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 // 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 -// the aligned size_t threshold that the flush path reads. +// RAM we grow it back toward the startup ceiling. nCoinCacheUsage is std::atomic, so this +// cross-thread write (vs the cs_main-held reads in FlushStateToDisk/VerifyDB) is well-defined, no lock needed. static void AdjustCoinCacheForMemoryPressure() { if (g_nMaxCoinCacheUsage == 0) @@ -2072,7 +2072,25 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) pcoinsdbview = new CCoinsViewDB(nCoinDBCache, false, fReindex); pcoinscatcher = new CCoinsViewErrorCatcher(pcoinsdbview); pcoinsTip = new CCoinsViewCache(pcoinscatcher); - pnotarizations = new NotarizationDB(100*1024*1024, false, fReindex); + try { + 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) {