LCOV - code coverage report
Current view: top level - src/llmq - utils.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 290 457 63.5 %
Date: 2026-06-25 07:23:51 Functions: 34 51 66.7 %

          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 <llmq/utils.h>
       6             : 
       7             : #include <bls/bls.h>
       8             : #include <evo/deterministicmns.h>
       9             : #include <llmq/options.h>
      10             : #include <llmq/snapshot.h>
      11             : #include <llmq/types.h>
      12             : #include <masternode/meta.h>
      13             : #include <util/helpers.h>
      14             : #include <util/std23.h>
      15             : 
      16             : #include <chainparams.h>
      17             : #include <deploymentstatus.h>
      18             : #include <random.h>
      19             : #include <util/time.h>
      20             : #include <validation.h>
      21             : 
      22             : #include <atomic>
      23             : #include <map>
      24             : #include <optional>
      25             : 
      26             : /**
      27             :  * Forward declarations
      28             :  */
      29             : std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
      30             : 
      31             : namespace {
      32             : using QuorumMembers = std::vector<CDeterministicMNCPtr>;
      33             : 
      34        8452 : std::string ToString(const QuorumMembers& members)
      35             : {
      36        8452 :     std::stringstream ss;
      37       11110 :     for (const auto& mn : members) {
      38        2658 :         ss << mn->proTxHash.ToString().substr(0, 4) << "|";
      39             :     }
      40        8452 :     return ss.str();
      41        8452 : }
      42             : 
      43             : struct MasternodeScore {
      44             :     arith_uint256 m_score;
      45             :     CDeterministicMNCPtr m_node;
      46             : };
      47             : 
      48             : struct QuorumQuarter : public llmq::CycleBase {
      49             :     std::vector<QuorumMembers> m_members;
      50             : 
      51             : public:
      52        4698 :     explicit QuorumQuarter(size_t size) : m_members(size) {}
      53             : };
      54             : 
      55             : // QuorumMembers per quorumIndex at heights H-Cycle, H-2Cycles, H-3Cycles
      56             : struct PreviousQuorumQuarters {
      57             :     QuorumQuarter quarterHMinusC;
      58             :     QuorumQuarter quarterHMinus2C;
      59             :     QuorumQuarter quarterHMinus3C;
      60             : 
      61             : public:
      62        1566 :     explicit PreviousQuorumQuarters(size_t s) : quarterHMinusC(s), quarterHMinus2C(s), quarterHMinus3C(s) {}
      63             : 
      64         783 :     std::vector<QuorumQuarter*> GetCycles() { return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C}; }
      65         328 :     std::vector<const QuorumQuarter*> GetCycles() const
      66             :     {
      67         328 :         return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C};
      68             :     }
      69             : };
      70             : 
      71        1042 : arith_uint256 calculateQuorumScore(const CDeterministicMNCPtr& dmn, const uint256& modifier)
      72             : {
      73             :     // calculate sha256(sha256(proTxHash, confirmedHash), modifier) per MN
      74             :     // Please note that this is not a double-sha256 but a single-sha256
      75             :     // The first part is already precalculated (confirmedHashWithProRegTxHash)
      76             :     // TODO When https://github.com/bitcoin/bitcoin/pull/13191 gets backported, implement something that is similar but for single-sha256
      77        1042 :     uint256 h;
      78        1042 :     CSHA256 sha256;
      79        2084 :     sha256.Write(dmn->pdmnState->confirmedHashWithProRegTxHash.begin(),
      80        1042 :                  dmn->pdmnState->confirmedHashWithProRegTxHash.size());
      81        1042 :     sha256.Write(modifier.begin(), modifier.size());
      82        1042 :     sha256.Finalize(h.begin());
      83        1042 :     return UintToArith256(h);
      84             : }
      85             : 
      86        1405 : uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, const Consensus::Params& consensus_params,
      87             :                         gsl::not_null<const CBlockIndex*> pCycleQuorumBaseBlockIndex)
      88             : {
      89             :     ASSERT_IF_DEBUG(pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval == 0);
      90        1405 :     const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
      91             : 
      92        1405 :     if (DeploymentActiveAfter(pWorkBlockIndex, consensus_params, Consensus::DEPLOYMENT_V20)) {
      93             :         // v20 is active: calculate modifier using the new way.
      94        1004 :         auto cbcl = GetNonNullCoinbaseChainlock(pWorkBlockIndex);
      95        1004 :         if (cbcl.has_value()) {
      96             :             // We have a non-null CL signature: calculate modifier using this CL signature
      97           0 :             auto& [bestCLSignature, bestCLHeightDiff] = cbcl.value();
      98           0 :             return ::SerializeHash(std::make_tuple(llmqParams.type, pWorkBlockIndex->nHeight, bestCLSignature));
      99             :         }
     100             :         // No non-null CL signature found in coinbase: calculate modifier using block hash only
     101        1004 :         return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
     102        1004 :     }
     103             : 
     104             :     // v20 isn't active yet: calculate modifier using the usual way
     105         401 :     if (llmqParams.useRotation) {
     106         401 :         return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
     107             :     }
     108           0 :     return ::SerializeHash(std::make_pair(llmqParams.type, pCycleQuorumBaseBlockIndex->GetBlockHash()));
     109        1405 : }
     110             : 
     111         164 : std::vector<MasternodeScore> CalculateScoresForQuorum(QuorumMembers&& dmns, const uint256& modifier, const bool onlyEvoNodes)
     112             : {
     113         164 :     std::vector<MasternodeScore> scores;
     114         164 :     scores.reserve(dmns.size());
     115             : 
     116         262 :     for (auto& dmn : dmns) {
     117          98 :         if (dmn->pdmnState->IsBanned()) continue;
     118          98 :         if (dmn->pdmnState->confirmedHash.IsNull()) {
     119             :             // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a
     120             :             // future quorums
     121           0 :             continue;
     122             :         }
     123          98 :         if (onlyEvoNodes && dmn->nType != MnType::Evo) {
     124           0 :             continue;
     125             :         }
     126          98 :         scores.emplace_back(calculateQuorumScore(dmn, modifier), std::move(dmn));
     127             :     };
     128         164 :     return scores;
     129         164 : }
     130             : 
     131         786 : std::vector<MasternodeScore> CalculateScoresForQuorum(const CDeterministicMNList& mn_list, const uint256& modifier,
     132             :                                                       const bool onlyEvoNodes)
     133             : {
     134         786 :     std::vector<MasternodeScore> scores;
     135         786 :     scores.reserve(mn_list.GetCounts().total());
     136             : 
     137        1730 :     mn_list.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     138         944 :         if (dmn->pdmnState->confirmedHash.IsNull()) {
     139             :             // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a
     140             :             // future quorums
     141           0 :             return;
     142             :         }
     143         944 :         if (onlyEvoNodes && dmn->nType != MnType::Evo) {
     144           0 :             return;
     145             :         }
     146         944 :         scores.emplace_back(calculateQuorumScore(dmn, modifier), dmn);
     147         944 :     });
     148         786 :     return scores;
     149         786 : }
     150             : 
     151             : /**
     152             :  * Calculate a quorum based on the modifier. The resulting list is deterministically sorted by score
     153             :  */
     154             : template <typename List>
     155         950 : QuorumMembers CalculateQuorum(List&& mn_list, const uint256& modifier, size_t maxSize = 0, const bool onlyEvoNodes = false)
     156             : {
     157         950 :     auto scores = CalculateScoresForQuorum(std::forward<List>(mn_list), modifier, onlyEvoNodes);
     158             : 
     159             :     // sort is descending order
     160        1846 :     std::sort(scores.rbegin(), scores.rend(), [](const MasternodeScore& a, const MasternodeScore& b) {
     161         896 :         if (a.m_score == b.m_score) {
     162             :             // this should actually never happen, but we should stay compatible with how the non-deterministic MNs did the sorting
     163             :             // TODO - add assert ?
     164           0 :             return a.m_node->collateralOutpoint < b.m_node->collateralOutpoint;
     165             :         }
     166         896 :         return a.m_score < b.m_score;
     167         896 :     });
     168             : 
     169             :     // return top maxSize entries only (if specified)
     170         950 :     if (maxSize > 0 && scores.size() > maxSize) {
     171           0 :         scores.resize(maxSize);
     172           0 :     }
     173             : 
     174         950 :     QuorumMembers result;
     175         950 :     result.reserve(scores.size());
     176        1992 :     for (auto& [_, node] : scores) {
     177        1042 :         result.emplace_back(std::move(node));
     178             :     }
     179         950 :     return result;
     180         950 : }
     181             : 
     182         458 : std::vector<QuorumMembers> GetQuorumQuarterMembersBySnapshot(const Consensus::LLMQParams& llmqParams,
     183             :                                                              CDeterministicMNManager& dmnman,
     184             :                                                              const Consensus::Params& consensus_params,
     185             :                                                              const CBlockIndex* pCycleQuorumBaseBlockIndex,
     186             :                                                              const llmq::CQuorumSnapshot& snapshot, int nHeight)
     187             : {
     188         458 :     if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
     189             :         ASSERT_IF_DEBUG(false);
     190           0 :         return {};
     191             :     }
     192             : 
     193         458 :     std::vector<CDeterministicMNCPtr> sortedCombinedMns;
     194             :     {
     195         916 :         const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(
     196         458 :             pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
     197         458 :         auto mn_list = dmnman.GetListForBlock(pWorkBlockIndex);
     198         458 :         const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
     199         458 :         auto sortedAllMns = CalculateQuorum(mn_list, modifier);
     200             : 
     201         458 :         std::vector<CDeterministicMNCPtr> usedMNs;
     202         458 :         size_t i{0};
     203         972 :         for (const auto& dmn : sortedAllMns) {
     204         514 :             if (snapshot.activeQuorumMembers[i]) {
     205         446 :                 usedMNs.push_back(dmn);
     206         446 :             } else {
     207          68 :                 if (!dmn->pdmnState->IsBanned()) {
     208             :                     // the list begins with all the unused MNs
     209          68 :                     sortedCombinedMns.push_back(dmn);
     210          68 :                 }
     211             :             }
     212         514 :             i++;
     213             :         }
     214             : 
     215             :         // Now add the already used MNs to the end of the list
     216         458 :         std::move(usedMNs.begin(), usedMNs.end(), std::back_inserter(sortedCombinedMns));
     217         458 :     }
     218             : 
     219         458 :     if (LogAcceptDebug(BCLog::LLMQ)) {
     220         458 :         LogPrint(BCLog::LLMQ, "%s h[%d] from[%d] sortedCombinedMns[%s]\n", __func__,
     221             :                  pCycleQuorumBaseBlockIndex->nHeight, nHeight, ToString(sortedCombinedMns));
     222         458 :     }
     223             : 
     224         458 :     size_t numQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
     225         458 :     size_t quorumSize = static_cast<size_t>(llmqParams.size);
     226         458 :     auto quarterSize{quorumSize / 4};
     227             : 
     228         458 :     std::vector<QuorumMembers> quarterQuorumMembers(numQuorums);
     229             : 
     230         458 :     if (sortedCombinedMns.empty()) {
     231           0 :         return quarterQuorumMembers;
     232             :     }
     233             : 
     234         458 :     switch (snapshot.mnSkipListMode) {
     235             :     case SnapshotSkipMode::MODE_NO_SKIPPING: {
     236         458 :         auto itm = sortedCombinedMns.begin();
     237        1374 :         for (const size_t i : util::irange(numQuorums)) {
     238        1832 :             while (quarterQuorumMembers[i].size() < quarterSize) {
     239         916 :                 quarterQuorumMembers[i].push_back(*itm);
     240         916 :                 itm++;
     241         916 :                 if (itm == sortedCombinedMns.end()) {
     242         900 :                     itm = sortedCombinedMns.begin();
     243         900 :                 }
     244             :             }
     245             :         }
     246         458 :         return quarterQuorumMembers;
     247             :     }
     248             :     case SnapshotSkipMode::MODE_SKIPPING_ENTRIES: // List holds entries to be skipped
     249             :     {
     250           0 :         size_t first_entry_index{0};
     251           0 :         std::vector<int> processesdSkipList;
     252           0 :         for (const auto& s : snapshot.mnSkipList) {
     253           0 :             if (first_entry_index == 0) {
     254           0 :                 first_entry_index = s;
     255           0 :                 processesdSkipList.push_back(s);
     256           0 :             } else {
     257           0 :                 processesdSkipList.push_back(first_entry_index + s);
     258             :             }
     259             :         }
     260             : 
     261           0 :         int idx = 0;
     262           0 :         auto itsk = processesdSkipList.begin();
     263           0 :         for (const size_t i : util::irange(numQuorums)) {
     264           0 :             while (quarterQuorumMembers[i].size() < quarterSize) {
     265           0 :                 if (itsk != processesdSkipList.end() && idx == *itsk) {
     266           0 :                     itsk++;
     267           0 :                 } else {
     268           0 :                     quarterQuorumMembers[i].push_back(sortedCombinedMns[idx]);
     269             :                 }
     270           0 :                 idx++;
     271           0 :                 if (idx == static_cast<int>(sortedCombinedMns.size())) {
     272           0 :                     idx = 0;
     273           0 :                 }
     274             :             }
     275             :         }
     276           0 :         return quarterQuorumMembers;
     277           0 :     }
     278             :     case SnapshotSkipMode::MODE_NO_SKIPPING_ENTRIES: // List holds entries to be kept
     279           0 :     case SnapshotSkipMode::MODE_ALL_SKIPPED:         // Every node was skipped. Returning empty quarterQuorumMembers
     280             :     default:
     281           0 :         return quarterQuorumMembers;
     282             :     }
     283         458 : }
     284             : 
     285           0 : QuorumMembers ComputeQuorumMembers(Consensus::LLMQType llmqType, const CChainParams& chainparams,
     286             :                                    const CDeterministicMNList& mn_list, const CBlockIndex* pQuorumBaseBlockIndex)
     287             : {
     288           0 :     bool EvoOnly = (chainparams.GetConsensus().llmqTypePlatform == llmqType) &&
     289           0 :                    DeploymentActiveAfter(pQuorumBaseBlockIndex, chainparams.GetConsensus(), Consensus::DEPLOYMENT_V19);
     290           0 :     const auto& llmq_params_opt = chainparams.GetLLMQ(llmqType);
     291           0 :     assert(llmq_params_opt.has_value());
     292           0 :     if (llmq_params_opt->useRotation || pQuorumBaseBlockIndex->nHeight % llmq_params_opt->dkgInterval != 0) {
     293             :         ASSERT_IF_DEBUG(false);
     294           0 :         return {};
     295             :     }
     296             : 
     297           0 :     const auto modifier = GetHashModifier(llmq_params_opt.value(), chainparams.GetConsensus(), pQuorumBaseBlockIndex);
     298           0 :     return CalculateQuorum(mn_list, modifier, llmq_params_opt->size, EvoOnly);
     299           0 : }
     300             : 
     301         164 : void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const Consensus::Params& consensus_params,
     302             :                          const CDeterministicMNList& allMns, const CDeterministicMNList& mnUsedAtH,
     303             :                          std::vector<CDeterministicMNCPtr>& sortedCombinedMns, llmq::CQuorumSnapshot& quorumSnapshot,
     304             :                          std::vector<int>& skipList, const CBlockIndex* pCycleQuorumBaseBlockIndex)
     305             : {
     306         164 :     if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
     307             :         ASSERT_IF_DEBUG(false);
     308           0 :         return;
     309             :     }
     310             : 
     311         164 :     const auto allMnsTotal = allMns.GetCounts().total();
     312         164 :     quorumSnapshot.activeQuorumMembers.resize(allMnsTotal);
     313         164 :     const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
     314         164 :     auto sortedAllMns = CalculateQuorum(allMns, modifier);
     315             : 
     316         164 :     LogPrint(BCLog::LLMQ, "BuildQuorumSnapshot h[%d] numMns[%d]\n", pCycleQuorumBaseBlockIndex->nHeight,
     317             :              allMnsTotal);
     318             : 
     319         164 :     std::fill(quorumSnapshot.activeQuorumMembers.begin(), quorumSnapshot.activeQuorumMembers.end(), false);
     320         164 :     size_t index = {};
     321         428 :     for (const auto& dmn : sortedAllMns) {
     322         264 :         if (mnUsedAtH.HasMN(dmn->proTxHash)) {
     323         166 :             quorumSnapshot.activeQuorumMembers[index] = true;
     324         166 :         }
     325         264 :         index++;
     326             :     }
     327             : 
     328         164 :     if (skipList.empty()) {
     329         164 :         quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_NO_SKIPPING;
     330         164 :         quorumSnapshot.mnSkipList.clear();
     331         164 :     } else {
     332           0 :         quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
     333           0 :         quorumSnapshot.mnSkipList = std::move(skipList);
     334             :     }
     335         164 : }
     336             : 
     337         783 : std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams,
     338             :                                                         const llmq::UtilParameters& util_params,
     339             :                                                         const CDeterministicMNList& allMns,
     340             :                                                         const PreviousQuorumQuarters& previousQuarters)
     341             : {
     342         783 :     if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
     343             :         ASSERT_IF_DEBUG(false);
     344           0 :         return {};
     345             :     }
     346             : 
     347         783 :     size_t nQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
     348         783 :     std::vector<QuorumMembers> quarterQuorumMembers{nQuorums};
     349             : 
     350         783 :     size_t quorumSize = static_cast<size_t>(llmqParams.size);
     351         783 :     auto quarterSize{quorumSize / 4};
     352         783 :     const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index);
     353             : 
     354         783 :     if (allMns.GetCounts().enabled() < quarterSize) {
     355         619 :         return quarterQuorumMembers;
     356             :     }
     357             : 
     358         164 :     auto MnsUsedAtH = CDeterministicMNList();
     359         164 :     std::vector<CDeterministicMNList> MnsUsedAtHIndexed{nQuorums};
     360             : 
     361         328 :     bool skipRemovedMNs = DeploymentActiveAfter(util_params.m_base_index, util_params.m_chainman.GetConsensus(),
     362         164 :                                                 Consensus::DEPLOYMENT_V19) ||
     363           0 :                           (util_params.m_chainman.GetParams().NetworkIDString() == CBaseChainParams::TESTNET);
     364             : 
     365         492 :     for (const size_t idx : util::irange(nQuorums)) {
     366        1312 :         for (auto* prev_cycle : previousQuarters.GetCycles()) {
     367        1900 :             for (const auto& mn : prev_cycle->m_members[idx]) {
     368         916 :                 if (skipRemovedMNs && !allMns.HasMN(mn->proTxHash)) {
     369           0 :                     continue;
     370             :                 }
     371         916 :                 if (allMns.IsMNPoSeBanned(mn->proTxHash)) {
     372           2 :                     continue;
     373             :                 }
     374             :                 try {
     375         914 :                     MnsUsedAtH.AddMN(mn);
     376         914 :                 } catch (const std::runtime_error& e) {
     377         748 :                 }
     378             :                 try {
     379         914 :                     MnsUsedAtHIndexed[idx].AddMN(mn);
     380         914 :                 } catch (const std::runtime_error& e) {
     381         596 :                 }
     382             :             }
     383             :         }
     384             :     }
     385             : 
     386         164 :     std::vector<CDeterministicMNCPtr> MnsNotUsedAtH;
     387         432 :     allMns.ForEachMNShared(/*onlyValid=*/false, [&MnsUsedAtH, &MnsNotUsedAtH](const auto& dmn) {
     388         268 :         if (!MnsUsedAtH.HasMN(dmn->proTxHash)) {
     389         102 :             if (!dmn->pdmnState->IsBanned()) {
     390          98 :                 MnsNotUsedAtH.push_back(dmn);
     391          98 :             }
     392         102 :         }
     393         268 :     });
     394             : 
     395         164 :     auto sortedMnsUsedAtHM = CalculateQuorum(MnsUsedAtH, modifier);
     396         164 :     auto sortedCombinedMnsList = CalculateQuorum(std::move(MnsNotUsedAtH), modifier);
     397         330 :     for (auto& m : sortedMnsUsedAtHM) {
     398         166 :         sortedCombinedMnsList.push_back(std::move(m));
     399             :     }
     400             : 
     401         164 :     if (LogAcceptDebug(BCLog::LLMQ)) {
     402         164 :         LogPrint(BCLog::LLMQ, "%s h[%d] sortedCombinedMns[%s]\n", __func__, util_params.m_base_index->nHeight,
     403             :                  ToString(sortedCombinedMnsList));
     404         164 :     }
     405             : 
     406         164 :     std::vector<int> skipList;
     407         164 :     size_t firstSkippedIndex = 0;
     408         164 :     size_t idx{0};
     409         492 :     for (const size_t i : util::irange(nQuorums)) {
     410         328 :         auto usedMNsCount = MnsUsedAtHIndexed[i].GetCounts().total();
     411         328 :         bool updated{false};
     412         328 :         size_t initial_loop_idx = idx;
     413         352 :         while (quarterQuorumMembers[i].size() < quarterSize && (usedMNsCount + quarterQuorumMembers[i].size() < sortedCombinedMnsList.size())) {
     414          24 :             bool skip{true};
     415          24 :             if (!MnsUsedAtHIndexed[i].HasMN(sortedCombinedMnsList[idx]->proTxHash)) {
     416             :                 try {
     417             :                     // NOTE: AddMN is the one that can throw exceptions, must be exicuted first
     418          24 :                     MnsUsedAtHIndexed[i].AddMN(sortedCombinedMnsList[idx]);
     419          24 :                     quarterQuorumMembers[i].push_back(sortedCombinedMnsList[idx]);
     420          24 :                     updated = true;
     421          24 :                     skip = false;
     422          24 :                 } catch (const std::runtime_error& e) {
     423           0 :                 }
     424          24 :             }
     425          24 :             if (skip) {
     426           0 :                 if (firstSkippedIndex == 0) {
     427           0 :                     firstSkippedIndex = idx;
     428           0 :                     skipList.push_back(idx);
     429           0 :                 } else {
     430           0 :                     skipList.push_back(idx - firstSkippedIndex);
     431             :                 }
     432           0 :             }
     433          24 :             if (++idx == sortedCombinedMnsList.size()) {
     434           4 :                 idx = 0;
     435           4 :             }
     436          24 :             if (idx == initial_loop_idx) {
     437             :                 // we made full "while" loop
     438           4 :                 if (!updated) {
     439             :                     // there are not enough MNs, there is nothing we can do here
     440           0 :                     return std::vector<QuorumMembers>(nQuorums);
     441             :                 }
     442             :                 // reset and try again
     443           4 :                 updated = false;
     444           4 :             }
     445             :         }
     446             :     }
     447             : 
     448         164 :     llmq::CQuorumSnapshot quorumSnapshot{};
     449         164 :     BuildQuorumSnapshot(llmqParams, util_params.m_chainman.GetConsensus(), allMns, MnsUsedAtH, sortedCombinedMnsList,
     450         164 :                         quorumSnapshot, skipList, util_params.m_base_index);
     451         164 :     util_params.m_qsnapman.StoreSnapshotForBlock(llmqParams.type, util_params.m_base_index, quorumSnapshot);
     452             : 
     453         164 :     return quarterQuorumMembers;
     454        2127 : }
     455             : 
     456         783 : std::vector<QuorumMembers> ComputeQuorumMembersByQuarterRotation(const Consensus::LLMQParams& llmqParams,
     457             :                                                                  const llmq::UtilParameters& util_params)
     458             : {
     459         783 :     const int cycleLength = llmqParams.dkgInterval;
     460         783 :     if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
     461             :         ASSERT_IF_DEBUG(false);
     462           0 :         return {};
     463             :     }
     464         783 :     const auto nQuorums{static_cast<size_t>(llmqParams.signingActiveQuorumCount)};
     465             : 
     466         783 :     const CBlockIndex* pWorkBlockIndex = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
     467             :                                                                                llmq::WORK_DIFF_DEPTH);
     468         783 :     CDeterministicMNList allMns = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
     469         783 :     LogPrint(BCLog::LLMQ, "ComputeQuorumMembersByQuarterRotation llmqType[%d] nHeight[%d] allMns[%d]\n",
     470             :              std23::to_underlying(llmqParams.type), util_params.m_base_index->nHeight, allMns.GetCounts().enabled());
     471             : 
     472         783 :     PreviousQuorumQuarters previousQuarters(nQuorums);
     473         783 :     auto prev_cycles{previousQuarters.GetCycles()};
     474        1241 :     for (size_t idx{0}; idx < prev_cycles.size(); idx++) {
     475        2186 :         prev_cycles[idx]->m_cycle_index = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
     476        1093 :                                                                                 (cycleLength * (idx + 1)));
     477        1093 :         if (auto opt_snap = util_params.m_qsnapman.GetSnapshotForBlock(llmqParams.type, prev_cycles[idx]->m_cycle_index);
     478        2186 :             opt_snap.has_value()) {
     479         458 :             prev_cycles[idx]->m_snap = opt_snap.value();
     480         458 :         } else {
     481             :             // TODO: Check if it is triggered from outside (P2P, block validation) and maybe throw an exception
     482             :             // assert(false);
     483         635 :             break;
     484             :         }
     485         458 :         prev_cycles[idx]->m_members = GetQuorumQuarterMembersBySnapshot(llmqParams, util_params.m_dmnman,
     486         458 :                                                                         util_params.m_chainman.GetConsensus(),
     487         458 :                                                                         prev_cycles[idx]->m_cycle_index,
     488         458 :                                                                         prev_cycles[idx]->m_snap,
     489         458 :                                                                         util_params.m_base_index->nHeight);
     490         458 :     }
     491             : 
     492         783 :     auto newQuarterMembers = BuildNewQuorumQuarterMembers(llmqParams, util_params, allMns, previousQuarters);
     493             :     // TODO: Check if it is triggered from outside (P2P, block validation) and maybe throw an exception
     494             :     // assert (!newQuarterMembers.empty());
     495             : 
     496         783 :     if (LogAcceptDebug(BCLog::LLMQ)) {
     497        2349 :         for (const size_t i : util::irange(nQuorums)) {
     498        1566 :             std::stringstream ss;
     499        6264 :             for (size_t idx = prev_cycles.size(); idx-- > 0;) {
     500        4698 :                 ss << strprintf(" %dCmns[%s]", idx, ToString(prev_cycles[idx]->m_members[i]));
     501             :             }
     502        1566 :             ss << strprintf(" new[%s]", ToString(newQuarterMembers[i]));
     503        1566 :             LogPrint(BCLog::LLMQ, "QuarterComposition h[%d] i[%d]:%s\n", util_params.m_base_index->nHeight, i, ss.str());
     504        1566 :         }
     505         783 :     }
     506             : 
     507         783 :     std::vector<QuorumMembers> quorumMembers(nQuorums);
     508        2349 :     for (const size_t i : util::irange(nQuorums)) {
     509             :         // Move elements from previous quarters into quorumMembers
     510        6264 :         for (auto* prev_cycle : prev_cycles | std::views::reverse) {
     511        4698 :             std::move(prev_cycle->m_members[i].begin(), prev_cycle->m_members[i].end(),
     512        4698 :                       std::back_inserter(quorumMembers[i]));
     513             :         }
     514        1566 :         std::move(newQuarterMembers[i].begin(), newQuarterMembers[i].end(), std::back_inserter(quorumMembers[i]));
     515        1566 :         if (LogAcceptDebug(BCLog::LLMQ)) {
     516        1566 :             LogPrint(BCLog::LLMQ, "QuorumComposition h[%d] i[%d]: [%s]\n", util_params.m_base_index->nHeight, i,
     517             :                      ToString(quorumMembers[i]));
     518        1566 :         }
     519             :     }
     520             : 
     521         783 :     return quorumMembers;
     522         783 : }
     523             : } // anonymous namespace
     524             : 
     525             : namespace llmq {
     526             : namespace utils {
     527           0 : BlsCheck::BlsCheck() = default;
     528             : 
     529           0 : BlsCheck::BlsCheck(CBLSSignature sig, std::vector<CBLSPublicKey> pubkeys, uint256 msg_hash, std::string id_string) :
     530           0 :     m_sig(sig),
     531           0 :     m_pubkeys(pubkeys),
     532           0 :     m_msg_hash(msg_hash),
     533           0 :     m_id_string(id_string)
     534           0 : {
     535           0 : }
     536             : 
     537           0 : BlsCheck::~BlsCheck() = default;
     538             : 
     539           0 : bool BlsCheck::operator()()
     540             : {
     541           0 :     if (m_pubkeys.size() > 1) {
     542           0 :         if (!m_sig.VerifySecureAggregated(m_pubkeys, m_msg_hash)) {
     543           0 :             LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
     544           0 :             return false;
     545             :         }
     546           0 :     } else if (m_pubkeys.size() == 1) {
     547           0 :         if (!m_sig.VerifyInsecure(m_pubkeys.back(), m_msg_hash)) {
     548           0 :             LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
     549           0 :             return false;
     550             :         }
     551           0 :     } else {
     552             :         // we should not get there ever
     553           0 :         LogPrint(BCLog::LLMQ, "%s - no public keys are provided\n", m_id_string);
     554           0 :         return false;
     555             :     }
     556           0 :     return true;
     557           0 : }
     558             : 
     559           0 : void BlsCheck::swap(BlsCheck& obj)
     560             : {
     561           0 :     std::swap(m_sig, obj.m_sig);
     562           0 :     std::swap(m_pubkeys, obj.m_pubkeys);
     563           0 :     std::swap(m_msg_hash, obj.m_msg_hash);
     564           0 :     std::swap(m_id_string, obj.m_id_string);
     565           0 : }
     566             : 
     567         792 : QuorumMembers GetAllQuorumMembers(Consensus::LLMQType llmqType, const UtilParameters& util_params, bool reset_cache)
     568             : {
     569         792 :     static RecursiveMutex cs_members;
     570         792 :     static std::map<Consensus::LLMQType, Uint256LruHashMap<QuorumMembers>> mapQuorumMembers GUARDED_BY(cs_members);
     571         792 :     static RecursiveMutex cs_indexed_members;
     572         792 :     static std::map<Consensus::LLMQType, unordered_lru_cache<std::pair<uint256, int>, QuorumMembers, StaticSaltedHasher>> mapIndexedQuorumMembers GUARDED_BY(cs_indexed_members);
     573             : 
     574         792 :     if (!util_params.m_chainman.IsQuorumTypeEnabled(llmqType, util_params.m_base_index->pprev)) {
     575           0 :         return {};
     576             :     }
     577             : 
     578         792 :     std::vector<CDeterministicMNCPtr> quorumMembers;
     579             :     {
     580         792 :         LOCK(cs_members);
     581         792 :         if (mapQuorumMembers.empty()) {
     582           5 :             InitQuorumsCache(mapQuorumMembers, util_params.m_chainman.GetConsensus());
     583           5 :         }
     584         792 :         if (reset_cache) {
     585          16 :             mapQuorumMembers[llmqType].clear();
     586         792 :         } else if (mapQuorumMembers[llmqType].get(util_params.m_base_index->GetBlockHash(), quorumMembers)) {
     587           9 :             return quorumMembers;
     588             :         }
     589         792 :     }
     590             : 
     591         783 :     const auto& llmq_params_opt = util_params.m_chainman.GetParams().GetLLMQ(llmqType);
     592         783 :     assert(llmq_params_opt.has_value());
     593         783 :     const auto& llmq_params = llmq_params_opt.value();
     594             : 
     595         783 :     if (IsQuorumRotationEnabled(llmq_params, util_params.m_base_index)) {
     596         788 :         if (LOCK(cs_indexed_members); mapIndexedQuorumMembers.empty()) {
     597           5 :             InitQuorumsCache(mapIndexedQuorumMembers, util_params.m_chainman.GetConsensus());
     598           5 :         }
     599             :         /*
     600             :          * Quorums created with rotation are now created in a different way. All signingActiveQuorumCount are created
     601             :          * during the period of dkgInterval. But they are not created exactly in the same block, they are spread
     602             :          * overtime: one quorum in each block until all signingActiveQuorumCount are created. The new concept of
     603             :          * quorumIndex is introduced in order to identify them. In every dkgInterval blocks (also called
     604             :          * CycleQuorumBaseBlock), the spread quorum creation starts like this: For quorumIndex = 0 :
     605             :          * signingActiveQuorumCount Quorum Q with quorumIndex is created at height CycleQuorumBaseBlock + quorumIndex
     606             :          */
     607             : 
     608         783 :         int quorumIndex = util_params.m_base_index->nHeight % llmq_params.dkgInterval;
     609         783 :         if (quorumIndex >= llmq_params.signingActiveQuorumCount) {
     610           0 :             return {};
     611             :         }
     612         783 :         int cycleQuorumBaseHeight = util_params.m_base_index->nHeight - quorumIndex;
     613         783 :         const CBlockIndex* pCycleQuorumBaseBlockIndex = util_params.m_base_index->GetAncestor(cycleQuorumBaseHeight);
     614             : 
     615             :         /*
     616             :          * Since mapQuorumMembers stores Quorum members per block hash, and we don't know yet the block hashes of blocks
     617             :          * for all quorumIndexes (since these blocks are not created yet) We store them in a second cache
     618             :          * mapIndexedQuorumMembers which stores them by {CycleQuorumBaseBlockHash, quorumIndex}
     619             :          */
     620         783 :         if (reset_cache) {
     621          16 :             LOCK(cs_indexed_members);
     622          16 :             mapIndexedQuorumMembers[llmqType].clear();
     623        1550 :         } else if (LOCK(cs_indexed_members); mapIndexedQuorumMembers[llmqType].get(
     624        1534 :                        std::pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), quorumIndex), quorumMembers)) {
     625           0 :             LOCK(cs_members);
     626           0 :             mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
     627           0 :             return quorumMembers;
     628           0 :         }
     629             : 
     630         783 :         auto q = ComputeQuorumMembersByQuarterRotation(llmq_params, util_params.replace_index(pCycleQuorumBaseBlockIndex));
     631         783 :         quorumMembers = q[quorumIndex];
     632             : 
     633         783 :         LOCK(cs_indexed_members);
     634        2349 :         for (const size_t i : util::irange(q.size())) {
     635        1566 :             mapIndexedQuorumMembers[llmqType].emplace(std::make_pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), i),
     636        1566 :                                                       std::move(q[i]));
     637             :         }
     638         783 :     } else {
     639           0 :         const CBlockIndex* pWorkBlockIndex = DeploymentActiveAfter(util_params.m_base_index,
     640           0 :                                                                    util_params.m_chainman.GetConsensus(),
     641             :                                                                    Consensus::DEPLOYMENT_V20)
     642           0 :                                                  ? util_params.m_base_index->GetAncestor(
     643           0 :                                                        util_params.m_base_index->nHeight - WORK_DIFF_DEPTH)
     644           0 :                                                  : util_params.m_base_index.get();
     645           0 :         CDeterministicMNList mn_list = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
     646           0 :         quorumMembers = ComputeQuorumMembers(llmqType, util_params.m_chainman.GetParams(), mn_list,
     647           0 :                                              util_params.m_base_index);
     648           0 :     }
     649             : 
     650         783 :     LOCK(cs_members);
     651         783 :     mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
     652         783 :     return quorumMembers;
     653         792 : }
     654             : 
     655          15 : uint256 DeterministicOutboundConnection(const uint256& proTxHash1, const uint256& proTxHash2)
     656             : {
     657             :     // We need to deterministically select who is going to initiate the connection. The naive way would be to simply
     658             :     // return the min(proTxHash1, proTxHash2), but this would create a bias towards MNs with a numerically low
     659             :     // hash. To fix this, we return the proTxHash that has the lowest value of:
     660             :     //   hash(min(proTxHash1, proTxHash2), max(proTxHash1, proTxHash2), proTxHashX)
     661             :     // where proTxHashX is the proTxHash to compare
     662          15 :     uint256 h1;
     663          15 :     uint256 h2;
     664          15 :     if (proTxHash1 < proTxHash2) {
     665           7 :         h1 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash1));
     666           7 :         h2 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash2));
     667           7 :     } else {
     668           8 :         h1 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash1));
     669           8 :         h2 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash2));
     670             :     }
     671          15 :     if (h1 < h2) {
     672           7 :         return proTxHash1;
     673             :     }
     674           8 :     return proTxHash2;
     675          15 : }
     676             : 
     677           0 : Uint256HashSet GetQuorumConnections(const Consensus::LLMQParams& llmqParams, const CSporkManager& sporkman,
     678             :                                     const UtilParameters& util_params, const uint256& forMember, bool onlyOutbound)
     679             : {
     680           0 :     if (IsAllMembersConnectedEnabled(llmqParams.type, sporkman)) {
     681           0 :         auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
     682           0 :         Uint256HashSet result;
     683             : 
     684           0 :         for (const auto& dmn : mns) {
     685           0 :             if (dmn->proTxHash == forMember) {
     686           0 :                 continue;
     687             :             }
     688             :             // Determine which of the two MNs (forMember vs dmn) should initiate the outbound connection and which
     689             :             // one should wait for the inbound connection. We do this in a deterministic way, so that even when we
     690             :             // end up with both connecting to each other, we know which one to disconnect
     691           0 :             uint256 deterministicOutbound = DeterministicOutboundConnection(forMember, dmn->proTxHash);
     692           0 :             if (!onlyOutbound || deterministicOutbound == dmn->proTxHash) {
     693           0 :                 result.emplace(dmn->proTxHash);
     694           0 :             }
     695             :         }
     696           0 :         return result;
     697           0 :     }
     698           0 :     return GetQuorumRelayMembers(llmqParams, util_params, forMember, onlyOutbound);
     699           0 : }
     700             : 
     701           0 : Uint256HashSet GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, const UtilParameters& util_params,
     702             :                                      const uint256& forMember, bool onlyOutbound)
     703             : {
     704           0 :     auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
     705           0 :     Uint256HashSet result;
     706             : 
     707           0 :     auto calcOutbound = [&](size_t i, const uint256& proTxHash) {
     708             :         // Relay to nodes at indexes (i+2^k)%n, where
     709             :         //   k: 0..max(1, floor(log2(n-1))-1)
     710             :         //   n: size of the quorum/ring
     711           0 :         Uint256HashSet r{};
     712           0 :         if (mns.size() == 1) {
     713             :             // No outbound connections are needed when there is one MN only.
     714             :             // Also note that trying to calculate results via the algorithm below
     715             :             // would result in an endless loop.
     716           0 :             return r;
     717             :         }
     718           0 :         int gap = 1;
     719           0 :         int gap_max = (int)mns.size() - 1;
     720           0 :         int k = 0;
     721           0 :         while ((gap_max >>= 1) || k <= 1) {
     722           0 :             size_t idx = (i + gap) % mns.size();
     723             :             // It doesn't matter if this node is going to be added to the resulting set or not,
     724             :             // we should always bump the gap and the k (step count) regardless.
     725             :             // Refusing to bump the gap results in an incomplete set in the best case scenario
     726             :             // (idx won't ever change again once we hit `==`). Not bumping k guarantees an endless
     727             :             // loop when the first or the second node we check is the one that should be skipped
     728             :             // (k <= 1 forever).
     729           0 :             gap <<= 1;
     730           0 :             k++;
     731           0 :             const auto& otherDmn = mns[idx];
     732           0 :             if (otherDmn->proTxHash == proTxHash) {
     733           0 :                 continue;
     734             :             }
     735           0 :             r.emplace(otherDmn->proTxHash);
     736             :         }
     737           0 :         return r;
     738           0 :     };
     739             : 
     740           0 :     for (const auto i : util::irange(mns.size())) {
     741           0 :         const auto& dmn = mns[i];
     742           0 :         if (dmn->proTxHash == forMember) {
     743           0 :             auto r = calcOutbound(i, dmn->proTxHash);
     744           0 :             result.insert(r.begin(), r.end());
     745           0 :         } else if (!onlyOutbound) {
     746           0 :             auto r = calcOutbound(i, dmn->proTxHash);
     747           0 :             if (r.count(forMember)) {
     748           0 :                 result.emplace(dmn->proTxHash);
     749           0 :             }
     750           0 :         }
     751             :     }
     752             : 
     753           0 :     return result;
     754           0 : }
     755             : 
     756           0 : std::unordered_set<size_t> CalcDeterministicWatchConnections(Consensus::LLMQType llmqType,
     757             :                                                              gsl::not_null<const CBlockIndex*> pQuorumBaseBlockIndex,
     758             :                                                              size_t memberCount, size_t connectionCount)
     759             : {
     760             :     static uint256 qwatchConnectionSeed;
     761             :     static std::atomic<bool> qwatchConnectionSeedGenerated{false};
     762           0 :     static RecursiveMutex qwatchConnectionSeedCs;
     763           0 :     if (!qwatchConnectionSeedGenerated) {
     764           0 :         LOCK(qwatchConnectionSeedCs);
     765           0 :         qwatchConnectionSeed = GetRandHash();
     766           0 :         qwatchConnectionSeedGenerated = true;
     767           0 :     }
     768             : 
     769           0 :     std::unordered_set<size_t> result;
     770           0 :     uint256 rnd = qwatchConnectionSeed;
     771           0 :     for ([[maybe_unused]] const auto _ : util::irange(connectionCount)) {
     772           0 :         rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash())));
     773           0 :         result.emplace(rnd.GetUint64(0) % memberCount);
     774             :     }
     775           0 :     return result;
     776           0 : }
     777             : 
     778             : } // namespace utils
     779             : } // namespace llmq

Generated by: LCOV version 1.16