LCOV - code coverage report
Current view: top level - src/evo - deterministicmns.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 577 881 65.5 %
Date: 2026-06-25 07:23:43 Functions: 60 63 95.2 %

          Line data    Source code
       1             : // Copyright (c) 2018-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <evo/deterministicmns.h>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <coins.h>
       9             : #include <consensus/validation.h>
      10             : #include <deploymentstatus.h>
      11             : #include <evo/dmn_types.h>
      12             : #include <evo/dmnstate.h>
      13             : #include <evo/evodb.h>
      14             : #include <evo/providertx.h>
      15             : #include <evo/simplifiedmns.h>
      16             : #include <evo/specialtx.h>
      17             : #include <masternode/meta.h>
      18             : #include <node/blockstorage.h>
      19             : #include <script/standard.h>
      20             : #include <stats/client.h>
      21             : #include <uint256.h>
      22             : #include <util/helpers.h>
      23             : 
      24             : #include <univalue.h>
      25             : 
      26             : #include <functional>
      27             : #include <optional>
      28             : #include <memory>
      29             : #include <ranges>
      30             : 
      31             : static const std::string DB_LIST_SNAPSHOT = "dmn_S3";
      32             : static const std::string DB_LIST_DIFF = "dmn_D4";        // Bumped for nVersion-first format
      33             : static const std::string DB_LIST_DIFF_LEGACY = "dmn_D3"; // Legacy format key
      34             : static const std::string DB_LIST_REPAIRED = "dmn_R1";
      35             : 
      36     1001002 : uint64_t CDeterministicMN::GetInternalId() const
      37             : {
      38             :     // can't get it if it wasn't set yet
      39     1001002 :     assert(internalId != std::numeric_limits<uint64_t>::max());
      40     1001002 :     return internalId;
      41             : }
      42             : 
      43      642687 : CSimplifiedMNListEntry CDeterministicMN::to_sml_entry() const
      44             : {
      45      642687 :     const CDeterministicMNState& state{*pdmnState};
      46     1285374 :     return CSimplifiedMNListEntry(proTxHash, state.confirmedHash, state.netInfo, state.pubKeyOperator,
      47      642687 :                                   state.keyIDVoting, !state.IsBanned(), state.platformHTTPPort, state.platformNodeID,
      48      642687 :                                   state.scriptPayout, state.scriptOperatorPayout, state.nVersion, nType);
      49             : }
      50             : 
      51         898 : std::string CDeterministicMN::ToString() const
      52             : {
      53         898 :     return strprintf("CDeterministicMN(proTxHash=%s, collateralOutpoint=%s, nOperatorReward=%f, state=%s", proTxHash.ToString(), collateralOutpoint.ToStringShort(), (double)nOperatorReward / 100, pdmnState->ToString());
      54           0 : }
      55             : 
      56       73124 : bool CDeterministicMNList::IsMNValid(const uint256& proTxHash) const
      57             : {
      58       73124 :     auto p = mnMap.find(proTxHash);
      59       73124 :     if (p == nullptr) {
      60          32 :         return false;
      61             :     }
      62       73092 :     return !(*p)->pdmnState->IsBanned();
      63       73124 : }
      64             : 
      65       22322 : bool CDeterministicMNList::IsMNPoSeBanned(const uint256& proTxHash) const
      66             : {
      67       22322 :     auto p = mnMap.find(proTxHash);
      68       22322 :     if (p == nullptr) {
      69           0 :         return false;
      70             :     }
      71       22322 :     return (*p)->pdmnState->IsBanned();
      72       22322 : }
      73             : 
      74     2651494 : CDeterministicMNCPtr CDeterministicMNList::GetMN(const uint256& proTxHash) const
      75             : {
      76     2651494 :     auto p = mnMap.find(proTxHash);
      77     2651494 :     if (p == nullptr) {
      78       42386 :         return nullptr;
      79             :     }
      80     2609108 :     return *p;
      81     2651494 : }
      82             : 
      83        7738 : CDeterministicMNCPtr CDeterministicMNList::GetValidMN(const uint256& proTxHash) const
      84             : {
      85        7738 :     auto dmn = GetMN(proTxHash);
      86        7738 :     if (dmn && dmn->pdmnState->IsBanned()) {
      87          41 :         return nullptr;
      88             :     }
      89        7697 :     return dmn;
      90        7738 : }
      91             : 
      92        3340 : CDeterministicMNCPtr CDeterministicMNList::GetMNByOperatorKey(const CBLSPublicKey& pubKey) const
      93             : {
      94       26256 :     const auto it = std::ranges::find_if(mnMap, [&pubKey](const auto& p) {
      95       22916 :         return p.second->pdmnState->pubKeyOperator.Get() == pubKey;
      96             :     });
      97        3340 :     if (it == mnMap.end()) {
      98        1812 :         return nullptr;
      99             :     }
     100        1528 :     return it->second;
     101        3340 : }
     102             : 
     103      851851 : CDeterministicMNCPtr CDeterministicMNList::GetMNByCollateral(const COutPoint& collateralOutpoint) const
     104             : {
     105      851851 :     return GetUniquePropertyMN(collateralOutpoint);
     106             : }
     107             : 
     108           0 : CDeterministicMNCPtr CDeterministicMNList::GetValidMNByCollateral(const COutPoint& collateralOutpoint) const
     109             : {
     110           0 :     auto dmn = GetMNByCollateral(collateralOutpoint);
     111           0 :     if (dmn && dmn->pdmnState->IsBanned()) {
     112           0 :         return nullptr;
     113             :     }
     114           0 :     return dmn;
     115           0 : }
     116             : 
     117          56 : CDeterministicMNCPtr CDeterministicMNList::GetMNByService(const CService& service) const
     118             : {
     119          56 :     return GetUniquePropertyMN(service);
     120             : }
     121             : 
     122      495407 : CDeterministicMNCPtr CDeterministicMNList::GetMNByInternalId(uint64_t internalId) const
     123             : {
     124      495407 :     auto proTxHash = mnInternalIdMap.find(internalId);
     125      495407 :     if (!proTxHash) {
     126           0 :         return nullptr;
     127             :     }
     128      495407 :     return GetMN(*proTxHash);
     129      495407 : }
     130             : 
     131     2446190 : static int CompareByLastPaid_GetHeight(const CDeterministicMN& dmn)
     132             : {
     133     2446190 :     int height = dmn.pdmnState->nLastPaidHeight;
     134     2446190 :     if (dmn.pdmnState->nPoSeRevivedHeight != -1 && dmn.pdmnState->nPoSeRevivedHeight > height) {
     135        1436 :         height = dmn.pdmnState->nPoSeRevivedHeight;
     136     2446190 :     } else if (height == 0) {
     137       69907 :         height = dmn.pdmnState->nRegisteredHeight;
     138       69907 :     }
     139     2446190 :     return height;
     140             : }
     141             : 
     142     1223095 : static bool CompareByLastPaid(const CDeterministicMN& _a, const CDeterministicMN& _b)
     143             : {
     144     1223095 :     int ah = CompareByLastPaid_GetHeight(_a);
     145     1223095 :     int bh = CompareByLastPaid_GetHeight(_b);
     146     1223095 :     if (ah == bh) {
     147        9865 :         return _a.proTxHash < _b.proTxHash;
     148             :     } else {
     149     1213230 :         return ah < bh;
     150             :     }
     151     1223095 : }
     152     1223095 : static bool CompareByLastPaid(const CDeterministicMN* _a, const CDeterministicMN* _b)
     153             : {
     154     1223095 :     return CompareByLastPaid(*_a, *_b);
     155             : }
     156             : 
     157      348142 : CDeterministicMNCPtr CDeterministicMNList::GetMNPayee(gsl::not_null<const CBlockIndex*> pindexPrev) const
     158             : {
     159      348142 :     if (mnMap.size() == 0) {
     160       79299 :         return nullptr;
     161             :     }
     162             : 
     163             :     // The flag is-v19-activate is used for optimization; we don't need to go over all masternodes every pre-v19 block
     164      268843 :     const bool isv19Active{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_V19)};
     165      268843 :     const bool isMNRewardReallocation{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_MN_RR)};
     166             :     // EvoNodes are rewarded 4 blocks in a row until MNRewardReallocation (Platform release)
     167             :     // For optimization purposes we also check if v19 active to avoid loop over all masternodes
     168      268843 :     CDeterministicMNCPtr best = nullptr;
     169      268843 :     if (isv19Active && !isMNRewardReallocation) {
     170      585424 :         ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     171      477311 :             if (dmn->pdmnState->nLastPaidHeight == nHeight) {
     172             :                 // We found the last MN Payee.
     173             :                 // If the last payee is an EvoNode, we need to check its consecutive payments and pay him again if needed
     174      106420 :                 if (dmn->nType == MnType::Evo && dmn->pdmnState->nConsecutivePayments < dmn_types::Evo.voting_weight) {
     175       17637 :                     best = dmn;
     176       17637 :                 }
     177      106420 :             }
     178      477311 :         });
     179             : 
     180      108113 :         if (best != nullptr) return best;
     181             : 
     182             :         // Note: If the last payee was a regular MN or if the payee is an EvoNode that was removed from the mnList then that's fine.
     183             :         // We can proceed with classic MN payee selection
     184       90476 :     }
     185             : 
     186     1723000 :     ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     187     1471794 :         if (best == nullptr || CompareByLastPaid(dmn.get(), best.get())) {
     188      550179 :             best = dmn;
     189      550179 :         }
     190     1471794 :     });
     191             : 
     192      251206 :     return best;
     193      616985 : }
     194             : 
     195         159 : std::vector<CDeterministicMNCPtr> CDeterministicMNList::GetProjectedMNPayees(gsl::not_null<const CBlockIndex* const> pindexPrev, int nCount) const
     196             : {
     197         159 :     if (nCount < 0 ) {
     198           0 :         return {};
     199             :     }
     200         159 :     const bool isMNRewardReallocation = DeploymentActiveAfter(pindexPrev, Params().GetConsensus(),
     201             :                                                               Consensus::DEPLOYMENT_MN_RR);
     202         159 :     const auto counts = GetCounts();
     203         159 :     const auto weighted_count = isMNRewardReallocation ? counts.enabled() : counts.m_valid_weighted;
     204         159 :     nCount = std::min(nCount, int(weighted_count));
     205             : 
     206         159 :     std::vector<CDeterministicMNCPtr> result;
     207         159 :     result.reserve(weighted_count);
     208             : 
     209         159 :     int remaining_evo_payments{0};
     210         159 :     CDeterministicMNCPtr evo_to_be_skipped{nullptr};
     211         159 :     if (!isMNRewardReallocation) {
     212         230 :         ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     213         192 :             if (dmn->pdmnState->nLastPaidHeight == nHeight) {
     214             :                 // We found the last MN Payee.
     215             :                 // If the last payee is an EvoNode, we need to check its consecutive payments and pay him again if needed
     216          38 :                 if (dmn->nType == MnType::Evo && dmn->pdmnState->nConsecutivePayments < dmn_types::Evo.voting_weight) {
     217           2 :                     remaining_evo_payments = dmn_types::Evo.voting_weight - dmn->pdmnState->nConsecutivePayments;
     218           7 :                     for ([[maybe_unused]] auto _ : util::irange(remaining_evo_payments)) {
     219           5 :                         result.emplace_back(dmn);
     220           5 :                         evo_to_be_skipped = dmn;
     221             :                     }
     222           2 :                 }
     223          38 :             }
     224         192 :         });
     225          38 :     }
     226             : 
     227         948 :     ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     228         789 :         if (dmn == evo_to_be_skipped) return;
     229        1592 :         for ([[maybe_unused]] auto _ : util::irange(isMNRewardReallocation ? 1 : GetMnType(dmn->nType).voting_weight)) {
     230         805 :             result.emplace_back(dmn);
     231             :         }
     232         789 :     });
     233             : 
     234         159 :     if (evo_to_be_skipped != nullptr) {
     235             :         // if EvoNode is in the middle of payments, add entries for already paid ones to the end of the list
     236           5 :         for ([[maybe_unused]] auto _ : util::irange(evo_to_be_skipped->pdmnState->nConsecutivePayments)) {
     237           3 :             result.emplace_back(evo_to_be_skipped);
     238             :         }
     239           2 :     }
     240             : 
     241        1521 :     std::sort(result.begin() + remaining_evo_payments, result.end(), [&](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) {
     242        1362 :         return CompareByLastPaid(a.get(), b.get());
     243             :     });
     244             : 
     245         159 :     result.resize(nCount);
     246             : 
     247         159 :     return result;
     248         318 : }
     249             : 
     250      344121 : gsl::not_null<std::shared_ptr<const CSimplifiedMNList>> CDeterministicMNList::to_sml() const
     251             : {
     252      344121 :     LOCK(m_cached_sml_mutex);
     253      344121 :     if (!m_cached_sml) {
     254        7675 :         std::vector<std::unique_ptr<CSimplifiedMNListEntry>> sml_entries;
     255        7675 :         sml_entries.reserve(mnMap.size());
     256             : 
     257       58536 :         ForEachMN(/*onlyValid=*/false, [&sml_entries](const auto& dmn) {
     258       50861 :             sml_entries.emplace_back(std::make_unique<CSimplifiedMNListEntry>(dmn.to_sml_entry()));
     259       50861 :         });
     260        7675 :         m_cached_sml = std::make_shared<CSimplifiedMNList>(std::move(sml_entries));
     261        7675 :     }
     262             : 
     263      344121 :     return m_cached_sml;
     264      344121 : }
     265             : 
     266         708 : int CDeterministicMNList::CalcMaxPoSePenalty() const
     267             : {
     268             :     // Maximum PoSe penalty is dynamic and equals the number of registered MNs
     269             :     // It's however at least 100.
     270             :     // This means that the max penalty is usually equal to a full payment cycle
     271         708 :     return std::max(100, (int)GetCounts().total());
     272             : }
     273             : 
     274         354 : int CDeterministicMNList::CalcPenalty(int percent) const
     275             : {
     276         354 :     assert(percent > 0);
     277         354 :     return (CalcMaxPoSePenalty() * percent) / 100;
     278             : }
     279             : 
     280         354 : void CDeterministicMNList::PoSePunish(const uint256& proTxHash, int penalty, bool debugLogs)
     281             : {
     282         354 :     assert(penalty > 0);
     283             : 
     284         354 :     auto dmn = GetMN(proTxHash);
     285         354 :     if (!dmn) {
     286           0 :         throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString())));
     287             :     }
     288             : 
     289         354 :     int maxPenalty = CalcMaxPoSePenalty();
     290             : 
     291         354 :     auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
     292         354 :     newState->nPoSePenalty += penalty;
     293         354 :     newState->nPoSePenalty = std::min(maxPenalty, newState->nPoSePenalty);
     294             : 
     295         354 :     if (!dmn->pdmnState->IsBanned()) {
     296         354 :         if (newState->nPoSePenalty >= maxPenalty) {
     297         153 :             newState->BanIfNotBanned(nHeight);
     298         153 :         }
     299         354 :         if (debugLogs) {
     300         354 :             LogPrintf("CDeterministicMNList::%s -- %s MN %s at height %d, penalty %d->%d (max=%d)\n", __func__,
     301             :                       newState->IsBanned() ? "banned" : "punished", proTxHash.ToString(), nHeight,
     302             :                       dmn->pdmnState->nPoSePenalty, newState->nPoSePenalty, maxPenalty);
     303         354 :         }
     304         354 :     }
     305         354 :     UpdateMN(proTxHash, newState);
     306         354 : }
     307             : 
     308      214461 : void CDeterministicMNList::DecreaseScores()
     309             : {
     310      214461 :     std::vector<CDeterministicMNCPtr> toDecrease;
     311      214461 :     toDecrease.reserve(GetCounts().total() / 10);
     312             :     // only iterate and decrease for valid ones (not PoSe banned yet)
     313             :     // if a MN ever reaches the maximum, it stays in PoSe banned state until revived
     314      975504 :     ForEachMNShared(/*onlyValid=*/true, [&toDecrease](const auto& dmn) {
     315             :         // There is no reason to check if this MN is banned here since onlyValid=true will only run on non-banned MNs
     316      761043 :         if (dmn->pdmnState->nPoSePenalty > 0) {
     317        4508 :             toDecrease.emplace_back(dmn);
     318        4508 :         }
     319      761043 :     });
     320             : 
     321      218969 :     for (const auto& proTxHash : toDecrease) {
     322        4508 :         PoSeDecrease(*proTxHash);
     323             :     }
     324      214461 : }
     325             : 
     326        4508 : void CDeterministicMNList::PoSeDecrease(const CDeterministicMN& dmn)
     327             : {
     328        4508 :     assert(dmn.pdmnState->nPoSePenalty > 0 && !dmn.pdmnState->IsBanned());
     329             : 
     330        4508 :     auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
     331        4508 :     newState->nPoSePenalty--;
     332        4508 :     UpdateMN(dmn, newState);
     333        4508 : }
     334             : 
     335      229021 : CDeterministicMNListDiff CDeterministicMNList::BuildDiff(const CDeterministicMNList& to) const
     336             : {
     337      229021 :     CDeterministicMNListDiff diffRet;
     338             : 
     339     1475676 :     for (const auto& p : to.mnMap) {
     340     1246655 :         const auto& toPtr = p.second;
     341     1246655 :         auto fromPtr = GetMN(toPtr->proTxHash);
     342     1246655 :         if (fromPtr == nullptr) {
     343        6168 :             diffRet.addedMNs.emplace_back(toPtr);
     344     1246655 :         } else if (fromPtr != toPtr || fromPtr->pdmnState != toPtr->pdmnState) {
     345      209707 :             CDeterministicMNStateDiff stateDiff(*fromPtr->pdmnState, *toPtr->pdmnState);
     346      209707 :             if (stateDiff.fields) {
     347      208371 :                 diffRet.updatedMNs.emplace(toPtr->GetInternalId(), std::move(stateDiff));
     348      208371 :             }
     349      209707 :         }
     350     1246655 :     }
     351      229021 :     if (mnMap.size() + diffRet.addedMNs.size() != to.mnMap.size()) {
     352        8603 :         for (auto& fromPtr : mnMap) {
     353        8603 :             const auto toPtr = to.GetMN(fromPtr.second->proTxHash);
     354        8603 :             if (toPtr == nullptr) {
     355        2468 :                 diffRet.removedMns.emplace(fromPtr.second->GetInternalId());
     356        2468 :                 if (mnMap.size() + diffRet.addedMNs.size() - diffRet.removedMns.size() == to.mnMap.size()) break;
     357           0 :             }
     358        8603 :         };
     359        2468 :     }
     360             : 
     361             :     // added MNs need to be sorted by internalId so that these are added in correct order when the diff is applied later
     362             :     // otherwise internalIds will not match with the original list
     363      236138 :     std::sort(diffRet.addedMNs.begin(), diffRet.addedMNs.end(), [](const CDeterministicMNCPtr& a, const CDeterministicMNCPtr& b) {
     364        7117 :         return a->GetInternalId() < b->GetInternalId();
     365             :     });
     366             : 
     367      229021 :     return diffRet;
     368      229021 : }
     369             : 
     370      533628 : void CDeterministicMNList::ApplyDiff(gsl::not_null<const CBlockIndex*> pindex, const CDeterministicMNListDiff& diff)
     371             : {
     372      533628 :     blockHash = pindex->GetBlockHash();
     373      533628 :     nHeight = pindex->nHeight;
     374             : 
     375      534350 :     for (const auto& id : diff.removedMns) {
     376         722 :         auto dmn = GetMNByInternalId(id);
     377         722 :         if (!dmn) {
     378           0 :             throw std::runtime_error(strprintf("%s: can't find a removed masternode, id=%d", __func__, id));
     379             :         }
     380         722 :         RemoveMN(dmn->proTxHash);
     381         722 :     }
     382      559644 :     for (const auto& dmn : diff.addedMNs) {
     383       26016 :         AddMN(dmn);
     384             :     }
     385      935129 :     for (const auto& p : diff.updatedMNs) {
     386      401501 :         auto dmn = GetMNByInternalId(p.first);
     387      401501 :         if (!dmn) {
     388           0 :             throw std::runtime_error(strprintf("%s: can't find an updated masternode, id=%d", __func__, p.first));
     389             :         }
     390      401501 :         UpdateMN(*dmn, p.second);
     391      401501 :     }
     392      533628 : }
     393             : 
     394       82668 : void CDeterministicMNList::AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount)
     395             : {
     396       82668 :     assert(dmn != nullptr);
     397             : 
     398       82668 :     if (mnMap.find(dmn->proTxHash)) {
     399        7108 :         throw(std::runtime_error(strprintf("%s: Can't add a masternode with a duplicate proTxHash=%s", __func__, dmn->proTxHash.ToString())));
     400             :     }
     401       75560 :     if (mnInternalIdMap.find(dmn->GetInternalId())) {
     402           0 :         throw(std::runtime_error(strprintf("%s: Can't add a masternode with a duplicate internalId=%d", __func__, dmn->GetInternalId())));
     403             :     }
     404             : 
     405             :     // All mnUniquePropertyMap's updates must be atomic.
     406             :     // Using this temporary map as a checkpoint to roll back to in case of any issues.
     407       75560 :     decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap;
     408             : 
     409       75560 :     if (!AddUniqueProperty(*dmn, dmn->collateralOutpoint)) {
     410           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     411           0 :         throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate collateralOutpoint=%s", __func__,
     412           0 :                 dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort())));
     413             :     }
     414      151171 :     for (const auto& entry : dmn->pdmnState->netInfo->GetEntries()) {
     415      151222 :         if (const auto service_opt{entry.GetAddrPort()}) {
     416       75611 :             if (!AddUniqueProperty(*dmn, *service_opt)) {
     417           0 :                 mnUniquePropertyMap = mnUniquePropertyMapSaved;
     418           0 :                 throw std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate address=%s", __func__,
     419           0 :                                                    dmn->proTxHash.ToString(), service_opt->ToStringAddrPort()));
     420             :             }
     421       75611 :         } else if (const auto domain_opt{entry.GetDomainPort()}) {
     422           0 :             if (!AddUniqueProperty(*dmn, *domain_opt)) {
     423           0 :                 mnUniquePropertyMap = mnUniquePropertyMapSaved;
     424           0 :                 throw std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate address=%s",
     425           0 :                                                    __func__, dmn->proTxHash.ToString(), domain_opt->ToStringAddrPort()));
     426             :             }
     427           0 :         } else {
     428           0 :             mnUniquePropertyMap = mnUniquePropertyMapSaved;
     429           0 :             throw std::runtime_error(
     430           0 :                 strprintf("%s: Can't add a masternode %s with invalid address", __func__, dmn->proTxHash.ToString()));
     431             :         }
     432             :     }
     433       75560 :     if (!AddUniqueProperty(*dmn, dmn->pdmnState->keyIDOwner)) {
     434           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     435           0 :         throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate keyIDOwner=%s", __func__,
     436           0 :                 dmn->proTxHash.ToString(), EncodeDestination(PKHash(dmn->pdmnState->keyIDOwner)))));
     437             :     }
     438       75560 :     if (dmn->pdmnState->pubKeyOperator != CBLSLazyPublicKey() && !AddUniqueProperty(*dmn, dmn->pdmnState->pubKeyOperator)) {
     439           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     440           0 :         throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate pubKeyOperator=%s", __func__,
     441           0 :                 dmn->proTxHash.ToString(), dmn->pdmnState->pubKeyOperator.ToString())));
     442             :     }
     443             : 
     444       75560 :     if (dmn->nType == MnType::Evo) {
     445        6885 :         if (dmn->pdmnState->platformNodeID != uint160() && !AddUniqueProperty(*dmn, dmn->pdmnState->platformNodeID)) {
     446           0 :             mnUniquePropertyMap = mnUniquePropertyMapSaved;
     447           0 :             throw(std::runtime_error(strprintf("%s: Can't add a masternode %s with a duplicate platformNodeID=%s", __func__,
     448           0 :                                                dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
     449             :         }
     450        6885 :     }
     451             : 
     452       75560 :     mnMap = mnMap.set(dmn->proTxHash, dmn);
     453       75560 :     mnInternalIdMap = mnInternalIdMap.set(dmn->GetInternalId(), dmn->proTxHash);
     454       75560 :     InvalidateSMLCache();
     455       75560 :     if (fBumpTotalCount) {
     456             :         // nTotalRegisteredCount acts more like a checkpoint, not as a limit,
     457       72502 :         nTotalRegisteredCount = std::max(dmn->GetInternalId() + 1, (uint64_t)nTotalRegisteredCount);
     458       72502 :     }
     459       82668 : }
     460             : 
     461      550296 : void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
     462             : {
     463      550296 :     auto dmn = std::make_shared<CDeterministicMN>(oldDmn);
     464      550296 :     auto oldState = dmn->pdmnState;
     465             : 
     466             :     // All mnUniquePropertyMap's updates must be atomic.
     467             :     // Using this temporary map as a checkpoint to roll back to in case of any issues.
     468      550296 :     decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap;
     469             : 
     470     1100592 :     auto updateNetInfo = [this](const CDeterministicMN& dmn, const std::shared_ptr<NetInfoInterface>& oldInfo,
     471             :                                 const std::shared_ptr<NetInfoInterface>& newInfo) -> std::string {
     472      550296 :         if (util::shared_ptr_not_equal(oldInfo, newInfo)) {
     473             :             // We track each individual entry in netInfo as opposed to netInfo itself (preventing us from
     474             :             // using UpdateUniqueProperty()), so we need to successfully purge all old entries and insert
     475             :             // new entries to successfully update.
     476        7134 :             for (const auto& old_entry : oldInfo->GetEntries()) {
     477        7250 :                 if (const auto service_opt{old_entry.GetAddrPort()}) {
     478        3625 :                     if (!DeleteUniqueProperty(dmn, *service_opt)) {
     479           0 :                         return "internal error"; // This shouldn't be possible
     480             :                     }
     481        3625 :                 } else if (const auto domain_opt{old_entry.GetDomainPort()}) {
     482           0 :                     if (!DeleteUniqueProperty(dmn, *domain_opt)) {
     483           0 :                         return "internal error"; // This shouldn't be possible
     484             :                     }
     485           0 :                 } else {
     486           0 :                     return "invalid address";
     487             :                 }
     488             :             }
     489        6977 :             for (const auto& new_entry : newInfo->GetEntries()) {
     490        6936 :                 if (const auto service_opt{new_entry.GetAddrPort()}) {
     491        3448 :                     if (!AddUniqueProperty(dmn, *service_opt)) {
     492           0 :                         return strprintf("duplicate (%s)", service_opt->ToStringAddrPort());
     493             :                     }
     494        3488 :                 } else if (const auto domain_opt{new_entry.GetDomainPort()}) {
     495          20 :                     if (!AddUniqueProperty(dmn, *domain_opt)) {
     496           0 :                         return strprintf("duplicate (%s)", domain_opt->ToStringAddrPort());
     497             :                     }
     498          20 :                 } else {
     499           0 :                     return "invalid address";
     500             :                 }
     501             :             }
     502        3509 :         }
     503      550296 :         return "";
     504      550296 :     };
     505             : 
     506      550296 :     assert(oldState->netInfo && pdmnState->netInfo);
     507      550296 :     if (auto err = updateNetInfo(*dmn, oldState->netInfo, pdmnState->netInfo); !err.empty()) {
     508           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     509           0 :         throw(std::runtime_error(strprintf("%s: Can't update masternode %s with addresses, reason=%s", __func__,
     510           0 :                                            oldDmn.proTxHash.ToString(), err)));
     511             :     }
     512      550296 :     if (!UpdateUniqueProperty(*dmn, oldState->keyIDOwner, pdmnState->keyIDOwner)) {
     513           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     514           0 :         throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate keyIDOwner=%s", __func__,
     515           0 :                 oldDmn.proTxHash.ToString(), EncodeDestination(PKHash(pdmnState->keyIDOwner)))));
     516             :     }
     517      550296 :     if (!UpdateUniqueProperty(*dmn, oldState->pubKeyOperator, pdmnState->pubKeyOperator)) {
     518           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     519           0 :         throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate pubKeyOperator=%s", __func__,
     520           0 :                 oldDmn.proTxHash.ToString(), pdmnState->pubKeyOperator.ToString())));
     521             :     }
     522      550296 :     if (dmn->nType == MnType::Evo) {
     523       78072 :         if (!UpdateUniqueProperty(*dmn, oldState->platformNodeID, pdmnState->platformNodeID)) {
     524           0 :             mnUniquePropertyMap = mnUniquePropertyMapSaved;
     525           0 :             throw(std::runtime_error(strprintf("%s: Can't update a masternode %s with a duplicate platformNodeID=%s", __func__,
     526           0 :                                                oldDmn.proTxHash.ToString(), pdmnState->platformNodeID.ToString())));
     527             :         }
     528       78072 :     }
     529             : 
     530      550296 :     dmn->pdmnState = pdmnState;
     531      550296 :     mnMap = mnMap.set(oldDmn.proTxHash, dmn);
     532      550296 :     LOCK(m_cached_sml_mutex);
     533      845714 :     if (m_cached_sml && oldDmn.to_sml_entry() != dmn->to_sml_entry()) {
     534        5440 :         m_cached_sml = nullptr;
     535        5440 :     }
     536      550296 : }
     537             : 
     538      144283 : void CDeterministicMNList::UpdateMN(const uint256& proTxHash, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
     539             : {
     540      144283 :     auto oldDmn = mnMap.find(proTxHash);
     541      144283 :     if (!oldDmn) {
     542           0 :         throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString())));
     543             :     }
     544      144283 :     UpdateMN(**oldDmn, pdmnState);
     545      144283 : }
     546             : 
     547      401501 : void CDeterministicMNList::UpdateMN(const CDeterministicMN& oldDmn, const CDeterministicMNStateDiff& stateDiff)
     548             : {
     549      401501 :     auto oldState = oldDmn.pdmnState;
     550      401501 :     auto newState = std::make_shared<CDeterministicMNState>(*oldState);
     551      401501 :     stateDiff.ApplyToState(*newState);
     552      401501 :     UpdateMN(oldDmn, newState);
     553      401501 : }
     554             : 
     555        1285 : void CDeterministicMNList::RemoveMN(const uint256& proTxHash)
     556             : {
     557        1285 :     auto dmn = GetMN(proTxHash);
     558        1285 :     if (!dmn) {
     559           0 :         throw(std::runtime_error(strprintf("%s: Can't find a masternode with proTxHash=%s", __func__, proTxHash.ToString())));
     560             :     }
     561             : 
     562             :     // All mnUniquePropertyMap's updates must be atomic.
     563             :     // Using this temporary map as a checkpoint to roll back to in case of any issues.
     564        1285 :     decltype(mnUniquePropertyMap) mnUniquePropertyMapSaved = mnUniquePropertyMap;
     565             : 
     566        1285 :     if (!DeleteUniqueProperty(*dmn, dmn->collateralOutpoint)) {
     567           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     568           0 :         throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a collateralOutpoint=%s", __func__,
     569           0 :                 proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort())));
     570             :     }
     571        2574 :     for (const auto& entry : dmn->pdmnState->netInfo->GetEntries()) {
     572        2578 :         if (const auto service_opt{entry.GetAddrPort()}) {
     573        1289 :             if (!DeleteUniqueProperty(*dmn, *service_opt)) {
     574           0 :                 mnUniquePropertyMap = mnUniquePropertyMapSaved;
     575           0 :                 throw std::runtime_error(strprintf("%s: Can't delete a masternode %s with an address=%s", __func__,
     576           0 :                                                    proTxHash.ToString(), service_opt->ToStringAddrPort()));
     577             :             }
     578        1289 :         } else if (const auto domain_opt{entry.GetDomainPort()}) {
     579           0 :             if (!DeleteUniqueProperty(*dmn, *domain_opt)) {
     580           0 :                 mnUniquePropertyMap = mnUniquePropertyMapSaved;
     581           0 :                 throw std::runtime_error(strprintf("%s: Can't delete a masternode %s with an address=%s", __func__,
     582           0 :                                                    proTxHash.ToString(), domain_opt->ToStringAddrPort()));
     583             :             }
     584           0 :         } else {
     585           0 :             mnUniquePropertyMap = mnUniquePropertyMapSaved;
     586           0 :             throw std::runtime_error(strprintf("%s: Can't delete a masternode %s with invalid address", __func__,
     587           0 :                                                dmn->proTxHash.ToString()));
     588             :         }
     589             :     }
     590        1285 :     if (!DeleteUniqueProperty(*dmn, dmn->pdmnState->keyIDOwner)) {
     591           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     592           0 :         throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a keyIDOwner=%s", __func__,
     593           0 :                 proTxHash.ToString(), EncodeDestination(PKHash(dmn->pdmnState->keyIDOwner)))));
     594             :     }
     595        2567 :     if (dmn->pdmnState->pubKeyOperator != CBLSLazyPublicKey() &&
     596        1282 :         !DeleteUniqueProperty(*dmn, dmn->pdmnState->pubKeyOperator)) {
     597           0 :         mnUniquePropertyMap = mnUniquePropertyMapSaved;
     598           0 :         throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a pubKeyOperator=%s", __func__,
     599           0 :                 proTxHash.ToString(), dmn->pdmnState->pubKeyOperator.ToString())));
     600             :     }
     601             : 
     602        1285 :     if (dmn->nType == MnType::Evo) {
     603         100 :         if (dmn->pdmnState->platformNodeID != uint160() && !DeleteUniqueProperty(*dmn, dmn->pdmnState->platformNodeID)) {
     604           0 :             mnUniquePropertyMap = mnUniquePropertyMapSaved;
     605           0 :             throw(std::runtime_error(strprintf("%s: Can't delete a masternode %s with a platformNodeID=%s", __func__,
     606           0 :                                                dmn->proTxHash.ToString(), dmn->pdmnState->platformNodeID.ToString())));
     607             :         }
     608         100 :     }
     609             : 
     610        1285 :     mnMap = mnMap.erase(proTxHash);
     611        1285 :     mnInternalIdMap = mnInternalIdMap.erase(dmn->GetInternalId());
     612        1285 :     InvalidateSMLCache();
     613        1285 : }
     614             : 
     615        6126 : CDeterministicMNManager::CDeterministicMNManager(CEvoDB& evoDb, CMasternodeMetaMan& mn_metaman) :
     616             :     m_evoDb{evoDb},
     617             :     m_mn_metaman{mn_metaman}
     618        3063 : {
     619        3063 : }
     620             : 
     621        6126 : CDeterministicMNManager::~CDeterministicMNManager() = default;
     622             : 
     623      129650 : bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex,
     624             :                                            BlockValidationState& state, const CDeterministicMNList& newList,
     625             :                                            std::optional<MNListUpdates>& updatesRet)
     626             : {
     627      129650 :     AssertLockHeld(::cs_main);
     628             : 
     629      129650 :     const auto& consensusParams = Params().GetConsensus();
     630      129650 :     if (!DeploymentActiveAt(*pindex, consensusParams, Consensus::DEPLOYMENT_DIP0003)) {
     631           0 :         return true;
     632             :     }
     633             : 
     634      129650 :     CDeterministicMNList oldList;
     635      129650 :     CDeterministicMNListDiff diff;
     636             : 
     637      129650 :     int nHeight = pindex->nHeight;
     638             : 
     639             :     try {
     640      129650 :         newList.to_sml(); // to populate the SML cache
     641             : 
     642      129638 :         LOCK(cs);
     643             : 
     644      129650 :         oldList = GetListForBlockInternal(pindex->pprev);
     645      129638 :         diff = oldList.BuildDiff(newList);
     646             : 
     647             :         // apply platform unban for platform revive too
     648      317866 :         for (int i = 1; i < (int)block.vtx.size(); i++) {
     649      188228 :             const CTransaction& tx = *block.vtx[i];
     650      188228 :             if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_PROVIDER_UPDATE_SERVICE) {
     651             :                 // only interested in revive transactions
     652      186065 :                 continue;
     653             :             }
     654        2163 :             const auto opt_proTx = GetTxPayload<CProUpServTx>(tx);
     655        2163 :             if (!opt_proTx) continue; // should not happen but does not matter
     656             : 
     657        2163 :             if (!m_mn_metaman.ResetPlatformBan(opt_proTx->proTxHash, nHeight)) {
     658        2112 :                 LogPrint(BCLog::LLMQ, "%s -- MN %s is failed to Platform revived at height %d\n", __func__,
     659             :                          opt_proTx->proTxHash.ToString(), nHeight);
     660        2112 :             }
     661        2163 :         }
     662             : 
     663      129638 :         m_evoDb.Write(std::make_pair(DB_LIST_DIFF, newList.GetBlockHash()), diff);
     664      129638 :         if ((nHeight % DISK_SNAPSHOT_PERIOD) == 0 || pindex->pprev == m_initial_snapshot_index) {
     665          56 :             m_evoDb.Write(std::make_pair(DB_LIST_SNAPSHOT, newList.GetBlockHash()), newList);
     666          62 :             mnListsCache.emplace(newList.GetBlockHash(), newList);
     667          62 :             LogPrintf("CDeterministicMNManager::%s -- Wrote snapshot. nHeight=%d, mapCurMNs.allMNsCount=%d\n",
     668             :                 __func__, nHeight, newList.GetCounts().total());
     669          62 :         }
     670             : 
     671      129644 :         diff.nHeight = pindex->nHeight;
     672      129638 :         mnListDiffsCache.emplace(pindex->GetBlockHash(), diff);
     673      129638 :         mnListsCache.emplace(newList.GetBlockHash(), newList);
     674      129638 :     } catch (const std::exception& e) {
     675           0 :         LogPrintf("CDeterministicMNManager::%s -- internal error: %s\n", __func__, e.what());
     676           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-dmn-block");
     677           0 :     }
     678             : 
     679      129638 :     if (diff.HasChanges()) {
     680       97741 :         updatesRet = {.old_list = oldList, .new_list = newList, .diff = diff};
     681       97741 :     }
     682             : 
     683      129638 :     if (::g_stats_client->active()) {
     684           0 :         const auto counts{newList.GetCounts()};
     685           0 :         ::g_stats_client->gauge("masternodes.count", counts.total());
     686           0 :         ::g_stats_client->gauge("masternodes.weighted_count", counts.m_total_weighted);
     687           0 :         ::g_stats_client->gauge("masternodes.enabled", counts.enabled());
     688           0 :         ::g_stats_client->gauge("masternodes.weighted_enabled", counts.m_valid_weighted);
     689           0 :         ::g_stats_client->gauge("masternodes.evo.count", counts.m_total_evo);
     690           0 :         ::g_stats_client->gauge("masternodes.evo.enabled", counts.m_valid_evo);
     691           0 :         ::g_stats_client->gauge("masternodes.mn.count", counts.m_total_mn);
     692           0 :         ::g_stats_client->gauge("masternodes.mn.enabled", counts.m_valid_mn);
     693           0 :     }
     694             : 
     695      129638 :     if (nHeight == consensusParams.DIP0003EnforcementHeight) {
     696         219 :         if (!consensusParams.DIP0003EnforcementHash.IsNull() && consensusParams.DIP0003EnforcementHash != pindex->GetBlockHash()) {
     697           0 :             LogPrintf("CDeterministicMNManager::%s -- DIP3 enforcement block has wrong hash: hash=%s, expected=%s, nHeight=%d\n", __func__,
     698             :                     pindex->GetBlockHash().ToString(), consensusParams.DIP0003EnforcementHash.ToString(), nHeight);
     699           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-dip3-enf-block");
     700             :         }
     701         219 :         LogPrintf("CDeterministicMNManager::%s -- DIP3 is enforced now. nHeight=%d\n", __func__, nHeight);
     702         219 :     }
     703      129638 :     int current = to_cleanup.load();
     704      129638 :     while (nHeight > current && !to_cleanup.compare_exchange_weak(current, nHeight)) {
     705             :         // Loop continues if compare_exchange_weak failed (another thread changed it) (current is updated to the new value in to_cleanup)
     706             :     }
     707      129638 :     return true;
     708      129686 : }
     709             : 
     710       26566 : bool CDeterministicMNManager::UndoBlock(gsl::not_null<const CBlockIndex*> pindex, std::optional<MNListUpdates>& updatesRet)
     711             : {
     712       26566 :     int nHeight = pindex->nHeight;
     713       26566 :     uint256 blockHash = pindex->GetBlockHash();
     714             : 
     715       26566 :     CDeterministicMNList prevList;
     716       26566 :     CDeterministicMNListDiff diff;
     717             :     {
     718       26566 :         LOCK(cs);
     719       26566 :         m_evoDb.Read(std::make_pair(DB_LIST_DIFF, blockHash), diff);
     720             : 
     721       26566 :         if (diff.HasChanges()) {
     722             :             // need to call this before erasing
     723        8080 :             prevList = GetListForBlockInternal(pindex->pprev);
     724        8080 :         }
     725             : 
     726       26566 :         mnListsCache.erase(blockHash);
     727       26566 :         mnListDiffsCache.erase(blockHash);
     728       26566 :     }
     729       26566 :     if (diff.HasChanges()) {
     730        8080 :         CDeterministicMNList curList{prevList};
     731        8080 :         curList.ApplyDiff(pindex, diff);
     732             : 
     733        8080 :         auto inversedDiff{curList.BuildDiff(prevList)};
     734        8080 :         updatesRet = {.old_list = curList, .new_list = prevList, .diff = inversedDiff};
     735        8080 :     }
     736             : 
     737       26566 :     const auto& consensusParams = Params().GetConsensus();
     738       26566 :     if (nHeight == consensusParams.DIP0003EnforcementHeight) {
     739           0 :         LogPrintf("CDeterministicMNManager::%s -- DIP3 is not enforced anymore. nHeight=%d\n", __func__, nHeight);
     740           0 :     }
     741             : 
     742             :     return true;
     743       26566 : }
     744             : 
     745      224882 : void CDeterministicMNManager::UpdatedBlockTip(gsl::not_null<const CBlockIndex*> pindex)
     746             : {
     747      224882 :     LOCK(cs);
     748             : 
     749      224882 :     tipIndex = pindex;
     750      224882 : }
     751             : 
     752     2499166 : CDeterministicMNList CDeterministicMNManager::GetListForBlockInternal(gsl::not_null<const CBlockIndex*> pindex)
     753             : {
     754     2499166 :     CDeterministicMNList snapshot;
     755             : 
     756     2499166 :     if (!DeploymentActiveAt(*pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
     757      478438 :         return snapshot;
     758             :     }
     759             : 
     760     2020728 :     AssertLockHeld(cs);
     761             : 
     762     2020728 :     std::list<const CBlockIndex*> listDiffIndexes;
     763             : 
     764     2541657 :     while (true) {
     765             :         // try using cache before reading from disk
     766     2541657 :         auto itLists = mnListsCache.find(pindex->GetBlockHash());
     767     2541657 :         if (itLists != mnListsCache.end()) {
     768     2019839 :             snapshot = itLists->second;
     769     2019839 :             break;
     770             :         }
     771             : 
     772      521818 :         if (m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, pindex->GetBlockHash()), snapshot)) {
     773          35 :             mnListsCache.emplace(pindex->GetBlockHash(), snapshot);
     774          35 :             break;
     775             :         }
     776             : 
     777             :         // no snapshot found yet, check diffs
     778      521783 :         auto itDiffs = mnListDiffsCache.find(pindex->GetBlockHash());
     779      521783 :         if (itDiffs != mnListDiffsCache.end()) {
     780      394583 :             listDiffIndexes.emplace_front(pindex);
     781      394583 :             pindex = pindex->pprev;
     782      394583 :             continue;
     783             :         }
     784             : 
     785      127200 :         CDeterministicMNListDiff diff;
     786      127200 :         if (!m_evoDb.Read(std::make_pair(DB_LIST_DIFF, pindex->GetBlockHash()), diff)) {
     787             :             // no snapshot and no diff on disk means that it's the initial snapshot
     788         854 :             m_initial_snapshot_index = pindex;
     789         854 :             snapshot = CDeterministicMNList(pindex->GetBlockHash(), pindex->nHeight, 0);
     790         854 :             mnListsCache.emplace(pindex->GetBlockHash(), snapshot);
     791         854 :             LogPrintf("CDeterministicMNManager::%s -- initial snapshot. blockHash=%s nHeight=%d\n",
     792             :                     __func__, snapshot.GetBlockHash().ToString(), snapshot.GetHeight());
     793         854 :             break;
     794             :         }
     795             : 
     796      126346 :         diff.nHeight = pindex->nHeight;
     797      126346 :         mnListDiffsCache.emplace(pindex->GetBlockHash(), std::move(diff));
     798      126346 :         listDiffIndexes.emplace_front(pindex);
     799      126346 :         pindex = pindex->pprev;
     800      127200 :     }
     801             : 
     802     2541657 :     for (const auto& diffIndex : listDiffIndexes) {
     803      520929 :         const auto& diff = mnListDiffsCache.at(diffIndex->GetBlockHash());
     804      520929 :         snapshot.ApplyDiff(diffIndex, diff);
     805             : 
     806             :         static constexpr int MINI_SNAPSHOT_INTERVAL = 32;
     807      520929 :         if (!node::fReindex && snapshot.GetHeight() % MINI_SNAPSHOT_INTERVAL == 0) {
     808             :             // Add this temporary mini-snapshot to the cache.
     809             :             // Persistent masternode list snapshots are stored in evo-db every 576 blocks.
     810             :             // To answer GetListForBlock() between these snapshots, the node must rebuild
     811             :             // state by applying up to 575 diffs from the nearest persistent snapshot.
     812             :             // If GetListForBlock() is called repeatedly in that range, the work multiplies
     813             :             // (up to 575 diffs * number of calls).
     814             :             // Mini-snapshots reduce this overhead by caching intermediate states
     815             :             // every MINI_SNAPSHOT_INTERVAL blocks. Unlike persistent snapshots, these live
     816             :             // only in memory and are cleaned up after a short time by the scheduled cleanup().
     817             :             // There is also separate in-memory caching for the current tip and active quorums,
     818             :             // but this mini-snapshot cache specifically speeds up repeated requests
     819             :             // for nearby historical blocks.
     820        8660 :             mnListsCache.emplace(snapshot.GetBlockHash(), snapshot);
     821        8660 :         }
     822             :     }
     823             : 
     824     2020728 :     if (tipIndex) {
     825             :         // always keep a snapshot for the tip
     826     1961641 :         if (const auto snapshot_hash = snapshot.GetBlockHash(); snapshot_hash == tipIndex->GetBlockHash()) {
     827     1827612 :             mnListsCache.emplace(snapshot_hash, snapshot);
     828     1827612 :         } else {
     829             :             // keep snapshots for yet alive quorums
     830      786514 :             if (std::ranges::any_of(Params().GetConsensus().llmqs, [&snapshot, this](
     831             :                                                                        const auto& params) EXCLUSIVE_LOCKS_REQUIRED(cs) {
     832      652485 :                     AssertLockHeld(cs);
     833      657290 :                     return (snapshot.GetHeight() % params.dkgInterval == 0) &&
     834        9610 :                            (snapshot.GetHeight() + params.dkgInterval * (params.keepOldConnections + 1) >=
     835        4805 :                             tipIndex->nHeight);
     836             :                 })) {
     837        4421 :                 mnListsCache.emplace(snapshot_hash, snapshot);
     838        4421 :             }
     839             :         }
     840     1961641 :     }
     841             : 
     842     2020728 :     assert(snapshot.GetHeight() != -1);
     843     2020728 :     return snapshot;
     844     2499166 : }
     845             : 
     846     1655062 : CDeterministicMNList CDeterministicMNManager::GetListAtChainTip()
     847             : {
     848     1655062 :     LOCK(cs);
     849     1655062 :     if (!tipIndex) {
     850       56618 :         return {};
     851             :     }
     852     1598444 :     return GetListForBlockInternal(tipIndex);
     853     1655062 : }
     854             : 
     855      230483 : bool CDeterministicMNManager::IsProTxWithCollateral(const CTransactionRef& tx, uint32_t n)
     856             : {
     857      230483 :     if (!tx->IsSpecialTxVersion() || tx->nType != TRANSACTION_PROVIDER_REGISTER) {
     858      229241 :         return false;
     859             :     }
     860        1242 :     const auto opt_proTx = GetTxPayload<CProRegTx>(*tx);
     861        1242 :     if (!opt_proTx) {
     862           0 :         return false;
     863             :     }
     864        1242 :     auto& proTx = *opt_proTx;
     865             : 
     866        1242 :     if (!proTx.collateralOutpoint.hash.IsNull()) {
     867         246 :         return false;
     868             :     }
     869         996 :     if (proTx.collateralOutpoint.n >= tx->vout.size() || proTx.collateralOutpoint.n != n) {
     870         325 :         return false;
     871             :     }
     872             : 
     873             : 
     874         671 :     if (const CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount; tx->vout[n].nValue != expectedCollateral) {
     875           0 :         return false;
     876             :     }
     877         671 :     return true;
     878      230483 : }
     879             : 
     880        8710 : void CDeterministicMNManager::CleanupCache(int nHeight)
     881             : {
     882        8710 :     AssertLockHeld(cs);
     883             : 
     884        8710 :     std::vector<uint256> toDeleteLists;
     885        8710 :     std::vector<uint256> toDeleteDiffs;
     886      168011 :     for (const auto& p : mnListsCache) {
     887      159301 :         if (p.second.GetHeight() + LIST_DIFFS_CACHE_SIZE < nHeight) {
     888             :             // too old, drop it
     889           0 :             toDeleteLists.emplace_back(p.first);
     890           0 :             continue;
     891             :         }
     892      318602 :         if (tipIndex != nullptr && p.first == tipIndex->GetBlockHash()) {
     893             :             // it's a snapshot for the tip, keep it
     894        8710 :             continue;
     895             :         }
     896      786283 :         bool fQuorumCache = std::ranges::any_of(Params().GetConsensus().llmqs, [&nHeight, &p](const auto& params) {
     897      696719 :             return (p.second.GetHeight() % params.dkgInterval == 0) &&
     898       61027 :                    (p.second.GetHeight() + params.dkgInterval * (params.keepOldConnections + 1) >= nHeight);
     899             :         });
     900      150591 :         if (fQuorumCache) {
     901             :             // at least one quorum could be using it, keep it
     902       33089 :             continue;
     903             :         }
     904             :         // none of the above, drop it
     905      117502 :         toDeleteLists.emplace_back(p.first);
     906             :     }
     907      126212 :     for (const auto& h : toDeleteLists) {
     908      117502 :         mnListsCache.erase(h);
     909             :     }
     910     2250645 :     for (const auto& p : mnListDiffsCache) {
     911     2241935 :         if (p.second.nHeight + LIST_DIFFS_CACHE_SIZE < nHeight) {
     912           0 :             toDeleteDiffs.emplace_back(p.first);
     913           0 :         }
     914             :     }
     915        8710 :     for (const auto& h : toDeleteDiffs) {
     916           0 :         mnListDiffsCache.erase(h);
     917             :     }
     918             : 
     919        8710 : }
     920             : 
     921             : //end
     922             : 
     923       15672 : void CDeterministicMNManager::DoMaintenance() {
     924       15672 :     LOCK(cs_cleanup);
     925       15672 :     int loc_to_cleanup = to_cleanup.load();
     926       15672 :     if (loc_to_cleanup <= did_cleanup) return;
     927        8710 :     LOCK(cs);
     928        8710 :     CleanupCache(loc_to_cleanup);
     929        8710 :     did_cleanup = loc_to_cleanup;
     930       15672 : }
     931             : 
     932        3049 : bool CDeterministicMNManager::IsMigrationRequired() const
     933             : {
     934             :     // Check if there are any legacy format diffs in the database
     935             :     // by looking for DB_LIST_DIFF_LEGACY entries
     936             : 
     937        3049 :     AssertLockHeld(::cs_main);
     938             : 
     939        3049 :     std::unique_ptr<CDBIterator> pcursor{m_evoDb.GetRawDB().NewIterator()};
     940        3049 :     auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256{})};
     941        3049 :     pcursor->Seek(start);
     942             : 
     943             :     // If we find any entries with the legacy key, migration is needed
     944        3049 :     if (pcursor->Valid()) {
     945        1724 :         decltype(start) k;
     946        1724 :         if (pcursor->GetKey(k) && std::get<0>(k) == DB_LIST_DIFF_LEGACY) {
     947           0 :             LogPrintf("CDeterministicMNManager::%s -- Migration to nVersion-first format is needed\n", __func__);
     948           0 :             pcursor.reset();
     949           0 :             return true;
     950             :         }
     951        1724 :     }
     952             : 
     953        3049 :     LogPrintf("CDeterministicMNManager::%s -- Migration to nVersion-first format is not needed\n", __func__);
     954        3049 :     pcursor.reset();
     955        3049 :     return false; // No legacy format found
     956        3049 : }
     957             : 
     958           0 : bool CDeterministicMNManager::MigrateLegacyDiffs(const CBlockIndex* const tip_index)
     959             : {
     960             :     // CRITICAL: This migration converts ALL stored CDeterministicMNListDiff entries
     961             :     // from legacy database key (DB_LIST_DIFF_LEGACY) to new key (DB_LIST_DIFF) format
     962             : 
     963           0 :     AssertLockHeld(::cs_main);
     964             : 
     965           0 :     LogPrintf("CDeterministicMNManager::%s -- Starting migration to nVersion-first format\n", __func__);
     966             : 
     967           0 :     std::vector<const CBlockIndex*> keys_to_erase;
     968             : 
     969           0 :     CDBBatch batch(m_evoDb.GetRawDB());
     970           0 :     std::unique_ptr<CDBIterator> pcursor{m_evoDb.GetRawDB().NewIterator()};
     971             : 
     972             :     // Keep track of the list to get correct nVersion at the current height
     973           0 :     CDeterministicMNList snapshot;
     974             : 
     975           0 :     const auto start_height{Params().GetConsensus().DeploymentHeight(Consensus::DEPLOYMENT_DIP0003)};
     976           0 :     for (auto current_height : std::views::iota(start_height, tip_index->nHeight + 1)) {
     977           0 :         auto current_index = tip_index->GetAncestor(current_height);
     978           0 :         auto target_key{std::make_tuple(DB_LIST_DIFF_LEGACY, current_index->GetBlockHash())};
     979           0 :         pcursor->Seek(target_key);
     980             : 
     981           0 :         decltype(target_key) key;
     982           0 :         if (!pcursor->Valid() || !pcursor->GetKey(key) || std::get<0>(key) != DB_LIST_DIFF_LEGACY) {
     983           0 :             break;
     984             :         }
     985             : 
     986           0 :         if (std::get<1>(key) != current_index->GetBlockHash()) {
     987           0 :             throw std::ios_base::failure("Invalid data, we must have legacy diffs for each height");
     988             :         }
     989             : 
     990             :         // Use legacy-aware deserialization for DB_LIST_DIFF_LEGACY entries
     991           0 :         CDataStream s(SER_DISK, CLIENT_VERSION);
     992           0 :         if (!m_evoDb.GetRawDB().ReadDataStream(key, s)) {
     993           0 :             break;
     994             :         }
     995             : 
     996           0 :         CDeterministicMNListDiff legacyDiff;
     997           0 :         legacyDiff.UnserializeLegacyFormat(s); // Use legacy format deserializer
     998           0 :         snapshot.ApplyDiff(current_index, legacyDiff);
     999             : 
    1000           0 :         CDeterministicMNListDiff convertedDiff;
    1001           0 :         convertedDiff.addedMNs = legacyDiff.addedMNs;
    1002           0 :         convertedDiff.removedMns = legacyDiff.removedMns;
    1003             : 
    1004             :         // The conversion is already done by UnserializeLegacyFormat()!
    1005             :         // CDeterministicMNStateDiffLegacy.ToNewFormat() was called during deserialization
    1006             :         // So legacyDiff.updatedMNs already contains properly converted CDeterministicMNStateDiff objects
    1007             : 
    1008             :         // Copy the already-converted state diffs but make sure pubKeyOperator, nVersion and fields are set properly
    1009           0 :         for (auto& [internalId, stateDiff] : legacyDiff.updatedMNs) {
    1010           0 :             auto dmn = snapshot.GetMNByInternalId(internalId);
    1011           0 :             if (!dmn) {
    1012             :                 // shouldn't happen
    1013           0 :                 throw std::runtime_error(strprintf("%s: can't find an updated masternode, id=%d", __func__, internalId));
    1014             :             }
    1015           0 :             if (!(stateDiff.fields & CDeterministicMNStateDiff::Field_nVersion)) {
    1016           0 :                 if ((stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) ||
    1017           0 :                     (stateDiff.fields & CDeterministicMNStateDiff::Field_netInfo)) {
    1018           0 :                     stateDiff.fields |= CDeterministicMNStateDiff::Field_nVersion;
    1019           0 :                     stateDiff.state.nVersion = dmn->pdmnState->nVersion;
    1020           0 :                 }
    1021           0 :             }
    1022           0 :             if (stateDiff.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) {
    1023           0 :                 stateDiff.state.pubKeyOperator.SetLegacy(stateDiff.state.nVersion == ProTxVersion::LegacyBLS);
    1024           0 :             }
    1025           0 :             convertedDiff.updatedMNs.emplace(internalId, stateDiff);
    1026           0 :         }
    1027             : 
    1028             :         // Write the converted diff to new database key
    1029           0 :         batch.Write(std::make_pair(DB_LIST_DIFF, std::get<1>(key)), convertedDiff);
    1030           0 :         keys_to_erase.push_back(current_index);
    1031             : 
    1032           0 :         if (batch.SizeEstimate() >= (1 << 24)) {
    1033           0 :             LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, current_height);
    1034           0 :             m_evoDb.GetRawDB().WriteBatch(batch);
    1035           0 :             batch.Clear();
    1036           0 :         }
    1037           0 :     }
    1038           0 :     pcursor.reset();
    1039             : 
    1040           0 :     LogPrintf("CDeterministicMNManager::%s -- Writing new diffs, height=%d...\n", __func__, tip_index->nHeight);
    1041           0 :     m_evoDb.GetRawDB().WriteBatch(batch);
    1042           0 :     batch.Clear();
    1043           0 :     LogPrintf("CDeterministicMNManager::%s -- Wrote %d new diffs\n", __func__, keys_to_erase.size());
    1044             : 
    1045             :     // Delete all found legacy format entries
    1046           0 :     for (const auto& index : keys_to_erase) {
    1047           0 :         batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, index->GetBlockHash()));
    1048             : 
    1049           0 :         if (batch.SizeEstimate() >= (1 << 24)) {
    1050           0 :             LogPrintf("CDeterministicMNManager::%s -- Erasing found legacy diffs, height=%d...\n", __func__,
    1051             :                       index->nHeight);
    1052           0 :             m_evoDb.GetRawDB().WriteBatch(batch);
    1053           0 :             batch.Clear();
    1054           0 :         }
    1055             :     }
    1056             : 
    1057           0 :     LogPrintf("CDeterministicMNManager::%s -- Erasing found legacy diffs, height=%d...\n", __func__, tip_index->nHeight);
    1058           0 :     m_evoDb.GetRawDB().WriteBatch(batch);
    1059           0 :     batch.Clear();
    1060           0 :     LogPrintf("CDeterministicMNManager::%s -- Erased %d found legacy diffs\n", __func__, keys_to_erase.size());
    1061             : 
    1062             :     // Delete all dangling legacy format entries
    1063           0 :     std::unique_ptr<CDBIterator> pcursor_dangling{m_evoDb.GetRawDB().NewIterator()};
    1064           0 :     auto start{std::make_tuple(DB_LIST_DIFF_LEGACY, uint256{})};
    1065           0 :     pcursor_dangling->Seek(start);
    1066           0 :     int count{0};
    1067           0 :     while (pcursor_dangling->Valid()) {
    1068           0 :         decltype(start) key;
    1069           0 :         if (!pcursor_dangling->GetKey(key) || std::get<0>(key) != DB_LIST_DIFF_LEGACY) {
    1070           0 :             break;
    1071             :         }
    1072           0 :         LogPrintf("CDeterministicMNManager::%s -- Erasing dangling legacy diff, hash=%s\n", __func__,
    1073             :                   std::get<1>(key).ToString());
    1074           0 :         batch.Erase(std::make_pair(DB_LIST_DIFF_LEGACY, std::get<1>(key)));
    1075           0 :         pcursor_dangling->Next();
    1076           0 :         ++count;
    1077           0 :     }
    1078           0 :     pcursor_dangling.reset();
    1079             : 
    1080           0 :     m_evoDb.GetRawDB().WriteBatch(batch);
    1081           0 :     batch.Clear();
    1082           0 :     LogPrintf("CDeterministicMNManager::%s -- Erased %d dangling legacy diffs\n", __func__, count);
    1083             : 
    1084           0 :     LogPrintf("CDeterministicMNManager::%s -- Compacting database...\n", __func__);
    1085           0 :     m_evoDb.GetRawDB().CompactFull();
    1086             : 
    1087             :     // flush it to disk
    1088           0 :     if (!m_evoDb.CommitRootTransaction()) {
    1089           0 :         LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__);
    1090           0 :         return false;
    1091             :     }
    1092             : 
    1093             :     // Clear caches to force reload with new format
    1094           0 :     LOCK(cs);
    1095           0 :     mnListsCache.clear();
    1096           0 :     mnListDiffsCache.clear();
    1097             : 
    1098           0 :     LogPrintf("CDeterministicMNManager::%s -- Successfully migrated %d diffs to nVersion-first format\n", __func__,
    1099             :               keys_to_erase.size());
    1100             : 
    1101           0 :     return true;
    1102           0 : }
    1103             : 
    1104         415 : CDeterministicMNManager::RecalcDiffsResult CDeterministicMNManager::RecalculateAndRepairDiffs(
    1105             :     const CBlockIndex* start_index, const CBlockIndex* stop_index, ChainstateManager& chainman,
    1106             :     BuildListFromBlockFunc build_list_func, bool repair)
    1107             : {
    1108         415 :     RecalcDiffsResult result;
    1109         415 :     result.start_height = start_index->nHeight;
    1110         415 :     result.stop_height = stop_index->nHeight;
    1111             : 
    1112         415 :     const auto& consensus_params = Params().GetConsensus();
    1113             : 
    1114             :     // Clamp start height to DIP0003 activation (no snapshots/diffs exist before this)
    1115         415 :     if (start_index->nHeight < consensus_params.DIP0003Height) {
    1116           0 :         start_index = stop_index->GetAncestor(consensus_params.DIP0003Height);
    1117           0 :         if (!start_index) {
    1118           0 :             result.verification_errors.push_back(strprintf("Stop height %d is below DIP0003 activation height %d",
    1119           0 :                                                            stop_index->nHeight, consensus_params.DIP0003Height));
    1120           0 :             return result;
    1121             :         }
    1122           0 :         LogPrintf("CDeterministicMNManager::%s -- Clamped start height from %d to DIP0003 activation height %d\n",
    1123             :                   __func__, result.start_height, consensus_params.DIP0003Height);
    1124             :         // Update result to reflect the clamped start height
    1125           0 :         result.start_height = start_index->nHeight;
    1126           0 :     }
    1127             : 
    1128             :     // Collect all snapshot blocks in the range
    1129         415 :     std::vector<const CBlockIndex*> snapshot_blocks = CollectSnapshotBlocks(start_index, stop_index, consensus_params);
    1130             : 
    1131         415 :     if (snapshot_blocks.empty()) {
    1132           0 :         result.verification_errors.push_back("Could not find starting snapshot");
    1133           0 :         return result;
    1134             :     }
    1135             : 
    1136         415 :     if (snapshot_blocks.size() < 2) {
    1137         408 :         result.verification_errors.push_back(strprintf("Need at least 2 snapshots, found %d", snapshot_blocks.size()));
    1138         408 :         return result;
    1139             :     }
    1140             : 
    1141           7 :     LogPrintf("CDeterministicMNManager::%s -- Processing %d snapshot pairs between heights %d and %d\n", __func__,
    1142             :               snapshot_blocks.size() - 1, result.start_height, result.stop_height);
    1143             : 
    1144             :     // Storage for recalculated diffs if we plan to repair
    1145           7 :     std::vector<std::pair<uint256, CDeterministicMNListDiff>> recalculated_diffs;
    1146             : 
    1147             :     // Process each pair of consecutive snapshots
    1148          18 :     for (size_t i = 0; i < snapshot_blocks.size() - 1; ++i) {
    1149          11 :         const CBlockIndex* from_index = snapshot_blocks[i];
    1150          11 :         const CBlockIndex* to_index = snapshot_blocks[i + 1];
    1151             : 
    1152             :         // Load the snapshots from disk
    1153          11 :         CDeterministicMNList from_snapshot;
    1154          11 :         CDeterministicMNList to_snapshot;
    1155             : 
    1156          11 :         bool has_from_snapshot = m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, from_index->GetBlockHash()), from_snapshot);
    1157          11 :         bool has_to_snapshot = m_evoDb.Read(std::make_pair(DB_LIST_SNAPSHOT, to_index->GetBlockHash()), to_snapshot);
    1158             : 
    1159             :         // Handle missing snapshots
    1160          11 :         if (!has_from_snapshot) {
    1161             :             // The initial snapshot at DIP0003 activation might not exist in the database on nodes
    1162             :             // that synced before the fix to explicitly write it. This is the only acceptable case.
    1163           7 :             if (from_index->nHeight == consensus_params.DIP0003Height) {
    1164             :                 // Create an empty initial snapshot (matching what GetListForBlockInternal does)
    1165           7 :                 from_snapshot = CDeterministicMNList(from_index->GetBlockHash(), from_index->nHeight, 0);
    1166           7 :                 LogPrintf("CDeterministicMNManager::%s -- Using empty initial snapshot at DIP0003 height %d\n",
    1167             :                           __func__, from_index->nHeight);
    1168           7 :             } else {
    1169             :                 // Any other missing snapshot is critical corruption beyond our repair capability
    1170           0 :                 result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. "
    1171           0 :                     "This cannot be repaired by this tool - full reindex required.", from_index->nHeight));
    1172           0 :                 return result;
    1173             :             }
    1174           7 :         }
    1175             : 
    1176          11 :         if (!has_to_snapshot) {
    1177             :             // Missing target snapshot is always critical - we cannot repair snapshots, only diffs
    1178           0 :             result.verification_errors.push_back(strprintf("CRITICAL: Snapshot missing at height %d. "
    1179           0 :                 "This cannot be repaired by this tool - full reindex required.", to_index->nHeight));
    1180           0 :             return result;
    1181             :         }
    1182             : 
    1183             :         // Log progress periodically (every 100 snapshot pairs) to avoid spam
    1184          11 :         if (i % 100 == 0) {
    1185           7 :             LogPrintf("CDeterministicMNManager::%s -- Progress: verifying snapshot pair %d/%d (heights %d-%d)\n",
    1186             :                       __func__, i + 1, snapshot_blocks.size() - 1, from_index->nHeight, to_index->nHeight);
    1187           7 :         }
    1188             : 
    1189             :         // Verify this snapshot pair
    1190          11 :         bool is_snapshot_pair_valid = VerifySnapshotPair(from_index, to_index, from_snapshot, to_snapshot, result);
    1191             : 
    1192             :         // If repair mode is enabled and verification failed, recalculate diffs from blockchain
    1193          11 :         if (repair && !is_snapshot_pair_valid) {
    1194           0 :             auto temp_diffs = RepairSnapshotPair(from_index, to_index, from_snapshot, to_snapshot, build_list_func, result);
    1195           0 :             if (temp_diffs.empty()) {
    1196             :                 // RepairSnapshotPair failed - this is a critical error, cannot continue
    1197           0 :                 return result;
    1198             :             }
    1199             :             // Only commit diffs if recalculation verification passed
    1200           0 :             recalculated_diffs.insert(recalculated_diffs.end(), temp_diffs.begin(), temp_diffs.end());
    1201           0 :             result.diffs_recalculated += temp_diffs.size();
    1202           0 :         }
    1203          11 :     }
    1204             : 
    1205             :     // Write repaired diffs to database
    1206           7 :     if (repair) {
    1207           7 :         WriteRepairedDiffs(recalculated_diffs, result);
    1208           7 :     }
    1209             : 
    1210           7 :     return result;
    1211         415 : }
    1212             : 
    1213        2758 : bool CDeterministicMNManager::IsRepaired() const { return m_evoDb.Exists(DB_LIST_REPAIRED); }
    1214             : 
    1215         488 : void CDeterministicMNManager::CompleteRepair()
    1216             : {
    1217         488 :     auto dbTx = m_evoDb.BeginTransaction();
    1218         488 :     m_evoDb.Write(DB_LIST_REPAIRED, 1);
    1219         488 :     dbTx->Commit();
    1220             :     // flush it to disk
    1221         488 :     if (!m_evoDb.CommitRootTransaction()) {
    1222           0 :         LogPrintf("CDeterministicMNManager::%s -- Failed to commit to evoDB\n", __func__);
    1223           0 :         assert(false);
    1224             :     }
    1225         488 : }
    1226             : 
    1227         415 : std::vector<const CBlockIndex*> CDeterministicMNManager::CollectSnapshotBlocks(
    1228             :     const CBlockIndex* start_index, const CBlockIndex* stop_index, const Consensus::Params& consensus_params)
    1229             : {
    1230         415 :     std::vector<const CBlockIndex*> snapshot_blocks;
    1231             : 
    1232             :     // Add the starting snapshot (find the snapshot at or before start)
    1233             :     // Walk backwards to find a snapshot block (divisible by DISK_SNAPSHOT_PERIOD)
    1234             :     // or the initial snapshot at DIP0003 activation height
    1235         415 :     const CBlockIndex* snapshot_start_index = start_index;
    1236         415 :     while (snapshot_start_index && snapshot_start_index->nHeight > consensus_params.DIP0003Height &&
    1237           0 :            (snapshot_start_index->nHeight % DISK_SNAPSHOT_PERIOD) != 0) {
    1238           0 :         snapshot_start_index = snapshot_start_index->pprev;
    1239             :     }
    1240             : 
    1241         415 :     if (!snapshot_start_index) {
    1242           0 :         return snapshot_blocks; // Empty vector indicates error
    1243             :     }
    1244             : 
    1245             :     // Collect all snapshot blocks up to and including the stop block
    1246         415 :     snapshot_blocks.push_back(snapshot_start_index);
    1247             : 
    1248             :     // Find all subsequent snapshot heights
    1249         415 :     int current_snapshot_height = snapshot_start_index->nHeight;
    1250         426 :     while (true) {
    1251             :         // Calculate next snapshot height
    1252             :         int next_snapshot_height;
    1253         426 :         if (current_snapshot_height == consensus_params.DIP0003Height) {
    1254             :             // If we're at DIP0003 activation (initial snapshot), next is at first regular interval
    1255         415 :             next_snapshot_height = ((consensus_params.DIP0003Height / DISK_SNAPSHOT_PERIOD) + 1) * DISK_SNAPSHOT_PERIOD;
    1256         415 :         } else {
    1257             :             // Otherwise, add DISK_SNAPSHOT_PERIOD
    1258          11 :             next_snapshot_height = current_snapshot_height + DISK_SNAPSHOT_PERIOD;
    1259             :         }
    1260             : 
    1261         426 :         if (next_snapshot_height > stop_index->nHeight) {
    1262         415 :             break;
    1263             :         }
    1264             : 
    1265          11 :         const CBlockIndex* next_snapshot_index = stop_index->GetAncestor(next_snapshot_height);
    1266          11 :         if (!next_snapshot_index) {
    1267           0 :             break;
    1268             :         }
    1269             : 
    1270          11 :         snapshot_blocks.push_back(next_snapshot_index);
    1271          11 :         current_snapshot_height = next_snapshot_height;
    1272             :     }
    1273             : 
    1274         415 :     return snapshot_blocks;
    1275         415 : }
    1276             : 
    1277          11 : bool CDeterministicMNManager::VerifySnapshotPair(
    1278             :     const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot,
    1279             :     const CDeterministicMNList& to_snapshot, RecalcDiffsResult& result)
    1280             : {
    1281             :     // Verify this snapshot pair by applying all stored diffs sequentially
    1282          11 :     CDeterministicMNList test_list = from_snapshot;
    1283             : 
    1284             :     try {
    1285        4613 :         for (int nHeight = from_index->nHeight + 1; nHeight <= to_index->nHeight; ++nHeight) {
    1286        4602 :             const CBlockIndex* pIndex = to_index->GetAncestor(nHeight);
    1287        4602 :             if (!pIndex) {
    1288           0 :                 result.verification_errors.push_back(strprintf("Failed to get ancestor at height %d", nHeight));
    1289           0 :                 return false;
    1290             :             }
    1291             : 
    1292        4602 :             CDeterministicMNListDiff diff;
    1293        4602 :             if (!m_evoDb.Read(std::make_pair(DB_LIST_DIFF, pIndex->GetBlockHash()), diff)) {
    1294           0 :                 result.verification_errors.push_back(strprintf("Failed to read diff at height %d", nHeight));
    1295           0 :                 return false;
    1296             :             }
    1297             : 
    1298        4602 :             diff.nHeight = nHeight;
    1299        4602 :             test_list.ApplyDiff(pIndex, diff);
    1300        4602 :         }
    1301          11 :     } catch (const std::exception& e) {
    1302           0 :         result.verification_errors.push_back(strprintf("Exception during verification: %s", e.what()));
    1303           0 :         return false;
    1304           0 :     }
    1305             : 
    1306             :     // Verify that applying all diffs results in the target snapshot
    1307          11 :     bool is_snapshot_pair_valid = test_list.IsEqual(to_snapshot);
    1308             : 
    1309          11 :     if (is_snapshot_pair_valid) {
    1310          11 :         result.snapshots_verified++;
    1311          11 :     } else {
    1312           0 :         result.verification_errors.push_back(
    1313           0 :             strprintf("Verification failed between snapshots at heights %d and %d: "
    1314             :                       "Applied diffs do not match target snapshot",
    1315           0 :                       from_index->nHeight, to_index->nHeight));
    1316             :     }
    1317             : 
    1318          11 :     return is_snapshot_pair_valid;
    1319          11 : }
    1320             : 
    1321           0 : std::vector<std::pair<uint256, CDeterministicMNListDiff>> CDeterministicMNManager::RepairSnapshotPair(
    1322             :     const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot,
    1323             :     const CDeterministicMNList& to_snapshot, BuildListFromBlockFunc build_list_func, RecalcDiffsResult& result)
    1324             : {
    1325           0 :     CDeterministicMNList current_list = from_snapshot;
    1326             :     // Temporary storage for recalculated diffs (one per block in this snapshot interval)
    1327           0 :     std::vector<std::pair<uint256, CDeterministicMNListDiff>> temp_diffs;
    1328           0 :     temp_diffs.reserve(to_index->nHeight - from_index->nHeight);
    1329             : 
    1330           0 :     LogPrintf("CDeterministicMNManager::%s -- Repairing: recalculating diffs between snapshots at heights %d and %d\n",
    1331             :               __func__, from_index->nHeight, to_index->nHeight);
    1332             : 
    1333             :     try {
    1334           0 :         for (int nHeight = from_index->nHeight + 1; nHeight <= to_index->nHeight; ++nHeight) {
    1335           0 :             const CBlockIndex* pIndex = to_index->GetAncestor(nHeight);
    1336             : 
    1337             :             // Read the actual block from disk
    1338           0 :             CBlock block;
    1339           0 :             if (!node::ReadBlockFromDisk(block, pIndex, Params().GetConsensus())) {
    1340           0 :                 result.repair_errors.push_back(strprintf("CRITICAL: Failed to read block at height %d. "
    1341             :                     "Cannot repair - full reindex required.", nHeight));
    1342           0 :                 return {}; // Critical error - cannot continue repair
    1343             :             }
    1344             : 
    1345             :             // Use a dummy coins view to avoid UTXO lookups. At chain tip, coins from
    1346             :             // historical blocks may already be spent. Since these blocks were fully
    1347             :             // validated when originally connected, we don't need to re-verify coin
    1348             :             // availability - we only need to extract special transactions.
    1349           0 :             CCoinsView view_dummy;
    1350           0 :             CCoinsViewCache view(&view_dummy);
    1351             : 
    1352             :             // Build the new list by processing this block's special transactions
    1353             :             // Starting from current_list (our trusted state), not from corrupted diffs
    1354           0 :             CDeterministicMNList next_list;
    1355           0 :             BlockValidationState state;
    1356           0 :             if (!build_list_func(block, pIndex->pprev, current_list, view, false, state, next_list)) {
    1357           0 :                 result.repair_errors.push_back(
    1358           0 :                     strprintf("CRITICAL: Failed to build list for block at height %d: %s. "
    1359           0 :                               "Cannot repair - full reindex required.", nHeight, state.ToString()));
    1360           0 :                 return {}; // Critical error - cannot continue repair
    1361             :             }
    1362             : 
    1363             :             // Set the correct block hash
    1364           0 :             next_list.SetBlockHash(pIndex->GetBlockHash());
    1365             : 
    1366             :             // Calculate the diff between current and next
    1367           0 :             CDeterministicMNListDiff recalc_diff = current_list.BuildDiff(next_list);
    1368           0 :             recalc_diff.nHeight = nHeight;
    1369             :             // Store in temporary vector for this snapshot pair
    1370           0 :             temp_diffs.emplace_back(pIndex->GetBlockHash(), recalc_diff);
    1371             : 
    1372             :             // Move forward
    1373           0 :             current_list = next_list; // TODO: make CDeterministicMNList moveable
    1374           0 :         }
    1375             : 
    1376             :         // Verify that applying all diffs results in the target snapshot
    1377           0 :         if (current_list.IsEqual(to_snapshot)) {
    1378           0 :             LogPrintf("CDeterministicMNManager::%s -- Successfully recalculated %d diffs between heights %d and %d\n",
    1379             :                       __func__, temp_diffs.size(), from_index->nHeight, to_index->nHeight);
    1380           0 :             return temp_diffs; // Success - return recalculated diffs
    1381             :         } else {
    1382           0 :             result.repair_errors.push_back(
    1383           0 :                 strprintf("CRITICAL: Recalculation failed between snapshots at heights %d and %d: "
    1384             :                           "Applied diffs do not match target snapshot. Cannot repair - full reindex required.",
    1385           0 :                           from_index->nHeight, to_index->nHeight));
    1386           0 :             return {}; // Failed verification - return empty vector
    1387             :         }
    1388           0 :     } catch (const std::exception& e) {
    1389           0 :         result.repair_errors.push_back(strprintf("CRITICAL: Exception during recalculation: %s. "
    1390           0 :                                                   "Cannot repair - full reindex required.", e.what()));
    1391           0 :         return {}; // Exception - return empty vector
    1392           0 :     }
    1393           0 : }
    1394             : 
    1395           7 : void CDeterministicMNManager::WriteRepairedDiffs(
    1396             :     const std::vector<std::pair<uint256, CDeterministicMNListDiff>>& recalculated_diffs, RecalcDiffsResult& result)
    1397             : {
    1398           7 :     AssertLockNotHeld(cs);
    1399             : 
    1400           7 :     if (recalculated_diffs.empty()) {
    1401           7 :         return;
    1402             :     }
    1403             : 
    1404           0 :     CDBBatch batch(m_evoDb.GetRawDB());
    1405           0 :     const size_t BATCH_SIZE_THRESHOLD = 1 << 24; // 16MB
    1406           0 :     size_t diffs_written = 0;
    1407             : 
    1408           0 :     LogPrintf("CDeterministicMNManager::%s -- Writing %d repaired diffs to database...\n",
    1409             :               __func__, recalculated_diffs.size());
    1410             : 
    1411           0 :     for (const auto& [block_hash, diff] : recalculated_diffs) {
    1412           0 :         batch.Write(std::make_pair(DB_LIST_DIFF, block_hash), diff);
    1413           0 :         diffs_written++;
    1414             : 
    1415             :         // Write batch when it gets too large
    1416           0 :         if (batch.SizeEstimate() >= BATCH_SIZE_THRESHOLD) {
    1417           0 :             LogPrintf("CDeterministicMNManager::%s -- Flushing batch (%d diffs written so far)...\n",
    1418             :                       __func__, diffs_written);
    1419           0 :             m_evoDb.GetRawDB().WriteBatch(batch);
    1420           0 :             batch.Clear();
    1421           0 :         }
    1422             :     }
    1423             : 
    1424             :     // Write any remaining diffs in the batch
    1425           0 :     if (batch.SizeEstimate() > 0) {
    1426           0 :         LogPrintf("CDeterministicMNManager::%s -- Writing final batch...\n", __func__);
    1427           0 :         m_evoDb.GetRawDB().WriteBatch(batch);
    1428           0 :         batch.Clear();
    1429           0 :     }
    1430             : 
    1431             :     // Clear caches for repaired diffs so next read gets fresh data from disk
    1432             :     // Must clear both diff cache and list cache since lists were built from old diffs
    1433           0 :     LOCK(cs);
    1434           0 :     for (const auto& [block_hash, diff] : recalculated_diffs) {
    1435           0 :         mnListDiffsCache.erase(block_hash);
    1436           0 :         mnListsCache.erase(block_hash);
    1437             :     }
    1438             : 
    1439           0 :     LogPrintf("CDeterministicMNManager::%s -- Successfully repaired %d diffs (caches cleared)\n", __func__,
    1440             :               recalculated_diffs.size());
    1441           7 : }

Generated by: LCOV version 1.16