LCOV - code coverage report
Current view: top level - src/llmq - utils.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 442 457 96.7 %
Date: 2026-06-25 07:23:43 Functions: 47 51 92.2 %

          Line data    Source code
       1             : // Copyright (c) 2018-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <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       91933 : std::string ToString(const QuorumMembers& members)
      35             : {
      36       91933 :     std::stringstream ss;
      37      241153 :     for (const auto& mn : members) {
      38      149220 :         ss << mn->proTxHash.ToString().substr(0, 4) << "|";
      39             :     }
      40       91933 :     return ss.str();
      41       91933 : }
      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       45450 :     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       15150 :     explicit PreviousQuorumQuarters(size_t s) : quarterHMinusC(s), quarterHMinus2C(s), quarterHMinus3C(s) {}
      63             : 
      64        7575 :     std::vector<QuorumQuarter*> GetCycles() { return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C}; }
      65        9718 :     std::vector<const QuorumQuarter*> GetCycles() const
      66             :     {
      67        9718 :         return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C};
      68             :     }
      69             : };
      70             : 
      71      143162 : 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      143162 :     uint256 h;
      78      143162 :     CSHA256 sha256;
      79      286324 :     sha256.Write(dmn->pdmnState->confirmedHashWithProRegTxHash.begin(),
      80      143162 :                  dmn->pdmnState->confirmedHashWithProRegTxHash.size());
      81      143162 :     sha256.Write(modifier.begin(), modifier.size());
      82      143162 :     sha256.Finalize(h.begin());
      83      143162 :     return UintToArith256(h);
      84             : }
      85             : 
      86       29567 : 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       29567 :     const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
      91             : 
      92       29567 :     if (DeploymentActiveAfter(pWorkBlockIndex, consensus_params, Consensus::DEPLOYMENT_V20)) {
      93             :         // v20 is active: calculate modifier using the new way.
      94       22300 :         auto cbcl = GetNonNullCoinbaseChainlock(pWorkBlockIndex);
      95       22300 :         if (cbcl.has_value()) {
      96             :             // We have a non-null CL signature: calculate modifier using this CL signature
      97        9136 :             auto& [bestCLSignature, bestCLHeightDiff] = cbcl.value();
      98       18260 :             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       13164 :         return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
     102       22294 :     }
     103             : 
     104             :     // v20 isn't active yet: calculate modifier using the usual way
     105        7267 :     if (llmqParams.useRotation) {
     106        6329 :         return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
     107             :     }
     108         938 :     return ::SerializeHash(std::make_pair(llmqParams.type, pCycleQuorumBaseBlockIndex->GetBlockHash()));
     109       29573 : }
     110             : 
     111        4859 : std::vector<MasternodeScore> CalculateScoresForQuorum(QuorumMembers&& dmns, const uint256& modifier, const bool onlyEvoNodes)
     112             : {
     113        4859 :     std::vector<MasternodeScore> scores;
     114        4859 :     scores.reserve(dmns.size());
     115             : 
     116       17096 :     for (auto& dmn : dmns) {
     117       12237 :         if (dmn->pdmnState->IsBanned()) continue;
     118       12237 :         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         486 :             continue;
     122             :         }
     123       11751 :         if (onlyEvoNodes && dmn->nType != MnType::Evo) {
     124           0 :             continue;
     125             :         }
     126       11751 :         scores.emplace_back(calculateQuorumScore(dmn, modifier), std::move(dmn));
     127             :     };
     128        4859 :     return scores;
     129        4859 : }
     130             : 
     131       26847 : std::vector<MasternodeScore> CalculateScoresForQuorum(const CDeterministicMNList& mn_list, const uint256& modifier,
     132             :                                                       const bool onlyEvoNodes)
     133             : {
     134       26847 :     std::vector<MasternodeScore> scores;
     135       26847 :     scores.reserve(mn_list.GetCounts().total());
     136             : 
     137      172945 :     mn_list.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
     138      146100 :         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        2206 :             return;
     142             :         }
     143      143894 :         if (onlyEvoNodes && dmn->nType != MnType::Evo) {
     144       12483 :             return;
     145             :         }
     146      131411 :         scores.emplace_back(calculateQuorumScore(dmn, modifier), dmn);
     147      146100 :     });
     148       26845 :     return scores;
     149       26851 : }
     150             : 
     151             : /**
     152             :  * Calculate a quorum based on the modifier. The resulting list is deterministically sorted by score
     153             :  */
     154             : template <typename List>
     155       31704 : QuorumMembers CalculateQuorum(List&& mn_list, const uint256& modifier, size_t maxSize = 0, const bool onlyEvoNodes = false)
     156             : {
     157       31704 :     auto scores = CalculateScoresForQuorum(std::forward<List>(mn_list), modifier, onlyEvoNodes);
     158             : 
     159             :     // sort is descending order
     160      347160 :     std::sort(scores.rbegin(), scores.rend(), [](const MasternodeScore& a, const MasternodeScore& b) {
     161      315456 :         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      315456 :         return a.m_score < b.m_score;
     167      315456 :     });
     168             : 
     169             :     // return top maxSize entries only (if specified)
     170       31704 :     if (maxSize > 0 && scores.size() > maxSize) {
     171        2708 :         scores.resize(maxSize);
     172        2708 :     }
     173             : 
     174       31704 :     QuorumMembers result;
     175       31704 :     result.reserve(scores.size());
     176      164145 :     for (auto& [_, node] : scores) {
     177      132441 :         result.emplace_back(std::move(node));
     178             :     }
     179       31704 :     return result;
     180       31704 : }
     181             : 
     182       11324 : 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       11324 :     if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
     189             :         ASSERT_IF_DEBUG(false);
     190           0 :         return {};
     191             :     }
     192             : 
     193       11324 :     std::vector<CDeterministicMNCPtr> sortedCombinedMns;
     194             :     {
     195       22648 :         const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(
     196       11324 :             pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
     197       11324 :         auto mn_list = dmnman.GetListForBlock(pWorkBlockIndex);
     198       11324 :         const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
     199       11324 :         auto sortedAllMns = CalculateQuorum(mn_list, modifier);
     200             : 
     201       11324 :         std::vector<CDeterministicMNCPtr> usedMNs;
     202       11324 :         size_t i{0};
     203       73402 :         for (const auto& dmn : sortedAllMns) {
     204       62078 :             if (snapshot.activeQuorumMembers[i]) {
     205       33999 :                 usedMNs.push_back(dmn);
     206       33999 :             } else {
     207       28079 :                 if (!dmn->pdmnState->IsBanned()) {
     208             :                     // the list begins with all the unused MNs
     209       28079 :                     sortedCombinedMns.push_back(dmn);
     210       28079 :                 }
     211             :             }
     212       62078 :             i++;
     213             :         }
     214             : 
     215             :         // Now add the already used MNs to the end of the list
     216       11324 :         std::move(usedMNs.begin(), usedMNs.end(), std::back_inserter(sortedCombinedMns));
     217       11324 :     }
     218             : 
     219       11324 :     if (LogAcceptDebug(BCLog::LLMQ)) {
     220       11324 :         LogPrint(BCLog::LLMQ, "%s h[%d] from[%d] sortedCombinedMns[%s]\n", __func__,
     221             :                  pCycleQuorumBaseBlockIndex->nHeight, nHeight, ToString(sortedCombinedMns));
     222       11324 :     }
     223             : 
     224       11324 :     size_t numQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
     225       11324 :     size_t quorumSize = static_cast<size_t>(llmqParams.size);
     226       11324 :     auto quarterSize{quorumSize / 4};
     227             : 
     228       11324 :     std::vector<QuorumMembers> quarterQuorumMembers(numQuorums);
     229             : 
     230       11324 :     if (sortedCombinedMns.empty()) {
     231         478 :         return quarterQuorumMembers;
     232             :     }
     233             : 
     234       10846 :     switch (snapshot.mnSkipListMode) {
     235             :     case SnapshotSkipMode::MODE_NO_SKIPPING: {
     236        8607 :         auto itm = sortedCombinedMns.begin();
     237       25821 :         for (const size_t i : util::irange(numQuorums)) {
     238       34428 :             while (quarterQuorumMembers[i].size() < quarterSize) {
     239       17214 :                 quarterQuorumMembers[i].push_back(*itm);
     240       17214 :                 itm++;
     241       17214 :                 if (itm == sortedCombinedMns.end()) {
     242        2615 :                     itm = sortedCombinedMns.begin();
     243        2615 :                 }
     244             :             }
     245             :         }
     246        8607 :         return quarterQuorumMembers;
     247             :     }
     248             :     case SnapshotSkipMode::MODE_SKIPPING_ENTRIES: // List holds entries to be skipped
     249             :     {
     250        2239 :         size_t first_entry_index{0};
     251        2239 :         std::vector<int> processesdSkipList;
     252        6778 :         for (const auto& s : snapshot.mnSkipList) {
     253        4539 :             if (first_entry_index == 0) {
     254        3208 :                 first_entry_index = s;
     255        3208 :                 processesdSkipList.push_back(s);
     256        3208 :             } else {
     257        1331 :                 processesdSkipList.push_back(first_entry_index + s);
     258             :             }
     259             :         }
     260             : 
     261        2239 :         int idx = 0;
     262        2239 :         auto itsk = processesdSkipList.begin();
     263        6717 :         for (const size_t i : util::irange(numQuorums)) {
     264       13495 :             while (quarterQuorumMembers[i].size() < quarterSize) {
     265        9017 :                 if (itsk != processesdSkipList.end() && idx == *itsk) {
     266        4539 :                     itsk++;
     267        4539 :                 } else {
     268        4478 :                     quarterQuorumMembers[i].push_back(sortedCombinedMns[idx]);
     269             :                 }
     270        9017 :                 idx++;
     271        9017 :                 if (idx == static_cast<int>(sortedCombinedMns.size())) {
     272        1026 :                     idx = 0;
     273        1026 :                 }
     274             :             }
     275             :         }
     276        2239 :         return quarterQuorumMembers;
     277        2239 :     }
     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       11324 : }
     284             : 
     285        5803 : QuorumMembers ComputeQuorumMembers(Consensus::LLMQType llmqType, const CChainParams& chainparams,
     286             :                                    const CDeterministicMNList& mn_list, const CBlockIndex* pQuorumBaseBlockIndex)
     287             : {
     288        7773 :     bool EvoOnly = (chainparams.GetConsensus().llmqTypePlatform == llmqType) &&
     289        1970 :                    DeploymentActiveAfter(pQuorumBaseBlockIndex, chainparams.GetConsensus(), Consensus::DEPLOYMENT_V19);
     290        5803 :     const auto& llmq_params_opt = chainparams.GetLLMQ(llmqType);
     291        5803 :     assert(llmq_params_opt.has_value());
     292        5803 :     if (llmq_params_opt->useRotation || pQuorumBaseBlockIndex->nHeight % llmq_params_opt->dkgInterval != 0) {
     293             :         ASSERT_IF_DEBUG(false);
     294           0 :         return {};
     295             :     }
     296             : 
     297        5803 :     const auto modifier = GetHashModifier(llmq_params_opt.value(), chainparams.GetConsensus(), pQuorumBaseBlockIndex);
     298        5803 :     return CalculateQuorum(mn_list, modifier, llmq_params_opt->size, EvoOnly);
     299        5803 : }
     300             : 
     301        4859 : 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        4859 :     if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
     307             :         ASSERT_IF_DEBUG(false);
     308           0 :         return;
     309             :     }
     310             : 
     311        4859 :     const auto allMnsTotal = allMns.GetCounts().total();
     312        4859 :     quorumSnapshot.activeQuorumMembers.resize(allMnsTotal);
     313        4859 :     const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
     314        4859 :     auto sortedAllMns = CalculateQuorum(allMns, modifier);
     315             : 
     316        4859 :     LogPrint(BCLog::LLMQ, "BuildQuorumSnapshot h[%d] numMns[%d]\n", pCycleQuorumBaseBlockIndex->nHeight,
     317             :              allMnsTotal);
     318             : 
     319        4859 :     std::fill(quorumSnapshot.activeQuorumMembers.begin(), quorumSnapshot.activeQuorumMembers.end(), false);
     320        4859 :     size_t index = {};
     321       32819 :     for (const auto& dmn : sortedAllMns) {
     322       27960 :         if (mnUsedAtH.HasMN(dmn->proTxHash)) {
     323       16209 :             quorumSnapshot.activeQuorumMembers[index] = true;
     324       16209 :         }
     325       27960 :         index++;
     326             :     }
     327             : 
     328        4859 :     if (skipList.empty()) {
     329        3596 :         quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_NO_SKIPPING;
     330        3596 :         quorumSnapshot.mnSkipList.clear();
     331        3596 :     } else {
     332        1263 :         quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
     333        1263 :         quorumSnapshot.mnSkipList = std::move(skipList);
     334             :     }
     335        4859 : }
     336             : 
     337        7575 : std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams,
     338             :                                                         const llmq::UtilParameters& util_params,
     339             :                                                         const CDeterministicMNList& allMns,
     340             :                                                         const PreviousQuorumQuarters& previousQuarters)
     341             : {
     342        7575 :     if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
     343             :         ASSERT_IF_DEBUG(false);
     344           0 :         return {};
     345             :     }
     346             : 
     347        7575 :     size_t nQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
     348        7575 :     std::vector<QuorumMembers> quarterQuorumMembers{nQuorums};
     349             : 
     350        7575 :     size_t quorumSize = static_cast<size_t>(llmqParams.size);
     351        7575 :     auto quarterSize{quorumSize / 4};
     352        7575 :     const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index);
     353             : 
     354        7575 :     if (allMns.GetCounts().enabled() < quarterSize) {
     355        2716 :         return quarterQuorumMembers;
     356             :     }
     357             : 
     358        4859 :     auto MnsUsedAtH = CDeterministicMNList();
     359        4859 :     std::vector<CDeterministicMNList> MnsUsedAtHIndexed{nQuorums};
     360             : 
     361        9718 :     bool skipRemovedMNs = DeploymentActiveAfter(util_params.m_base_index, util_params.m_chainman.GetConsensus(),
     362        4931 :                                                 Consensus::DEPLOYMENT_V19) ||
     363          72 :                           (util_params.m_chainman.GetParams().NetworkIDString() == CBaseChainParams::TESTNET);
     364             : 
     365       14577 :     for (const size_t idx : util::irange(nQuorums)) {
     366       38872 :         for (auto* prev_cycle : previousQuarters.GetCycles()) {
     367       50846 :             for (const auto& mn : prev_cycle->m_members[idx]) {
     368       21692 :                 if (skipRemovedMNs && !allMns.HasMN(mn->proTxHash)) {
     369           0 :                     continue;
     370             :                 }
     371       21692 :                 if (allMns.IsMNPoSeBanned(mn->proTxHash)) {
     372         240 :                     continue;
     373             :                 }
     374             :                 try {
     375       21452 :                     MnsUsedAtH.AddMN(mn);
     376       21452 :                 } catch (const std::runtime_error& e) {
     377        5243 :                 }
     378             :                 try {
     379       21452 :                     MnsUsedAtHIndexed[idx].AddMN(mn);
     380       21452 :                 } catch (const std::runtime_error& e) {
     381        1865 :                 }
     382             :             }
     383             :         }
     384             :     }
     385             : 
     386        4859 :     std::vector<CDeterministicMNCPtr> MnsNotUsedAtH;
     387       34196 :     allMns.ForEachMNShared(/*onlyValid=*/false, [&MnsUsedAtH, &MnsNotUsedAtH](const auto& dmn) {
     388       29337 :         if (!MnsUsedAtH.HasMN(dmn->proTxHash)) {
     389       13128 :             if (!dmn->pdmnState->IsBanned()) {
     390       12237 :                 MnsNotUsedAtH.push_back(dmn);
     391       12237 :             }
     392       13128 :         }
     393       29337 :     });
     394             : 
     395        4859 :     auto sortedMnsUsedAtHM = CalculateQuorum(MnsUsedAtH, modifier);
     396        4859 :     auto sortedCombinedMnsList = CalculateQuorum(std::move(MnsNotUsedAtH), modifier);
     397       21068 :     for (auto& m : sortedMnsUsedAtHM) {
     398       16209 :         sortedCombinedMnsList.push_back(std::move(m));
     399             :     }
     400             : 
     401        4859 :     if (LogAcceptDebug(BCLog::LLMQ)) {
     402        4859 :         LogPrint(BCLog::LLMQ, "%s h[%d] sortedCombinedMns[%s]\n", __func__, util_params.m_base_index->nHeight,
     403             :                  ToString(sortedCombinedMnsList));
     404        4859 :     }
     405             : 
     406        4859 :     std::vector<int> skipList;
     407        4859 :     size_t firstSkippedIndex = 0;
     408        4859 :     size_t idx{0};
     409       14577 :     for (const size_t i : util::irange(nQuorums)) {
     410        9718 :         auto usedMNsCount = MnsUsedAtHIndexed[i].GetCounts().total();
     411        9718 :         bool updated{false};
     412        9718 :         size_t initial_loop_idx = idx;
     413       20340 :         while (quarterQuorumMembers[i].size() < quarterSize && (usedMNsCount + quarterQuorumMembers[i].size() < sortedCombinedMnsList.size())) {
     414       10622 :             bool skip{true};
     415       10622 :             if (!MnsUsedAtHIndexed[i].HasMN(sortedCombinedMnsList[idx]->proTxHash)) {
     416             :                 try {
     417             :                     // NOTE: AddMN is the one that can throw exceptions, must be exicuted first
     418        7899 :                     MnsUsedAtHIndexed[i].AddMN(sortedCombinedMnsList[idx]);
     419        7899 :                     quarterQuorumMembers[i].push_back(sortedCombinedMnsList[idx]);
     420        7899 :                     updated = true;
     421        7899 :                     skip = false;
     422        7899 :                 } catch (const std::runtime_error& e) {
     423           0 :                 }
     424        7899 :             }
     425       10622 :             if (skip) {
     426        2723 :                 if (firstSkippedIndex == 0) {
     427        1868 :                     firstSkippedIndex = idx;
     428        1868 :                     skipList.push_back(idx);
     429        1868 :                 } else {
     430         855 :                     skipList.push_back(idx - firstSkippedIndex);
     431             :                 }
     432        2723 :             }
     433       10622 :             if (++idx == sortedCombinedMnsList.size()) {
     434         795 :                 idx = 0;
     435         795 :             }
     436       10622 :             if (idx == initial_loop_idx) {
     437             :                 // we made full "while" loop
     438         241 :                 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         241 :                 updated = false;
     444         241 :             }
     445             :         }
     446             :     }
     447             : 
     448        4859 :     llmq::CQuorumSnapshot quorumSnapshot{};
     449        4859 :     BuildQuorumSnapshot(llmqParams, util_params.m_chainman.GetConsensus(), allMns, MnsUsedAtH, sortedCombinedMnsList,
     450        4859 :                         quorumSnapshot, skipList, util_params.m_base_index);
     451        4859 :     util_params.m_qsnapman.StoreSnapshotForBlock(llmqParams.type, util_params.m_base_index, quorumSnapshot);
     452             : 
     453        4859 :     return quarterQuorumMembers;
     454       14683 : }
     455             : 
     456        7575 : std::vector<QuorumMembers> ComputeQuorumMembersByQuarterRotation(const Consensus::LLMQParams& llmqParams,
     457             :                                                                  const llmq::UtilParameters& util_params)
     458             : {
     459        7575 :     const int cycleLength = llmqParams.dkgInterval;
     460        7575 :     if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
     461             :         ASSERT_IF_DEBUG(false);
     462           0 :         return {};
     463             :     }
     464        7575 :     const auto nQuorums{static_cast<size_t>(llmqParams.signingActiveQuorumCount)};
     465             : 
     466        7575 :     const CBlockIndex* pWorkBlockIndex = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
     467             :                                                                                llmq::WORK_DIFF_DEPTH);
     468        7575 :     CDeterministicMNList allMns = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
     469        7575 :     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        7575 :     PreviousQuorumQuarters previousQuarters(nQuorums);
     473        7575 :     auto prev_cycles{previousQuarters.GetCycles()};
     474       18899 :     for (size_t idx{0}; idx < prev_cycles.size(); idx++) {
     475       31390 :         prev_cycles[idx]->m_cycle_index = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
     476       15695 :                                                                                 (cycleLength * (idx + 1)));
     477       15695 :         if (auto opt_snap = util_params.m_qsnapman.GetSnapshotForBlock(llmqParams.type, prev_cycles[idx]->m_cycle_index);
     478       31390 :             opt_snap.has_value()) {
     479       11324 :             prev_cycles[idx]->m_snap = opt_snap.value();
     480       11324 :         } else {
     481             :             // TODO: Check if it is triggered from outside (P2P, block validation) and maybe throw an exception
     482             :             // assert(false);
     483        4371 :             break;
     484             :         }
     485       11324 :         prev_cycles[idx]->m_members = GetQuorumQuarterMembersBySnapshot(llmqParams, util_params.m_dmnman,
     486       11324 :                                                                         util_params.m_chainman.GetConsensus(),
     487       11324 :                                                                         prev_cycles[idx]->m_cycle_index,
     488       11324 :                                                                         prev_cycles[idx]->m_snap,
     489       11324 :                                                                         util_params.m_base_index->nHeight);
     490       11324 :     }
     491             : 
     492        7575 :     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        7575 :     if (LogAcceptDebug(BCLog::LLMQ)) {
     497       22725 :         for (const size_t i : util::irange(nQuorums)) {
     498       15150 :             std::stringstream ss;
     499       60600 :             for (size_t idx = prev_cycles.size(); idx-- > 0;) {
     500       45450 :                 ss << strprintf(" %dCmns[%s]", idx, ToString(prev_cycles[idx]->m_members[i]));
     501             :             }
     502       15150 :             ss << strprintf(" new[%s]", ToString(newQuarterMembers[i]));
     503       15150 :             LogPrint(BCLog::LLMQ, "QuarterComposition h[%d] i[%d]:%s\n", util_params.m_base_index->nHeight, i, ss.str());
     504       15150 :         }
     505        7575 :     }
     506             : 
     507        7575 :     std::vector<QuorumMembers> quorumMembers(nQuorums);
     508       22725 :     for (const size_t i : util::irange(nQuorums)) {
     509             :         // Move elements from previous quarters into quorumMembers
     510       60600 :         for (auto* prev_cycle : prev_cycles | std::views::reverse) {
     511       45450 :             std::move(prev_cycle->m_members[i].begin(), prev_cycle->m_members[i].end(),
     512       45450 :                       std::back_inserter(quorumMembers[i]));
     513             :         }
     514       15150 :         std::move(newQuarterMembers[i].begin(), newQuarterMembers[i].end(), std::back_inserter(quorumMembers[i]));
     515       15150 :         if (LogAcceptDebug(BCLog::LLMQ)) {
     516       15150 :             LogPrint(BCLog::LLMQ, "QuorumComposition h[%d] i[%d]: [%s]\n", util_params.m_base_index->nHeight, i,
     517             :                      ToString(quorumMembers[i]));
     518       15150 :         }
     519             :     }
     520             : 
     521        7575 :     return quorumMembers;
     522        7575 : }
     523             : } // anonymous namespace
     524             : 
     525             : namespace llmq {
     526             : namespace utils {
     527       38904 : BlsCheck::BlsCheck() = default;
     528             : 
     529       19452 : BlsCheck::BlsCheck(CBLSSignature sig, std::vector<CBLSPublicKey> pubkeys, uint256 msg_hash, std::string id_string) :
     530        9726 :     m_sig(sig),
     531        9726 :     m_pubkeys(pubkeys),
     532        9726 :     m_msg_hash(msg_hash),
     533        9726 :     m_id_string(id_string)
     534        9726 : {
     535       19452 : }
     536             : 
     537       60610 : BlsCheck::~BlsCheck() = default;
     538             : 
     539        9725 : bool BlsCheck::operator()()
     540             : {
     541        9725 :     if (m_pubkeys.size() > 1) {
     542        4805 :         if (!m_sig.VerifySecureAggregated(m_pubkeys, m_msg_hash)) {
     543           2 :             LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
     544           2 :             return false;
     545             :         }
     546        9723 :     } else if (m_pubkeys.size() == 1) {
     547        4920 :         if (!m_sig.VerifyInsecure(m_pubkeys.back(), m_msg_hash)) {
     548           2 :             LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
     549           2 :             return false;
     550             :         }
     551        4918 :     } 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        9721 :     return true;
     557        9725 : }
     558             : 
     559       19452 : void BlsCheck::swap(BlsCheck& obj)
     560             : {
     561       19452 :     std::swap(m_sig, obj.m_sig);
     562       19452 :     std::swap(m_pubkeys, obj.m_pubkeys);
     563       19452 :     std::swap(m_msg_hash, obj.m_msg_hash);
     564       19452 :     std::swap(m_id_string, obj.m_id_string);
     565       19452 : }
     566             : 
     567      661951 : QuorumMembers GetAllQuorumMembers(Consensus::LLMQType llmqType, const UtilParameters& util_params, bool reset_cache)
     568             : {
     569      661951 :     static RecursiveMutex cs_members;
     570      661951 :     static std::map<Consensus::LLMQType, Uint256LruHashMap<QuorumMembers>> mapQuorumMembers GUARDED_BY(cs_members);
     571      661951 :     static RecursiveMutex cs_indexed_members;
     572      661951 :     static std::map<Consensus::LLMQType, unordered_lru_cache<std::pair<uint256, int>, QuorumMembers, StaticSaltedHasher>> mapIndexedQuorumMembers GUARDED_BY(cs_indexed_members);
     573             : 
     574      661951 :     if (!util_params.m_chainman.IsQuorumTypeEnabled(llmqType, util_params.m_base_index->pprev)) {
     575        3355 :         return {};
     576             :     }
     577             : 
     578      658596 :     std::vector<CDeterministicMNCPtr> quorumMembers;
     579             :     {
     580      658596 :         LOCK(cs_members);
     581      658594 :         if (mapQuorumMembers.empty()) {
     582        1012 :             InitQuorumsCache(mapQuorumMembers, util_params.m_chainman.GetConsensus());
     583        1012 :         }
     584      658594 :         if (reset_cache) {
     585         696 :             mapQuorumMembers[llmqType].clear();
     586      658594 :         } else if (mapQuorumMembers[llmqType].get(util_params.m_base_index->GetBlockHash(), quorumMembers)) {
     587      642974 :             return quorumMembers;
     588             :         }
     589      658594 :     }
     590             : 
     591       15620 :     const auto& llmq_params_opt = util_params.m_chainman.GetParams().GetLLMQ(llmqType);
     592       15620 :     assert(llmq_params_opt.has_value());
     593       15620 :     const auto& llmq_params = llmq_params_opt.value();
     594             : 
     595       15620 :     if (IsQuorumRotationEnabled(llmq_params, util_params.m_base_index)) {
     596       10786 :         if (LOCK(cs_indexed_members); mapIndexedQuorumMembers.empty()) {
     597         969 :             InitQuorumsCache(mapIndexedQuorumMembers, util_params.m_chainman.GetConsensus());
     598         969 :         }
     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        9817 :         int quorumIndex = util_params.m_base_index->nHeight % llmq_params.dkgInterval;
     609        9817 :         if (quorumIndex >= llmq_params.signingActiveQuorumCount) {
     610           0 :             return {};
     611             :         }
     612        9817 :         int cycleQuorumBaseHeight = util_params.m_base_index->nHeight - quorumIndex;
     613        9817 :         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        9817 :         if (reset_cache) {
     621         696 :             LOCK(cs_indexed_members);
     622         696 :             mapIndexedQuorumMembers[llmqType].clear();
     623       18938 :         } else if (LOCK(cs_indexed_members); mapIndexedQuorumMembers[llmqType].get(
     624       18242 :                        std::pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), quorumIndex), quorumMembers)) {
     625        2242 :             LOCK(cs_members);
     626        2242 :             mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
     627        2242 :             return quorumMembers;
     628        2242 :         }
     629             : 
     630        7575 :         auto q = ComputeQuorumMembersByQuarterRotation(llmq_params, util_params.replace_index(pCycleQuorumBaseBlockIndex));
     631        7575 :         quorumMembers = q[quorumIndex];
     632             : 
     633        7575 :         LOCK(cs_indexed_members);
     634       22725 :         for (const size_t i : util::irange(q.size())) {
     635       15150 :             mapIndexedQuorumMembers[llmqType].emplace(std::make_pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), i),
     636       15150 :                                                       std::move(q[i]));
     637             :         }
     638        7575 :     } else {
     639       16471 :         const CBlockIndex* pWorkBlockIndex = DeploymentActiveAfter(util_params.m_base_index,
     640        5803 :                                                                    util_params.m_chainman.GetConsensus(),
     641             :                                                                    Consensus::DEPLOYMENT_V20)
     642        9730 :                                                  ? util_params.m_base_index->GetAncestor(
     643        4865 :                                                        util_params.m_base_index->nHeight - WORK_DIFF_DEPTH)
     644         938 :                                                  : util_params.m_base_index.get();
     645        5803 :         CDeterministicMNList mn_list = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
     646       11607 :         quorumMembers = ComputeQuorumMembers(llmqType, util_params.m_chainman.GetParams(), mn_list,
     647        5803 :                                              util_params.m_base_index);
     648        5804 :     }
     649             : 
     650       13377 :     LOCK(cs_members);
     651       13378 :     mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
     652       13378 :     return quorumMembers;
     653      661965 : }
     654             : 
     655      300797 : 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      300797 :     uint256 h1;
     663      300797 :     uint256 h2;
     664      300797 :     if (proTxHash1 < proTxHash2) {
     665      154235 :         h1 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash1));
     666      154235 :         h2 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash2));
     667      154235 :     } else {
     668      146562 :         h1 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash1));
     669      146562 :         h2 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash2));
     670             :     }
     671      300797 :     if (h1 < h2) {
     672      151564 :         return proTxHash1;
     673             :     }
     674      149233 :     return proTxHash2;
     675      300797 : }
     676             : 
     677      265741 : Uint256HashSet GetQuorumConnections(const Consensus::LLMQParams& llmqParams, const CSporkManager& sporkman,
     678             :                                     const UtilParameters& util_params, const uint256& forMember, bool onlyOutbound)
     679             : {
     680      265741 :     if (IsAllMembersConnectedEnabled(llmqParams.type, sporkman)) {
     681      101872 :         auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
     682      101872 :         Uint256HashSet result;
     683             : 
     684      477274 :         for (const auto& dmn : mns) {
     685      375402 :             if (dmn->proTxHash == forMember) {
     686       74867 :                 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      300535 :             uint256 deterministicOutbound = DeterministicOutboundConnection(forMember, dmn->proTxHash);
     692      300535 :             if (!onlyOutbound || deterministicOutbound == dmn->proTxHash) {
     693      180378 :                 result.emplace(dmn->proTxHash);
     694      180378 :             }
     695             :         }
     696      101872 :         return result;
     697      101872 :     }
     698      163869 :     return GetQuorumRelayMembers(llmqParams, util_params, forMember, onlyOutbound);
     699      265741 : }
     700             : 
     701      257095 : Uint256HashSet GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, const UtilParameters& util_params,
     702             :                                      const uint256& forMember, bool onlyOutbound)
     703             : {
     704      257095 :     auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
     705      257095 :     Uint256HashSet result;
     706             : 
     707      555314 :     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      298219 :         Uint256HashSet r{};
     712      298219 :         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        8891 :             return r;
     717             :         }
     718      289328 :         int gap = 1;
     719      289328 :         int gap_max = (int)mns.size() - 1;
     720      289328 :         int k = 0;
     721      867984 :         while ((gap_max >>= 1) || k <= 1) {
     722      578656 :             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      578656 :             gap <<= 1;
     730      578656 :             k++;
     731      578656 :             const auto& otherDmn = mns[idx];
     732      578656 :             if (otherDmn->proTxHash == proTxHash) {
     733       85887 :                 continue;
     734             :             }
     735      492768 :             r.emplace(otherDmn->proTxHash);
     736             :         }
     737      289328 :         return r;
     738      298221 :     };
     739             : 
     740      981843 :     for (const auto i : util::irange(mns.size())) {
     741      724747 :         const auto& dmn = mns[i];
     742      724747 :         if (dmn->proTxHash == forMember) {
     743      219919 :             auto r = calcOutbound(i, dmn->proTxHash);
     744      219920 :             result.insert(r.begin(), r.end());
     745      724748 :         } else if (!onlyOutbound) {
     746       78299 :             auto r = calcOutbound(i, dmn->proTxHash);
     747       78299 :             if (r.count(forMember)) {
     748       31468 :                 result.emplace(dmn->proTxHash);
     749       31468 :             }
     750       78299 :         }
     751             :     }
     752             : 
     753      257093 :     return result;
     754      257097 : }
     755             : 
     756       18372 : 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       18372 :     static RecursiveMutex qwatchConnectionSeedCs;
     763       18372 :     if (!qwatchConnectionSeedGenerated) {
     764         217 :         LOCK(qwatchConnectionSeedCs);
     765         217 :         qwatchConnectionSeed = GetRandHash();
     766         217 :         qwatchConnectionSeedGenerated = true;
     767         217 :     }
     768             : 
     769       18372 :     std::unordered_set<size_t> result;
     770       18372 :     uint256 rnd = qwatchConnectionSeed;
     771       36744 :     for ([[maybe_unused]] const auto _ : util::irange(connectionCount)) {
     772       18372 :         rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash())));
     773       18372 :         result.emplace(rnd.GetUint64(0) % memberCount);
     774             :     }
     775       18372 :     return result;
     776       18372 : }
     777             : 
     778             : } // namespace utils
     779             : } // namespace llmq

Generated by: LCOV version 1.16