From 2e5fdd83e419bb1618aa0484c4f66437ad90a961 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 21 Nov 2025 12:00:36 +0300 Subject: [PATCH] feat: verify and repair evodb diffs automatically at node startup Automatically verify and repair deterministic masternode list diffs in evodb during node startup. Helps detect and fix database corruption without manual intervention. - Add DB_LIST_REPAIRED marker to track when repair is complete - Skip repair during reindex (fresh rebuild) - Add -forceevodbrepair flag to force re-verification - Shutdown gracefully on critical errors with user instructions - Run in background thread with minimal cs_main locking Co-Authored-By: Claude Code (Anthropic) --- src/evo/deterministicmns.cpp | 15 +++++++++++ src/evo/deterministicmns.h | 2 ++ src/init.cpp | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index cca3bb82b2b0..0fbf6fb7ba0e 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -32,6 +32,7 @@ static const std::string DB_LIST_SNAPSHOT = "dmn_S3"; static const std::string DB_LIST_DIFF = "dmn_D4"; // Bumped for nVersion-first format static const std::string DB_LIST_DIFF_LEGACY = "dmn_D3"; // Legacy format key +static const std::string DB_LIST_REPAIRED = "dmn_R1"; uint64_t CDeterministicMN::GetInternalId() const { @@ -1690,6 +1691,20 @@ CDeterministicMNManager::RecalcDiffsResult CDeterministicMNManager::RecalculateA return result; } +bool CDeterministicMNManager::IsRepaired() const { return m_evoDb.Exists(DB_LIST_REPAIRED); } + +void CDeterministicMNManager::CompleteRepair() +{ + auto dbTx = m_evoDb.BeginTransaction(); + m_evoDb.Write(DB_LIST_REPAIRED, 1); + dbTx->Commit(); + // flush it to disk + if (!m_evoDb.CommitRootTransaction()) { + LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__); + assert(false); + } +} + std::vector CDeterministicMNManager::CollectSnapshotBlocks( const CBlockIndex* start_index, const CBlockIndex* stop_index, const Consensus::Params& consensus_params) { diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 80aabbe113aa..8ba7e9ab02cb 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -744,6 +744,8 @@ class CDeterministicMNManager const CBlockIndex* stop_index, ChainstateManager& chainman, BuildListFromBlockFunc build_list_func, bool repair) EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] bool IsRepaired() const; + void CompleteRepair(); // Migration support for nVersion-first CDeterministicMNStateDiff format [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); diff --git a/src/init.cpp b/src/init.cpp index 642df953f34f..501688cb0cb2 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -81,9 +81,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -751,6 +753,7 @@ void SetupServerArgs(ArgsManager& argsman) argsman.AddArg("-checkmempool=", strprintf("Run mempool consistency checks every transactions. Use 0 to disable. (default: %u, regtest: %u)", defaultChainParams->DefaultConsistencyChecks(), regtestChainParams->DefaultConsistencyChecks()), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-checkpoints", strprintf("Enable rejection of any forks from the known historical chain until block %s (default: %u)", defaultChainParams->Checkpoints().GetHeight(), DEFAULT_CHECKPOINTS_ENABLED), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-deprecatedrpc=", "Allows deprecated RPC method(s) to be used", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); + argsman.AddArg("-forceevodbrepair", "Force evodb masternode list diff verification and repair on startup, even if already repaired (default: 0)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorcount=", strprintf("Do not accept transactions if number of in-mempool ancestors is or more (default: %u)", DEFAULT_ANCESTOR_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitancestorsize=", strprintf("Do not accept transactions whose size with all in-mempool ancestors exceeds kilobytes (default: %u)", DEFAULT_ANCESTOR_SIZE_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-limitdescendantcount=", strprintf("Do not accept transactions if any ancestor would have or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); @@ -2408,6 +2411,53 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("Filling coin cache with masternode UTXOs: done in %dms\n", Ticks(SteadyClock::now() - start)); } + if (fReindex || fReindexChainState) { + LogPrintf("Skipping evodb repair during reindex\n"); + node.dmnman->CompleteRepair(); // Mark as repaired since we're rebuilding fresh + } else if (node.dmnman->IsRepaired() && !args.GetBoolArg("-forceevodbrepair", false)) { + LogPrintf("Masternode list diffs are already repaired\n"); + } else { + const CBlockIndex* start_index; + const CBlockIndex* stop_index; + { + LOCK(cs_main); + const auto& consensus_params = Params().GetConsensus(); + start_index = chainman.ActiveChain()[consensus_params.DIP0003Height]; + stop_index = chainman.ActiveChain().Tip(); + } + + if (start_index && stop_index && start_index->nHeight < stop_index->nHeight) { + LogPrintf("Verifying and repairing masternode list diffs...\n"); + const auto start{SteadyClock::now()}; + // Create a callback that wraps CSpecialTxProcessor::BuildNewListFromBlock + auto build_list_func = [&node](const CBlock& block, gsl::not_null pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, + bool debugLogs, BlockValidationState& state, + CDeterministicMNList& mnListRet) -> bool { + return node.chain_helper->special_tx->RebuildListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet); + }; + auto result = node.dmnman->RecalculateAndRepairDiffs(start_index, stop_index, chainman, build_list_func, true); + + if (!result.verification_errors.empty()) { + LogPrintf("WARNING: Verification errors:\n%s\n", Join(result.verification_errors, "\n")); + } + + if (!result.repair_errors.empty()) { + // Critical errors occurred - reindex required + LogPrintf("Failed to repair masternode list diffs. Database corruption detected. " /* Continued */ + "Please restart with -reindex to rebuild the database.\n" + "Errors:\n%s\n", + Join(result.repair_errors, "\n")); + StartShutdown(); + return; + } + node.dmnman->CompleteRepair(); + LogPrintf("Successfully repaired %d masternode list diffs, verified %d snapshots in %ds\n", + result.diffs_recalculated, result.snapshots_verified, + Ticks(SteadyClock::now() - start)); + } + } + if (node.mn_activeman != nullptr) { node.mn_activeman->Init(chainman.ActiveTip()); }