LCOV - code coverage report
Current view: top level - src/evo - deterministicmns.h (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 189 279 67.7 %
Date: 2026-06-25 07:23:51 Functions: 72 110 65.5 %

          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             : #ifndef BITCOIN_EVO_DETERMINISTICMNS_H
       6             : #define BITCOIN_EVO_DETERMINISTICMNS_H
       7             : 
       8             : #include <evo/dmn_types.h>
       9             : #include <evo/dmnstate.h>
      10             : #include <evo/providertx.h>
      11             : #include <evo/types.h>
      12             : 
      13             : #include <arith_uint256.h>
      14             : #include <clientversion.h>
      15             : #include <consensus/params.h>
      16             : #include <crypto/common.h>
      17             : #include <saltedhasher.h>
      18             : #include <scheduler.h>
      19             : #include <sync.h>
      20             : 
      21             : #include <gsl/pointers.h>
      22             : #include <immer/map.hpp>
      23             : 
      24             : #include <atomic>
      25             : #include <limits>
      26             : #include <numeric>
      27             : #include <unordered_map>
      28             : #include <utility>
      29             : 
      30             : class CBlock;
      31             : class CBlockIndex;
      32             : class CCoinsViewCache;
      33             : class ChainstateManager;
      34             : class CEvoDB;
      35             : class CSimplifiedMNList;
      36             : class CSimplifiedMNListEntry;
      37             : class CMasternodeMetaMan;
      38             : class CSpecialTxProcessor;
      39             : struct RPCResult;
      40             : 
      41             : extern RecursiveMutex cs_main; // NOLINT(readability-redundant-declaration)
      42             : 
      43             : class CDeterministicMN
      44             : {
      45             : private:
      46           0 :     uint64_t internalId{std::numeric_limits<uint64_t>::max()};
      47             : 
      48             : public:
      49             :     static constexpr uint16_t MN_VERSION_FORMAT = 2;
      50             :     static constexpr uint16_t MN_CURRENT_FORMAT = MN_VERSION_FORMAT;
      51             : 
      52             :     uint256 proTxHash;
      53             :     COutPoint collateralOutpoint;
      54          71 :     uint16_t nOperatorReward{0};
      55           0 :     MnType nType{MnType::Regular};
      56             :     std::shared_ptr<const CDeterministicMNState> pdmnState;
      57             : 
      58             :     CDeterministicMN() = delete; // no default constructor, must specify internalId
      59         284 :     explicit CDeterministicMN(uint64_t _internalId, MnType mnType = MnType::Regular) :
      60          71 :         internalId(_internalId),
      61          71 :         nType(mnType)
      62          71 :     {
      63             :         // only non-initial values
      64          71 :         assert(_internalId != std::numeric_limits<uint64_t>::max());
      65         142 :     }
      66             :     template <typename Stream>
      67           0 :     CDeterministicMN(deserialize_type, Stream& s) { s >> *this; }
      68             : 
      69         231 :     SERIALIZE_METHODS(CDeterministicMN, obj)
      70             :     {
      71          77 :         READWRITE(obj.proTxHash);
      72          77 :         READWRITE(VARINT(obj.internalId));
      73          77 :         READWRITE(obj.collateralOutpoint);
      74          77 :         READWRITE(obj.nOperatorReward);
      75          77 :         READWRITE(obj.pdmnState);
      76             :         // We can't know if we are serialising for the Disk or for the Network here (s.GetType() is not accessible)
      77             :         // Therefore if s.GetVersion() == CLIENT_VERSION -> Then we know we are serialising for the Disk
      78             :         // Otherwise, we can safely check with protocol versioning logic so we won't break old clients
      79          77 :         if (s.GetVersion() == CLIENT_VERSION || s.GetVersion() >= DMN_TYPE_PROTO_VERSION) {
      80          77 :             READWRITE(obj.nType);
      81          77 :         } else {
      82           0 :             SER_READ(obj, obj.nType = MnType::Regular);
      83             :         }
      84          77 :     }
      85             : 
      86             :     [[nodiscard]] uint64_t GetInternalId() const;
      87             : 
      88             :     [[nodiscard]] CSimplifiedMNListEntry to_sml_entry() const;
      89             :     [[nodiscard]] std::string ToString() const;
      90             : 
      91             :     [[nodiscard]] static RPCResult GetJsonHelp(const std::string& key, bool optional);
      92             :     [[nodiscard]] UniValue ToJson() const;
      93             : };
      94             : 
      95             : class CDeterministicMNListDiff;
      96             : 
      97             : template <typename Stream, typename K, typename T, typename Hash, typename Equal>
      98             : void SerializeImmerMap(Stream& os, const immer::map<K, T, Hash, Equal>& m)
      99             : {
     100             :     WriteCompactSize(os, m.size());
     101             :     for (typename immer::map<K, T, Hash, Equal>::const_iterator mi = m.begin(); mi != m.end(); ++mi)
     102             :         Serialize(os, (*mi));
     103             : }
     104             : 
     105             : template <typename Stream, typename K, typename T, typename Hash, typename Equal>
     106             : void UnserializeImmerMap(Stream& is, immer::map<K, T, Hash, Equal>& m)
     107             : {
     108             :     m = immer::map<K, T, Hash, Equal>();
     109             :     unsigned int nSize = ReadCompactSize(is);
     110             :     for (unsigned int i = 0; i < nSize; i++) {
     111             :         std::pair<K, T> item;
     112             :         Unserialize(is, item);
     113             :         m = m.set(item.first, item.second);
     114             :     }
     115             : }
     116             : 
     117             : // For some reason the compiler is not able to choose the correct Serialize/Deserialize methods without a specialized
     118             : // version of SerReadWrite. It otherwise always chooses the version that calls a.Serialize()
     119             : template<typename Stream, typename K, typename T, typename Hash, typename Equal>
     120             : inline void SerReadWrite(Stream& s, const immer::map<K, T, Hash, Equal>& m, CSerActionSerialize ser_action)
     121             : {
     122             :     ::SerializeImmerMap(s, m);
     123             : }
     124             : 
     125             : template<typename Stream, typename K, typename T, typename Hash, typename Equal>
     126             : inline void SerReadWrite(Stream& s, immer::map<K, T, Hash, Equal>& obj, CSerActionUnserialize ser_action)
     127             : {
     128             :     ::UnserializeImmerMap(s, obj);
     129             : }
     130             : 
     131             : 
     132             : class CDeterministicMNList
     133             : {
     134             : private:
     135             :     struct ImmerHasher
     136             :     {
     137       64819 :         size_t operator()(const uint256& hash) const { return ReadLE64(hash.begin()); }
     138             :     };
     139             : 
     140             : public:
     141             :     using MnMap = immer::map<uint256, CDeterministicMNCPtr, ImmerHasher>;
     142             :     using MnInternalIdMap = immer::map<uint64_t, uint256>;
     143             :     using MnUniquePropertyMap = immer::map<uint256, std::pair<uint256, uint32_t>, ImmerHasher>;
     144             : 
     145       79850 :     struct Counts {
     146       79850 :         size_t m_total_evo{0};
     147       79850 :         size_t m_total_mn{0};
     148       79850 :         size_t m_total_weighted{0};
     149       79850 :         size_t m_valid_evo{0};
     150       79850 :         size_t m_valid_mn{0};
     151       79850 :         size_t m_valid_weighted{0};
     152             : 
     153       78284 :         [[nodiscard]] size_t total() const { return m_total_mn + m_total_evo; }
     154        1566 :         [[nodiscard]] size_t enabled() const { return m_valid_mn + m_valid_evo; }
     155             :     };
     156             : 
     157             : private:
     158             :     uint256 blockHash;
     159      266719 :     int nHeight{-1};
     160      266719 :     uint32_t nTotalRegisteredCount{0};
     161             :     MnMap mnMap;
     162             :     MnInternalIdMap mnInternalIdMap;
     163             : 
     164             :     // map of unique properties like address and keys
     165             :     // we keep track of this as checking for duplicates would otherwise be painfully slow
     166             :     MnUniquePropertyMap mnUniquePropertyMap;
     167             : 
     168             :     // This SML could be null
     169             :     // This cache is used to improve performance and meant to be reused
     170             :     // for multiple CDeterministicMNList until mnMap is actually changed.
     171             :     // Calls of AddMN, RemoveMN and (in some cases) UpdateMN reset this cache;
     172             :     // it happens also for indirect calls such as ApplyDiff
     173             :     // Thread safety: Protected by its own mutex for thread-safe access
     174             :     mutable Mutex m_cached_sml_mutex;
     175             :     mutable std::shared_ptr<const CSimplifiedMNList> m_cached_sml GUARDED_BY(m_cached_sml_mutex);
     176             : 
     177             :     // Private helper method to invalidate SML cache
     178         608 :     void InvalidateSMLCache() EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex)
     179             :     {
     180         608 :         LOCK(m_cached_sml_mutex);
     181         608 :         m_cached_sml = nullptr;
     182         608 :     }
     183             : 
     184             : public:
     185      800157 :     CDeterministicMNList() = default;
     186           6 :     explicit CDeterministicMNList(const uint256& _blockHash, int _height, uint32_t _totalRegisteredCount) :
     187           2 :         blockHash(_blockHash),
     188           2 :         nHeight(_height),
     189           2 :         nTotalRegisteredCount(_totalRegisteredCount)
     190           2 :     {
     191           2 :         assert(nHeight >= 0);
     192           4 :     }
     193             : 
     194             :     // Copy constructor
     195      270426 :     CDeterministicMNList(const CDeterministicMNList& other) :
     196       90142 :         blockHash(other.blockHash),
     197       90142 :         nHeight(other.nHeight),
     198       90142 :         nTotalRegisteredCount(other.nTotalRegisteredCount),
     199       90142 :         mnMap(other.mnMap),
     200       90142 :         mnInternalIdMap(other.mnInternalIdMap),
     201       90142 :         mnUniquePropertyMap(other.mnUniquePropertyMap)
     202       90142 :     {
     203       90142 :         LOCK(other.m_cached_sml_mutex);
     204       90142 :         m_cached_sml = other.m_cached_sml;
     205      180284 :     }
     206             : 
     207             :     // Assignment operator
     208      126412 :     CDeterministicMNList& operator=(const CDeterministicMNList& other)
     209             :         EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex, !other.m_cached_sml_mutex)
     210             :     {
     211      126412 :         if (this != &other) {
     212      126412 :             blockHash = other.blockHash;
     213      126412 :             nHeight = other.nHeight;
     214      126412 :             nTotalRegisteredCount = other.nTotalRegisteredCount;
     215      126412 :             mnMap = other.mnMap;
     216      126412 :             mnInternalIdMap = other.mnInternalIdMap;
     217      126412 :             mnUniquePropertyMap = other.mnUniquePropertyMap;
     218             : 
     219      126412 :             LOCK2(m_cached_sml_mutex, other.m_cached_sml_mutex);
     220      126412 :             m_cached_sml = other.m_cached_sml;
     221      126412 :         }
     222      126412 :         return *this;
     223           0 :     }
     224             : 
     225             :     template <typename Stream, typename Operation>
     226          36 :     inline void SerializationOpBase(Stream& s, Operation ser_action)
     227             :     {
     228          36 :         READWRITE(blockHash);
     229          36 :         READWRITE(nHeight);
     230          36 :         READWRITE(nTotalRegisteredCount);
     231          36 :     }
     232             : 
     233             :     template<typename Stream>
     234          36 :     void Serialize(Stream& s) const
     235             :     {
     236          36 :         const_cast<CDeterministicMNList*>(this)->SerializationOpBase(s, CSerActionSerialize());
     237             : 
     238             :         // Serialize the map as a vector
     239          36 :         WriteCompactSize(s, mnMap.size());
     240          44 :         for (const auto& [_, dmn] : mnMap) {
     241          16 :             s << *dmn;
     242             :         }
     243          36 :     }
     244             : 
     245             :     template <typename Stream>
     246           0 :     void Unserialize(Stream& s) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex)
     247             :     {
     248           0 :         Clear();
     249             : 
     250           0 :         SerializationOpBase(s, CSerActionUnserialize());
     251             : 
     252           0 :         for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) {
     253           0 :             AddMN(std::make_shared<CDeterministicMN>(deserialize, s), /*fBumpTotalCount=*/false);
     254           0 :         }
     255           0 :     }
     256             : 
     257           0 :     void Clear() EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex)
     258             :     {
     259           0 :         blockHash = uint256{};
     260           0 :         nHeight = -1;
     261           0 :         nTotalRegisteredCount = 0;
     262           0 :         mnMap = MnMap();
     263           0 :         mnUniquePropertyMap = MnUniquePropertyMap();
     264           0 :         mnInternalIdMap = MnInternalIdMap();
     265           0 :         InvalidateSMLCache();
     266           0 :     }
     267             : 
     268       79850 :     [[nodiscard]] Counts GetCounts() const
     269             :     {
     270       79850 :         Counts ret;
     271      106816 :         for (const auto& [_, dmn] : mnMap) {
     272       26966 :             const bool is_evo = dmn->nType == MnType::Evo;
     273       26966 :             const bool is_valid = !dmn->pdmnState->IsBanned();
     274       26966 :             const auto weight = GetMnType(dmn->nType).voting_weight;
     275       26966 :             if (is_evo) {
     276           0 :                 ret.m_total_evo++;
     277           0 :                 if (is_valid) {
     278           0 :                     ret.m_valid_evo++;
     279           0 :                 }
     280           0 :             } else {
     281       26966 :                 ret.m_total_mn++;
     282       26966 :                 if (is_valid) {
     283       26631 :                     ret.m_valid_mn++;
     284       26631 :                 }
     285             :             }
     286       26966 :             if (is_valid) {
     287       26631 :                 ret.m_valid_weighted += weight;
     288       26631 :             }
     289       26966 :             ret.m_total_weighted += weight;
     290             :         }
     291       79850 :         return ret;
     292             :     }
     293             : 
     294             :     /**
     295             :      * Execute a callback on all masternodes in the mnList. This will pass a reference
     296             :      * of each masternode to the callback function. This should be preferred over ForEachMNShared.
     297             :      * @param onlyValid Run on all masternodes, or only "valid" (not banned) masternodes
     298             :      * @param cb callback to execute
     299             :      */
     300       73148 :     void ForEachMN(bool onlyValid, std::function<void(const CDeterministicMN&)> cb) const
     301             :     {
     302      102465 :         for (const auto& p : mnMap) {
     303       29317 :             if (!onlyValid || !p.second->pdmnState->IsBanned()) {
     304       29317 :                 cb(*p.second);
     305       29317 :             }
     306             :         }
     307       73148 :     }
     308             : 
     309             :     /**
     310             :      * Prefer ForEachMN. Execute a callback on all masternodes in the mnList.
     311             :      * This will pass a non-null shared_ptr of each masternode to the callback function.
     312             :      * Use this function only when a shared_ptr is needed in order to take shared ownership.
     313             :      * @param onlyValid Run on all masternodes, or only "valid" (not banned) masternodes
     314             :      * @param cb callback to execute
     315             :      */
     316       64395 :     void ForEachMNShared(bool onlyValid, std::function<void(const CDeterministicMNCPtr&)> cb) const
     317             :     {
     318      122639 :         for (const auto& p : mnMap) {
     319       58244 :             if (!onlyValid || !p.second->pdmnState->IsBanned()) {
     320       57585 :                 cb(p.second);
     321       57585 :             }
     322             :         }
     323       64395 :     }
     324             : 
     325       79973 :     [[nodiscard]] const uint256& GetBlockHash() const
     326             :     {
     327       79973 :         return blockHash;
     328             :     }
     329       54706 :     void SetBlockHash(const uint256& _blockHash)
     330             :     {
     331       54706 :         blockHash = _blockHash;
     332       54706 :     }
     333      138520 :     [[nodiscard]] int GetHeight() const
     334             :     {
     335      138520 :         assert(nHeight >= 0);
     336      138520 :         return nHeight;
     337             :     }
     338       36470 :     void SetHeight(int _height)
     339             :     {
     340       36470 :         assert(_height >= 0);
     341       36470 :         nHeight = _height;
     342       36470 :     }
     343          69 :     [[nodiscard]] uint32_t GetTotalRegisteredCount() const
     344             :     {
     345          69 :         return nTotalRegisteredCount;
     346             :     }
     347             : 
     348             :     [[nodiscard]] bool IsMNValid(const uint256& proTxHash) const;
     349             :     [[nodiscard]] bool IsMNPoSeBanned(const uint256& proTxHash) const;
     350             : 
     351        1538 :     [[nodiscard]] bool HasMN(const uint256& proTxHash) const
     352             :     {
     353        1538 :         return GetMN(proTxHash) != nullptr;
     354             :     }
     355         882 :     [[nodiscard]] bool HasMNByCollateral(const COutPoint& collateralOutpoint) const
     356             :     {
     357         882 :         return GetMNByCollateral(collateralOutpoint) != nullptr;
     358             :     }
     359           0 :     [[nodiscard]] bool HasValidMNByCollateral(const COutPoint& collateralOutpoint) const
     360             :     {
     361           0 :         return GetValidMNByCollateral(collateralOutpoint) != nullptr;
     362             :     }
     363             :     [[nodiscard]] CDeterministicMNCPtr GetMN(const uint256& proTxHash) const;
     364             :     [[nodiscard]] CDeterministicMNCPtr GetValidMN(const uint256& proTxHash) const;
     365             :     [[nodiscard]] CDeterministicMNCPtr GetMNByOperatorKey(const CBLSPublicKey& pubKey) const;
     366             :     [[nodiscard]] CDeterministicMNCPtr GetMNByCollateral(const COutPoint& collateralOutpoint) const;
     367             :     [[nodiscard]] CDeterministicMNCPtr GetValidMNByCollateral(const COutPoint& collateralOutpoint) const;
     368             :     [[nodiscard]] CDeterministicMNCPtr GetMNByService(const CService& service) const;
     369             :     [[nodiscard]] CDeterministicMNCPtr GetMNByInternalId(uint64_t internalId) const;
     370             :     [[nodiscard]] CDeterministicMNCPtr GetMNPayee(gsl::not_null<const CBlockIndex*> pindexPrev) const;
     371             : 
     372             :     /**
     373             :      * Calculates the projected MN payees for the next *count* blocks. The result is not guaranteed to be correct
     374             :      * as PoSe banning might occur later
     375             :      * @param nCount the number of payees to return. "nCount = max()"" means "all", use it to avoid calling GetCounts twice.
     376             :      */
     377             :     [[nodiscard]] std::vector<CDeterministicMNCPtr> GetProjectedMNPayees(gsl::not_null<const CBlockIndex* const> pindexPrev, int nCount = std::numeric_limits<int>::max()) const;
     378             : 
     379             :     /**
     380             :      * Calculates CSimplifiedMNList for current list and cache it
     381             :      * Thread safety: Uses internal mutex for thread-safe cache access
     382             :      */
     383             :     gsl::not_null<std::shared_ptr<const CSimplifiedMNList>> to_sml() const EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     384             : 
     385             :     /**
     386             :      * Calculates the maximum penalty which is allowed at the height of this MN list. It is dynamic and might change
     387             :      * for every block.
     388             :      */
     389             :     [[nodiscard]] int CalcMaxPoSePenalty() const;
     390             : 
     391             :     /**
     392             :      * Returns a the given percentage from the max penalty for this MN list. Always use this method to calculate the
     393             :      * value later passed to PoSePunish. The percentage should be high enough to take per-block penalty decreasing for MNs
     394             :      * into account. This means, if you want to accept 2 failures per payment cycle, you should choose a percentage that
     395             :      * is higher then 50%, e.g. 66%.
     396             :      */
     397             :     [[nodiscard]] int CalcPenalty(int percent) const;
     398             : 
     399             :     /**
     400             :      * Punishes a MN for misbehavior. If the resulting penalty score of the MN reaches the max penalty, it is banned.
     401             :      * Penalty scores are only increased when the MN is not already banned, which means that after banning the penalty
     402             :      * might appear lower then the current max penalty, while the MN is still banned.
     403             :      */
     404             :     void PoSePunish(const uint256& proTxHash, int penalty, bool debugLogs) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     405             : 
     406             :     void DecreaseScores() EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     407             :     /**
     408             :      * Decrease penalty score of MN by 1.
     409             :      * Only allowed on non-banned MNs.
     410             :      */
     411             :     void PoSeDecrease(const CDeterministicMN& dmn) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     412             : 
     413             :     [[nodiscard]] CDeterministicMNListDiff BuildDiff(const CDeterministicMNList& to) const;
     414             :     /**
     415             :      * Apply Diff modifies current object.
     416             :      * It is more efficient than creating a copy due to heavy copy constructor.
     417             :      * Calculating for old block may require up to {DISK_SNAPSHOT_PERIOD} object copy & destroy.
     418             :      */
     419             :     void ApplyDiff(gsl::not_null<const CBlockIndex*> pindex, const CDeterministicMNListDiff& diff)
     420             :         EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     421             : 
     422             :     void AddMN(const CDeterministicMNCPtr& dmn, bool fBumpTotalCount = true) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     423             :     void UpdateMN(const CDeterministicMN& oldDmn, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
     424             :         EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     425             :     void UpdateMN(const uint256& proTxHash, const std::shared_ptr<const CDeterministicMNState>& pdmnState)
     426             :         EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     427             :     void UpdateMN(const CDeterministicMN& oldDmn, const CDeterministicMNStateDiff& stateDiff)
     428             :         EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     429             :     void RemoveMN(const uint256& proTxHash) EXCLUSIVE_LOCKS_REQUIRED(!m_cached_sml_mutex);
     430             : 
     431             :     template <typename T>
     432         403 :     [[nodiscard]] bool HasUniqueProperty(const T& v) const
     433             :     {
     434         403 :         return mnUniquePropertyMap.count(GetUniquePropertyHash(v)) != 0;
     435             :     }
     436             :     template <typename T>
     437       25894 :     [[nodiscard]] CDeterministicMNCPtr GetUniquePropertyMN(const T& v) const
     438             :     {
     439       25894 :         auto p = mnUniquePropertyMap.find(GetUniquePropertyHash(v));
     440       25894 :         if (!p) {
     441       25887 :             return nullptr;
     442             :         }
     443           7 :         return GetMN(p->first);
     444       25894 :     }
     445             : 
     446             :     // Compare two masternode lists for equality, ignoring non-deterministic members.
     447             :     // Non-deterministic members (nTotalRegisteredCount, internalId) can differ between
     448             :     // nodes due to different sync histories, but don't affect consensus validity.
     449           0 :     bool IsEqual(const CDeterministicMNList& rhs) const
     450             :     {
     451             :         // Compare deterministic metadata
     452           0 :         if (blockHash != rhs.blockHash ||
     453           0 :             nHeight != rhs.nHeight ||
     454           0 :             mnUniquePropertyMap != rhs.mnUniquePropertyMap) {
     455           0 :             return false;
     456             :         }
     457             : 
     458             :         // Compare map sizes (actual entries compared below)
     459             :         // Note: Not comparing nTotalRegisteredCount (non-deterministic)
     460           0 :         if (mnMap.size() != rhs.mnMap.size() ||
     461           0 :             mnInternalIdMap.size() != rhs.mnInternalIdMap.size()) {
     462           0 :             return false;
     463             :         }
     464             : 
     465             :         // Compare each masternode entry
     466           0 :         for (const auto& [proTxHash, dmn] : mnMap) {
     467           0 :             auto dmn_rhs = rhs.mnMap.find(proTxHash);
     468           0 :             if (dmn_rhs == nullptr) {
     469           0 :                 return false;
     470             :             }
     471             : 
     472             :             // Compare deterministic masternode fields
     473             :             // Note: Not comparing internalId (non-deterministic)
     474           0 :             if (dmn->proTxHash != dmn_rhs->get()->proTxHash ||
     475           0 :                 dmn->collateralOutpoint != dmn_rhs->get()->collateralOutpoint ||
     476           0 :                 dmn->nOperatorReward != dmn_rhs->get()->nOperatorReward ||
     477           0 :                 dmn->nType != dmn_rhs->get()->nType ||
     478             :                 // Use SerializeHash for pdmnState to avoid enumerating all state fields
     479           0 :                 SerializeHash(*dmn->pdmnState) != SerializeHash(*dmn_rhs->get()->pdmnState)) {
     480           0 :                 return false;
     481             :             }
     482             :         }
     483           0 :         return true;
     484           0 :     }
     485             : 
     486             : private:
     487             :     template <typename T>
     488       28771 :     [[nodiscard]] uint256 GetUniquePropertyHash(const T& v) const
     489             :     {
     490             : #define DMNL_NO_TEMPLATE(name) \
     491             :     static_assert(!std::is_same_v<std::decay_t<T>, name>, "GetUniquePropertyHash cannot be templated against " #name)
     492             :         DMNL_NO_TEMPLATE(CBLSPublicKey);
     493             :         DMNL_NO_TEMPLATE(ExtNetInfo);
     494             :         DMNL_NO_TEMPLATE(MnNetInfo);
     495             :         DMNL_NO_TEMPLATE(NetInfoEntry);
     496             :         DMNL_NO_TEMPLATE(NetInfoInterface);
     497             :         DMNL_NO_TEMPLATE(std::shared_ptr<NetInfoInterface>);
     498             : #undef DMNL_NO_TEMPLATE
     499       28771 :         int ser_version{PROTOCOL_VERSION};
     500             :         if constexpr (std::is_same_v<std::decay_t<T>, CService>) {
     501             :             // Special handling is required if we're using addresses that can only be (de)serialized using
     502             :             // ADDRv2. Without this step, the address gets truncated, the hashmap gets contaminated with
     503             :             // an invalid entry and subsequent attempts at registering ADDRv2 entries get blocked. We cannot
     504             :             // apply this treatment ADDRv1 compatible addresses for backwards compatibility with the existing map.
     505         766 :             if (!v.IsAddrV1Compatible()) {
     506           0 :                 ser_version |= ADDRV2_FORMAT;
     507           0 :             }
     508             :         }
     509       28771 :         return ::SerializeHash(v, /*nType=*/SER_GETHASH, /*nVersion=*/ser_version);
     510             :     }
     511             :     template <typename T>
     512        2404 :     [[nodiscard]] bool AddUniqueProperty(const CDeterministicMN& dmn, const T& v)
     513             :     {
     514        2404 :         static const T nullValue{};
     515        2404 :         if (v == nullValue) {
     516           0 :             return false;
     517             :         }
     518             : 
     519        2404 :         auto hash = GetUniquePropertyHash(v);
     520        2404 :         auto oldEntry = mnUniquePropertyMap.find(hash);
     521        2404 :         if (oldEntry != nullptr && oldEntry->first != dmn.proTxHash) {
     522           0 :             return false;
     523             :         }
     524        2404 :         std::pair<uint256, uint32_t> newEntry(dmn.proTxHash, 1);
     525        2404 :         if (oldEntry != nullptr) {
     526           0 :             newEntry.second = oldEntry->second + 1;
     527           0 :         }
     528        2404 :         mnUniquePropertyMap = mnUniquePropertyMap.set(hash, newEntry);
     529        2404 :         return true;
     530        2404 :     }
     531             :     template <typename T>
     532          70 :     [[nodiscard]] bool DeleteUniqueProperty(const CDeterministicMN& dmn, const T& oldValue)
     533             :     {
     534          70 :         static const T nullValue{};
     535          70 :         if (oldValue == nullValue) {
     536           0 :             return false;
     537             :         }
     538             : 
     539          70 :         auto oldHash = GetUniquePropertyHash(oldValue);
     540          70 :         auto p = mnUniquePropertyMap.find(oldHash);
     541          70 :         if (p == nullptr || p->first != dmn.proTxHash) {
     542           0 :             return false;
     543             :         }
     544          70 :         if (p->second == 1) {
     545          70 :             mnUniquePropertyMap = mnUniquePropertyMap.erase(oldHash);
     546          70 :         } else {
     547           0 :             mnUniquePropertyMap = mnUniquePropertyMap.set(oldHash, std::make_pair(dmn.proTxHash, p->second - 1));
     548             :         }
     549          70 :         return true;
     550          70 :     }
     551             :     template <typename T>
     552       16666 :     [[nodiscard]] bool UpdateUniqueProperty(const CDeterministicMN& dmn, const T& oldValue, const T& newValue)
     553             :     {
     554       16666 :         if (oldValue == newValue) {
     555       16647 :             return true;
     556             :         }
     557          19 :         static const T nullValue{};
     558             : 
     559          19 :         if (oldValue != nullValue && !DeleteUniqueProperty(dmn, oldValue)) {
     560           0 :             return false;
     561             :         }
     562             : 
     563          19 :         if (newValue != nullValue && !AddUniqueProperty(dmn, newValue)) {
     564           0 :             return false;
     565             :         }
     566          19 :         return true;
     567       16666 :     }
     568             : 
     569       36474 :     friend bool operator==(const CDeterministicMNList& a, const CDeterministicMNList& b)
     570             :     {
     571       36562 :         return  a.blockHash == b.blockHash &&
     572          88 :                 a.nHeight == b.nHeight &&
     573          88 :                 a.nTotalRegisteredCount == b.nTotalRegisteredCount &&
     574          88 :                 a.mnMap == b.mnMap &&
     575          88 :                 a.mnInternalIdMap == b.mnInternalIdMap &&
     576          88 :                 a.mnUniquePropertyMap == b.mnUniquePropertyMap;
     577             :     }
     578             : };
     579             : 
     580       47840 : class CDeterministicMNListDiff
     581             : {
     582             : public:
     583       18574 :     int nHeight{-1}; //memory only
     584             : 
     585             :     std::vector<CDeterministicMNCPtr> addedMNs;
     586             :     // keys are all relating to the internalId of MNs
     587             :     std::unordered_map<uint64_t, CDeterministicMNStateDiff> updatedMNs;
     588             :     std::set<uint64_t> removedMns;
     589             : 
     590             :     template<typename Stream>
     591       18164 :     void Serialize(Stream& s) const
     592             :     {
     593       18164 :         s << addedMNs;
     594             : 
     595       18164 :         WriteCompactSize(s, updatedMNs.size());
     596       22248 :         for (const auto& [internalId, pdmnState] : updatedMNs) {
     597        8168 :             WriteVarInt<Stream, VarIntMode::DEFAULT, uint64_t>(s, internalId);
     598        8168 :             s << pdmnState;
     599             :         }
     600             : 
     601       18164 :         WriteCompactSize(s, removedMns.size());
     602       18169 :         for (const auto& internalId : removedMns) {
     603           5 :             WriteVarInt<Stream, VarIntMode::DEFAULT, uint64_t>(s, internalId);
     604             :         }
     605       18164 :     }
     606             : 
     607             :     template <typename Stream>
     608           0 :     void Unserialize(Stream& s)
     609             :     {
     610           0 :         UnserializeImpl(s, false); // Default: new format
     611           0 :     }
     612             : 
     613             :     // Legacy-aware unserialize method
     614             :     template <typename Stream>
     615           0 :     void UnserializeLegacyFormat(Stream& s)
     616             :     {
     617           0 :         UnserializeImpl(s, true); // Legacy format
     618           0 :     }
     619             : 
     620             : private:
     621             :     template <typename Stream>
     622           0 :     void UnserializeImpl(Stream& s, bool isLegacyFormat)
     623             :     {
     624             :         // Reset all collections before reading
     625           0 :         addedMNs.clear();
     626           0 :         updatedMNs.clear();
     627           0 :         removedMns.clear();
     628             : 
     629           0 :         for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) {
     630           0 :             addedMNs.push_back(std::make_shared<CDeterministicMN>(deserialize, s));
     631           0 :         }
     632             : 
     633           0 :         for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) {
     634           0 :             uint64_t internalId = ReadVarInt<Stream, VarIntMode::DEFAULT, uint64_t>(s);
     635             :             // CDeterministicMNState can have newer fields but doesn't need migration logic here as
     636             :             // CDeterministicMNStateDiff is always serialised using a bitmask and new fields have a new bit guide value,
     637             :             // so we are good to continue. We do need migration logic to change field order though.
     638           0 :             if (isLegacyFormat) {
     639             :                 // Use legacy deserializer for old format
     640           0 :                 CDeterministicMNStateDiffLegacy legacyDiff(deserialize, s);
     641             :                 // Convert to new format and store
     642           0 :                 updatedMNs.emplace(internalId, legacyDiff.ToNewFormat());
     643           0 :             } else {
     644             :                 // Use current deserializer for new format
     645           0 :                 updatedMNs.emplace(internalId, CDeterministicMNStateDiff(deserialize, s));
     646             :             }
     647           0 :         }
     648             : 
     649           0 :         for (size_t to_read = ReadCompactSize(s); to_read > 0; --to_read) {
     650           0 :             uint64_t internalId = ReadVarInt<Stream, VarIntMode::DEFAULT, uint64_t>(s);
     651           0 :             removedMns.emplace(internalId);
     652           0 :         }
     653           0 :     }
     654             : 
     655             : public:
     656        9861 :     bool HasChanges() const
     657             :     {
     658        9861 :         return !addedMNs.empty() || !updatedMNs.empty() || !removedMns.empty();
     659             :     }
     660             : };
     661             : 
     662             : 
     663             : constexpr int llmq_max_blocks() {
     664             :     int max_blocks{0};
     665             :     for (const auto& llmq : Consensus::available_llmqs) {
     666             :         int blocks = (llmq.useRotation ? 1 : llmq.signingActiveQuorumCount) * llmq.dkgInterval;
     667             :         max_blocks = std::max(max_blocks, blocks);
     668             :     }
     669             :     return max_blocks;
     670             : }
     671             : 
     672        2019 : struct MNListUpdates
     673             : {
     674             :     CDeterministicMNList old_list;
     675             :     CDeterministicMNList new_list;
     676             :     CDeterministicMNListDiff diff;
     677             : };
     678             : 
     679             : class CDeterministicMNManager
     680             : {
     681             :     static constexpr int DISK_SNAPSHOT_PERIOD = 576; // once per day
     682             :     // keep cache for enough disk snapshots to have all active quourms covered
     683             :     static constexpr int DISK_SNAPSHOTS = llmq_max_blocks() / DISK_SNAPSHOT_PERIOD + 1;
     684             :     static constexpr int LIST_DIFFS_CACHE_SIZE = DISK_SNAPSHOT_PERIOD * DISK_SNAPSHOTS;
     685             : 
     686             : private:
     687             :     Mutex cs;
     688             :     Mutex cs_cleanup;
     689             :     // We have performed CleanupCache() on this height.
     690             :     int did_cleanup GUARDED_BY(cs_cleanup) {0};
     691             : 
     692             :     // Main thread has indicated we should perform cleanup up to this height
     693             :     std::atomic<int> to_cleanup {0};
     694             : 
     695             :     CEvoDB& m_evoDb;
     696             :     CMasternodeMetaMan& m_mn_metaman;
     697             : 
     698             :     Uint256HashMap<CDeterministicMNList> mnListsCache GUARDED_BY(cs);
     699             :     Uint256HashMap<CDeterministicMNListDiff> mnListDiffsCache GUARDED_BY(cs);
     700             :     const CBlockIndex* tipIndex GUARDED_BY(cs) {nullptr};
     701             :     const CBlockIndex* m_initial_snapshot_index GUARDED_BY(cs) {nullptr};
     702             : 
     703             : public:
     704             :     CDeterministicMNManager() = delete;
     705             :     CDeterministicMNManager(const CDeterministicMNManager&) = delete;
     706             :     CDeterministicMNManager& operator=(const CDeterministicMNManager&) = delete;
     707             :     explicit CDeterministicMNManager(CEvoDB& evoDb, CMasternodeMetaMan& mn_metaman);
     708             :     ~CDeterministicMNManager();
     709             : 
     710             :     bool ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, BlockValidationState& state,
     711             :                       const CDeterministicMNList& newList, std::optional<MNListUpdates>& updatesRet)
     712             :         EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main);
     713             :     bool UndoBlock(gsl::not_null<const CBlockIndex*> pindex, std::optional<MNListUpdates>& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(!cs);
     714             : 
     715             :     void UpdatedBlockTip(gsl::not_null<const CBlockIndex*> pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs);
     716             : 
     717       79071 :     CDeterministicMNList GetListForBlock(gsl::not_null<const CBlockIndex*> pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs) {
     718       79071 :         LOCK(cs);
     719       79071 :         return GetListForBlockInternal(pindex);
     720       79071 :     };
     721             :     CDeterministicMNList GetListAtChainTip() EXCLUSIVE_LOCKS_REQUIRED(!cs);
     722             : 
     723             :     // Test if given TX is a ProRegTx which also contains the collateral at index n
     724             :     static bool IsProTxWithCollateral(const CTransactionRef& tx, uint32_t n);
     725             : 
     726             :     void DoMaintenance() EXCLUSIVE_LOCKS_REQUIRED(!cs, !cs_cleanup);
     727             : 
     728             :     // Recalculate and optionally repair diffs between snapshots
     729           0 :     struct RecalcDiffsResult {
     730           0 :         int start_height{0};
     731           0 :         int stop_height{0};
     732           0 :         int diffs_recalculated{0};
     733           0 :         int snapshots_verified{0};
     734             :         std::vector<std::string> verification_errors;
     735             :         std::vector<std::string> repair_errors;
     736             :     };
     737             : 
     738             :     // Callback type for building a new MN list from a block
     739             :     using BuildListFromBlockFunc = std::function<bool(
     740             :         const CBlock& block,
     741             :         gsl::not_null<const CBlockIndex*> pindexPrev,
     742             :         const CDeterministicMNList& prevList,
     743             :         const CCoinsViewCache& view,
     744             :         bool debugLogs,
     745             :         BlockValidationState& state,
     746             :         CDeterministicMNList& mnListRet)>;
     747             : 
     748             :     [[nodiscard]] RecalcDiffsResult RecalculateAndRepairDiffs(const CBlockIndex* start_index,
     749             :                                                               const CBlockIndex* stop_index, ChainstateManager& chainman,
     750             :                                                               BuildListFromBlockFunc build_list_func, bool repair)
     751             :         EXCLUSIVE_LOCKS_REQUIRED(!cs);
     752             :     [[nodiscard]] bool IsRepaired() const;
     753             :     void CompleteRepair();
     754             : 
     755             :     // Migration support for nVersion-first CDeterministicMNStateDiff format
     756             :     [[nodiscard]] bool IsMigrationRequired() const EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main);
     757             :     [[nodiscard]] bool MigrateLegacyDiffs(const CBlockIndex* const tip_index) EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main);
     758             : 
     759             : private:
     760             :     void CleanupCache(int nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs);
     761             :     CDeterministicMNList GetListForBlockInternal(gsl::not_null<const CBlockIndex*> pindex) EXCLUSIVE_LOCKS_REQUIRED(cs);
     762             : 
     763             :     // Helper methods for RecalculateAndRepairDiffs
     764             :     std::vector<const CBlockIndex*> CollectSnapshotBlocks(const CBlockIndex* start_index, const CBlockIndex* stop_index,
     765             :                                                           const Consensus::Params& consensus_params);
     766             :     bool VerifySnapshotPair(const CBlockIndex* from_index, const CBlockIndex* to_index,
     767             :                             const CDeterministicMNList& from_snapshot, const CDeterministicMNList& to_snapshot,
     768             :                             RecalcDiffsResult& result);
     769             :     std::vector<std::pair<uint256, CDeterministicMNListDiff>> RepairSnapshotPair(
     770             :         const CBlockIndex* from_index, const CBlockIndex* to_index, const CDeterministicMNList& from_snapshot,
     771             :         const CDeterministicMNList& to_snapshot, BuildListFromBlockFunc build_list_func, RecalcDiffsResult& result);
     772             :     void WriteRepairedDiffs(const std::vector<std::pair<uint256, CDeterministicMNListDiff>>& recalculated_diffs,
     773             :                             RecalcDiffsResult& result) EXCLUSIVE_LOCKS_REQUIRED(!cs);
     774             : };
     775             : #endif // BITCOIN_EVO_DETERMINISTICMNS_H

Generated by: LCOV version 1.16