LCOV - code coverage report
Current view: top level - src/llmq - quorums.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 31 175 17.7 %
Date: 2026-08-09 10:51:41 Functions: 3 22 13.6 %

          Line data    Source code
       1             : // Copyright (c) 2018 The Dash Core developers
       2             : // Copyright (c) 2023 The PIVX developers
       3             : // Distributed under the MIT/X11 software license, see the accompanying
       4             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       5             : 
       6             : #include "quorums.h"
       7             : 
       8             : #include "activemasternode.h"
       9             : #include "chainparams.h"
      10             : #include "cxxtimer.h"
      11             : #include "evo/deterministicmns.h"
      12             : #include "llmq/quorums_connections.h"
      13             : #include "logging.h"
      14             : #include "quorums_blockprocessor.h"
      15             : #include "quorums_commitment.h"
      16             : #include "quorums_dkgsessionmgr.h"
      17             : #include "shutdown.h"
      18             : #include "tiertwo/tiertwo_sync_state.h"
      19             : #include "univalue.h"
      20             : #include "validation.h"
      21             : 
      22             : #include <cstddef>
      23             : #include <iostream>
      24             : 
      25             : namespace llmq
      26             : {
      27             : 
      28             : static const std::string DB_QUORUM_SK_SHARE = "q_Qsk";
      29             : static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec";
      30             : 
      31             : std::unique_ptr<CQuorumManager> quorumManager{nullptr};
      32             : 
      33           0 : static uint256 MakeQuorumKey(const CQuorum& q)
      34             : {
      35           0 :     CHashWriter hw(SER_NETWORK, 0);
      36           0 :     hw << (uint8_t)q.params.type;
      37           0 :     hw << q.qc.quorumHash;
      38           0 :     for (const auto& dmn : q.members) {
      39           0 :         hw << dmn->proTxHash;
      40             :     }
      41           0 :     return hw.GetHash();
      42             : }
      43             : 
      44           0 : CQuorum::~CQuorum()
      45             : {
      46             :     // most likely the thread is already done
      47           0 :     stopCachePopulatorThread = true;
      48             :     // watch out to not join the thread when we're called from inside the thread, which might happen on shutdown. This
      49             :     // is because on shutdown the thread is the last owner of the shared CQuorum instance and thus the destroyer of it.
      50           0 :     if (cachePopulatorThread.joinable() && cachePopulatorThread.get_id() != std::this_thread::get_id()) {
      51           0 :         cachePopulatorThread.join();
      52             :     }
      53           0 : }
      54             : 
      55           0 : void CQuorum::Init(const CFinalCommitment& _qc, const CBlockIndex* _pindexQuorum, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members)
      56             : {
      57           0 :     qc = _qc;
      58           0 :     pindexQuorum = _pindexQuorum;
      59           0 :     members = _members;
      60           0 :     minedBlockHash = _minedBlockHash;
      61           0 : }
      62             : 
      63           0 : bool CQuorum::IsMember(const uint256& proTxHash) const
      64             : {
      65           0 :     for (auto& dmn : members) {
      66           0 :         if (dmn->proTxHash == proTxHash) {
      67           0 :             return true;
      68             :         }
      69             :     }
      70           0 :     return false;
      71             : }
      72             : 
      73           0 : bool CQuorum::IsValidMember(const uint256& proTxHash) const
      74             : {
      75           0 :     for (size_t i = 0; i < members.size(); i++) {
      76           0 :         if (members[i]->proTxHash == proTxHash) {
      77           0 :             return qc.validMembers[i];
      78             :         }
      79             :     }
      80             :     return false;
      81             : }
      82             : 
      83           0 : CBLSPublicKey CQuorum::GetPubKeyShare(size_t memberIdx) const
      84             : {
      85           0 :     if (quorumVvec == nullptr || memberIdx >= members.size() || !qc.validMembers[memberIdx]) {
      86           0 :         return CBLSPublicKey();
      87             :     }
      88           0 :     auto& m = members[memberIdx];
      89           0 :     return blsCache.BuildPubKeyShare(m->proTxHash, quorumVvec, CBLSId(m->proTxHash));
      90             : }
      91             : 
      92           0 : CBLSSecretKey CQuorum::GetSkShare() const
      93             : {
      94           0 :     return skShare;
      95             : }
      96             : 
      97           0 : int CQuorum::GetMemberIndex(const uint256& proTxHash) const
      98             : {
      99           0 :     for (size_t i = 0; i < members.size(); i++) {
     100           0 :         if (members[i]->proTxHash == proTxHash) {
     101           0 :             return (int)i;
     102             :         }
     103             :     }
     104             :     return -1;
     105             : }
     106             : 
     107           0 : void CQuorum::WriteContributions(CEvoDB& evoDb)
     108             : {
     109           0 :     uint256 dbKey = MakeQuorumKey(*this);
     110             : 
     111           0 :     if (quorumVvec != nullptr) {
     112           0 :         evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), *quorumVvec);
     113             :     }
     114           0 :     if (skShare.IsValid()) {
     115           0 :         evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
     116             :     }
     117           0 : }
     118             : 
     119           0 : bool CQuorum::ReadContributions(CEvoDB& evoDb)
     120             : {
     121           0 :     uint256 dbKey = MakeQuorumKey(*this);
     122             : 
     123           0 :     BLSVerificationVector qv;
     124           0 :     if (evoDb.Read(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), qv)) {
     125           0 :         quorumVvec = std::make_shared<BLSVerificationVector>(std::move(qv));
     126             :     } else {
     127             :         return false;
     128             :     }
     129             : 
     130             :     // We ignore the return value here as it is ok if this fails. If it fails, it usually means that we are not a
     131             :     // member of the quorum but observed the whole DKG process to have the quorum verification vector.
     132           0 :     evoDb.Read(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
     133             : 
     134           0 :     return true;
     135             : }
     136             : 
     137           0 : void CQuorum::StartCachePopulatorThread(std::shared_ptr<CQuorum> _this)
     138             : {
     139           0 :     if (_this->quorumVvec == nullptr) {
     140           0 :         return;
     141             :     }
     142             : 
     143           0 :     cxxtimer::Timer t(true);
     144           0 :     LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- start\n");
     145             : 
     146             :     // this thread will exit after some time
     147             :     // when then later some other thread tries to get keys, it will be much faster
     148           0 :     _this->cachePopulatorThread = std::thread(&TraceThread<std::function<void()> >, "quorum-cachepop", [_this, t] {
     149           0 :         for (size_t i = 0; i < _this->members.size() && !_this->stopCachePopulatorThread && !ShutdownRequested(); i++) {
     150           0 :             if (_this->qc.validMembers[i]) {
     151           0 :                 _this->GetPubKeyShare(i);
     152             :             }
     153             :         }
     154           0 :         LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- done. time=%d\n", t.count());
     155           0 :     });
     156             : }
     157             : 
     158         413 : CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) : evoDb(_evoDb),
     159             :                                                                                                           blsWorker(_blsWorker),
     160         413 :                                                                                                           dkgManager(_dkgManager)
     161             : {
     162         413 :     utils::InitQuorumsCache(mapQuorumsCache);
     163         413 :     utils::InitQuorumsCache(scanQuorumsCache);
     164         413 : }
     165             : 
     166       24761 : void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload)
     167             : {
     168       24761 :     if (!g_tiertwo_sync_state.IsBlockchainSynced() || !activeMasternodeManager) {
     169             :         return;
     170             :     }
     171             : 
     172          24 :     for (auto& p : Params().GetConsensus().llmqs) {
     173          12 :         const auto& params = Params().GetConsensus().llmqs.at(p.first);
     174             : 
     175          12 :         auto lastQuorums = ScanQuorums(p.first, pindexNew, (size_t)params.keepOldConnections);
     176             : 
     177          12 :         llmq::EnsureLatestQuorumConnections(p.first, pindexNew, activeMasternodeManager->GetProTx(), lastQuorums);
     178             :     }
     179             : }
     180             : 
     181             : 
     182           0 : bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, const uint256& minedBlockHash, std::shared_ptr<CQuorum>& quorum) const
     183             : {
     184           0 :     assert(pindexQuorum);
     185           0 :     assert(qc.quorumHash == pindexQuorum->GetBlockHash());
     186             : 
     187           0 :     auto members = deterministicMNManager->GetAllQuorumMembers((Consensus::LLMQType)qc.llmqType, pindexQuorum);
     188           0 :     quorum->Init(qc, pindexQuorum, minedBlockHash, members);
     189             : 
     190           0 :     bool hasValidVvec = false;
     191           0 :     if (quorum->ReadContributions(evoDb)) {
     192             :         hasValidVvec = true;
     193             :     } else {
     194           0 :         if (BuildQuorumContributions(qc, quorum)) {
     195           0 :             quorum->WriteContributions(evoDb);
     196             :             hasValidVvec = true;
     197             :         } else {
     198           0 :             LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- quorum.ReadContributions and BuildQuorumContributions for block %s failed\n", __func__, qc.quorumHash.ToString());
     199             :         }
     200             :     }
     201             : 
     202           0 :     if (hasValidVvec) {
     203             :         // pre-populate caches in the background
     204             :         // recovering public key shares is quite expensive and would result in serious lags for the first few signing
     205             :         // sessions if the shares would be calculated on-demand
     206           0 :         CQuorum::StartCachePopulatorThread(quorum);
     207             :     }
     208             : 
     209           0 :     return true;
     210             : }
     211             : 
     212           0 : bool CQuorumManager::BuildQuorumContributions(const CFinalCommitment& fqc, std::shared_ptr<CQuorum>& quorum) const
     213             : {
     214           0 :     std::vector<uint16_t> memberIndexes;
     215           0 :     std::vector<BLSVerificationVectorPtr> vvecs;
     216           0 :     BLSSecretKeyVector skContributions;
     217           0 :     if (!dkgManager.GetVerifiedContributions((Consensus::LLMQType)fqc.llmqType, quorum->pindexQuorum, fqc.validMembers, memberIndexes, vvecs, skContributions)) {
     218             :         return false;
     219             :     }
     220             : 
     221           0 :     BLSVerificationVectorPtr quorumVvec;
     222           0 :     CBLSSecretKey skShare;
     223             : 
     224           0 :     cxxtimer::Timer t2(true);
     225           0 :     quorumVvec = blsWorker.BuildQuorumVerificationVector(vvecs);
     226           0 :     if (quorumVvec == nullptr) {
     227           0 :         LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build quorumVvec\n", __func__);
     228             :         // without the quorum vvec, there can't be a skShare, so we fail here. Failure is not fatal here, as it still
     229             :         // allows to use the quorum as a non-member (verification through the quorum pub key)
     230           0 :         return false;
     231             :     }
     232           0 :     skShare = blsWorker.AggregateSecretKeys(skContributions);
     233           0 :     if (!skShare.IsValid()) {
     234           0 :         LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build skShare\n", __func__);
     235             :         // We don't bail out here as this is not a fatal error and still allows us to recover public key shares (as we
     236             :         // have a valid quorum vvec at this point)
     237             :     }
     238           0 :     t2.stop();
     239             : 
     240           0 :     LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- built quorum vvec and skShare. time=%d\n", __func__, t2.count());
     241             : 
     242           0 :     quorum->quorumVvec = quorumVvec;
     243           0 :     quorum->skShare = skShare;
     244             : 
     245           0 :     return true;
     246             : }
     247             : 
     248           0 : bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
     249             : {
     250           0 :     return quorumBlockProcessor->HasMinedCommitment(llmqType, quorumHash);
     251             : }
     252             : 
     253           0 : std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount)
     254             : {
     255           0 :     const CBlockIndex* pindex = WITH_LOCK(cs_main, return chainActive.Tip());
     256           0 :     return ScanQuorums(llmqType, pindex, maxCount);
     257             : }
     258             : 
     259          18 : std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount)
     260             : {
     261          18 :     if (pindexStart == nullptr || maxCount == 0) {
     262          18 :         return {};
     263             :     }
     264             : 
     265          18 :     bool fCacheExists{false};
     266          18 :     void* pIndexScanCommitments{(void*)pindexStart};
     267          18 :     size_t nScanCommitments{maxCount};
     268          18 :     std::vector<CQuorumCPtr> vecResultQuorums;
     269             : 
     270          18 :     {
     271          18 :         LOCK(quorumsCacheCs);
     272          18 :         auto& cache = scanQuorumsCache.at(llmqType);
     273          18 :         fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums);
     274          18 :         if (fCacheExists) {
     275             :             // We have exactly what requested so just return it
     276           0 :             if (vecResultQuorums.size() == maxCount) {
     277           0 :                 return vecResultQuorums;
     278             :             }
     279             :             // If we have more cached than requested return only a subvector
     280           0 :             if (vecResultQuorums.size() > maxCount) {
     281           0 :                 return {vecResultQuorums.begin(), vecResultQuorums.begin() + maxCount};
     282             :             }
     283             :             // If we have cached quorums but not enough, subtract what we have from the count and the set correct index where to start
     284             :             // scanning for the rests
     285           0 :             if (vecResultQuorums.size() > 0) {
     286           0 :                 nScanCommitments -= vecResultQuorums.size();
     287           0 :                 pIndexScanCommitments = (void*)vecResultQuorums.back()->pindexQuorum->pprev;
     288             :             }
     289             :         } else {
     290             :             // If there is nothing in cache request at least cache.max_size() because this gets cached then later
     291          24 :             nScanCommitments = std::max(maxCount, cache.max_size());
     292             :         }
     293             :     }
     294             :     // Get the block indexes of the mined commitments to build the required quorums from
     295          36 :     auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, (const CBlockIndex*)pIndexScanCommitments, nScanCommitments);
     296          18 :     vecResultQuorums.reserve(vecResultQuorums.size() + quorumIndexes.size());
     297             : 
     298          18 :     for (auto& quorumIndex : quorumIndexes) {
     299           0 :         assert(quorumIndex);
     300           0 :         auto quorum = GetQuorum(llmqType, quorumIndex);
     301           0 :         assert(quorum != nullptr);
     302           0 :         vecResultQuorums.emplace_back(quorum);
     303             :     }
     304             : 
     305          18 :     size_t nCountResult{vecResultQuorums.size()};
     306          18 :     if (nCountResult > 0 && !fCacheExists) {
     307           0 :         LOCK(quorumsCacheCs);
     308             :         // Don't cache more than cache.max_size() elements
     309           0 :         auto& cache = scanQuorumsCache.at(llmqType);
     310           0 :         size_t nCacheEndIndex = std::min(nCountResult, cache.max_size());
     311           0 :         cache.emplace(pindexStart->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex});
     312             :     }
     313             :     // Don't return more than nCountRequested elements
     314          18 :     size_t nResultEndIndex = std::min(nCountResult, maxCount);
     315          18 :     return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex};
     316             : }
     317             : 
     318           0 : CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
     319             : {
     320           0 :     CBlockIndex* pindexQuorum;
     321           0 :     {
     322           0 :         LOCK(cs_main);
     323           0 :         pindexQuorum = LookupBlockIndex(quorumHash);
     324             : 
     325           0 :         if (!pindexQuorum) {
     326           0 :             LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found.\n", __func__, quorumHash.ToString());
     327           0 :             return nullptr;
     328             :         }
     329             :     }
     330           0 :     return GetQuorum(llmqType, pindexQuorum);
     331             : }
     332             : 
     333           0 : CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum)
     334             : {
     335           0 :     assert(pindexQuorum);
     336             : 
     337           0 :     auto quorumHash = pindexQuorum->GetBlockHash();
     338             :     // we must check this before we look into the cache. Reorgs might have happened which would mean we might have
     339             :     // cached quorums which are not in the active chain anymore
     340           0 :     if (!HasQuorum(llmqType, quorumHash)) {
     341           0 :         return nullptr;
     342             :     }
     343             : 
     344           0 :     LOCK(quorumsCacheCs);
     345           0 :     CQuorumCPtr pQuorum;
     346           0 :     if (mapQuorumsCache.at(llmqType).get(quorumHash, pQuorum)) {
     347           0 :         return pQuorum;
     348             :     }
     349             : 
     350           0 :     CFinalCommitment qc;
     351           0 :     uint256 retMinedBlockHash;
     352           0 :     if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc, retMinedBlockHash)) {
     353           0 :         return nullptr;
     354             :     }
     355             : 
     356           0 :     auto& params = Params().GetConsensus().llmqs.at(llmqType);
     357             : 
     358           0 :     auto quorum = std::make_shared<CQuorum>(params, blsWorker);
     359           0 :     if (!BuildQuorumFromCommitment(qc, pindexQuorum, retMinedBlockHash, quorum)) {
     360           0 :         return nullptr;
     361             :     }
     362             : 
     363           0 :     mapQuorumsCache.at(llmqType).emplace(quorumHash, quorum);
     364             : 
     365           0 :     return quorum;
     366             : }
     367             : 
     368             : } // namespace llmq

Generated by: LCOV version 1.14