LCOV - code coverage report
Current view: top level - src/llmq - quorums.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 166 175 94.9 %
Date: 2025-04-02 01:23:23 Functions: 22 22 100.0 %

          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         170 : static uint256 MakeQuorumKey(const CQuorum& q)
      34             : {
      35         170 :     CHashWriter hw(SER_NETWORK, 0);
      36         170 :     hw << (uint8_t)q.params.type;
      37         170 :     hw << q.qc.quorumHash;
      38         680 :     for (const auto& dmn : q.members) {
      39         510 :         hw << dmn->proTxHash;
      40             :     }
      41         170 :     return hw.GetHash();
      42             : }
      43             : 
      44         171 : CQuorum::~CQuorum()
      45             : {
      46             :     // most likely the thread is already done
      47         124 :     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         171 :     if (cachePopulatorThread.joinable() && cachePopulatorThread.get_id() != std::this_thread::get_id()) {
      51          47 :         cachePopulatorThread.join();
      52             :     }
      53         124 : }
      54             : 
      55         124 : void CQuorum::Init(const CFinalCommitment& _qc, const CBlockIndex* _pindexQuorum, const uint256& _minedBlockHash, const std::vector<CDeterministicMNCPtr>& _members)
      56             : {
      57         124 :     qc = _qc;
      58         124 :     pindexQuorum = _pindexQuorum;
      59         124 :     members = _members;
      60         124 :     minedBlockHash = _minedBlockHash;
      61         124 : }
      62             : 
      63        7023 : bool CQuorum::IsMember(const uint256& proTxHash) const
      64             : {
      65       20250 :     for (auto& dmn : members) {
      66       17160 :         if (dmn->proTxHash == proTxHash) {
      67        3933 :             return true;
      68             :         }
      69             :     }
      70        3090 :     return false;
      71             : }
      72             : 
      73        3305 : bool CQuorum::IsValidMember(const uint256& proTxHash) const
      74             : {
      75        8795 :     for (size_t i = 0; i < members.size(); i++) {
      76        7595 :         if (members[i]->proTxHash == proTxHash) {
      77        2105 :             return qc.validMembers[i];
      78             :         }
      79             :     }
      80             :     return false;
      81             : }
      82             : 
      83         720 : CBLSPublicKey CQuorum::GetPubKeyShare(size_t memberIdx) const
      84             : {
      85         720 :     if (quorumVvec == nullptr || memberIdx >= members.size() || !qc.validMembers[memberIdx]) {
      86           0 :         return CBLSPublicKey();
      87             :     }
      88         720 :     auto& m = members[memberIdx];
      89         720 :     return blsCache.BuildPubKeyShare(m->proTxHash, quorumVvec, CBLSId(m->proTxHash));
      90             : }
      91             : 
      92         968 : CBLSSecretKey CQuorum::GetSkShare() const
      93             : {
      94         968 :     return skShare;
      95             : }
      96             : 
      97         968 : int CQuorum::GetMemberIndex(const uint256& proTxHash) const
      98             : {
      99        1744 :     for (size_t i = 0; i < members.size(); i++) {
     100        1744 :         if (members[i]->proTxHash == proTxHash) {
     101         968 :             return (int)i;
     102             :         }
     103             :     }
     104             :     return -1;
     105             : }
     106             : 
     107          46 : void CQuorum::WriteContributions(CEvoDB& evoDb)
     108             : {
     109          46 :     uint256 dbKey = MakeQuorumKey(*this);
     110             : 
     111          46 :     if (quorumVvec != nullptr) {
     112          92 :         evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), *quorumVvec);
     113             :     }
     114          46 :     if (skShare.IsValid()) {
     115          92 :         evoDb.GetRawDB().Write(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
     116             :     }
     117          46 : }
     118             : 
     119         124 : bool CQuorum::ReadContributions(CEvoDB& evoDb)
     120             : {
     121         124 :     uint256 dbKey = MakeQuorumKey(*this);
     122             : 
     123         248 :     BLSVerificationVector qv;
     124         124 :     if (evoDb.Read(std::make_pair(DB_QUORUM_QUORUM_VVEC, dbKey), qv)) {
     125           1 :         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           1 :     evoDb.Read(std::make_pair(DB_QUORUM_SK_SHARE, dbKey), skShare);
     133             : 
     134           1 :     return true;
     135             : }
     136             : 
     137          47 : void CQuorum::StartCachePopulatorThread(std::shared_ptr<CQuorum> _this)
     138             : {
     139          47 :     if (_this->quorumVvec == nullptr) {
     140           0 :         return;
     141             :     }
     142             : 
     143          94 :     cxxtimer::Timer t(true);
     144          47 :     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         376 :     _this->cachePopulatorThread = std::thread(&TraceThread<std::function<void()> >, "quorum-cachepop", [_this, t] {
     149         188 :         for (size_t i = 0; i < _this->members.size() && !_this->stopCachePopulatorThread && !ShutdownRequested(); i++) {
     150         141 :             if (_this->qc.validMembers[i]) {
     151         127 :                 _this->GetPubKeyShare(i);
     152             :             }
     153             :         }
     154          47 :         LogPrint(BCLog::LLMQ, "CQuorum::StartCachePopulatorThread -- done. time=%d\n", t.count());
     155          47 :     });
     156             : }
     157             : 
     158         475 : CQuorumManager::CQuorumManager(CEvoDB& _evoDb, CBLSWorker& _blsWorker, CDKGSessionManager& _dkgManager) : evoDb(_evoDb),
     159             :                                                                                                           blsWorker(_blsWorker),
     160         475 :                                                                                                           dkgManager(_dkgManager)
     161             : {
     162         475 :     utils::InitQuorumsCache(mapQuorumsCache);
     163         475 :     utils::InitQuorumsCache(scanQuorumsCache);
     164         475 : }
     165             : 
     166       41174 : void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload)
     167             : {
     168       41174 :     if (!g_tiertwo_sync_state.IsBlockchainSynced() || !activeMasternodeManager) {
     169             :         return;
     170             :     }
     171             : 
     172        8592 :     for (auto& p : Params().GetConsensus().llmqs) {
     173        4296 :         const auto& params = Params().GetConsensus().llmqs.at(p.first);
     174             : 
     175        4296 :         auto lastQuorums = ScanQuorums(p.first, pindexNew, (size_t)params.keepOldConnections);
     176             : 
     177        4296 :         llmq::EnsureLatestQuorumConnections(p.first, pindexNew, activeMasternodeManager->GetProTx(), lastQuorums);
     178             :     }
     179             : }
     180             : 
     181             : 
     182         124 : bool CQuorumManager::BuildQuorumFromCommitment(const CFinalCommitment& qc, const CBlockIndex* pindexQuorum, const uint256& minedBlockHash, std::shared_ptr<CQuorum>& quorum) const
     183             : {
     184         124 :     assert(pindexQuorum);
     185         124 :     assert(qc.quorumHash == pindexQuorum->GetBlockHash());
     186             : 
     187         124 :     auto members = deterministicMNManager->GetAllQuorumMembers((Consensus::LLMQType)qc.llmqType, pindexQuorum);
     188         124 :     quorum->Init(qc, pindexQuorum, minedBlockHash, members);
     189             : 
     190         124 :     bool hasValidVvec = false;
     191         124 :     if (quorum->ReadContributions(evoDb)) {
     192             :         hasValidVvec = true;
     193             :     } else {
     194         123 :         if (BuildQuorumContributions(qc, quorum)) {
     195          46 :             quorum->WriteContributions(evoDb);
     196             :             hasValidVvec = true;
     197             :         } else {
     198          77 :             LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- quorum.ReadContributions and BuildQuorumContributions for block %s failed\n", __func__, qc.quorumHash.ToString());
     199             :         }
     200             :     }
     201             : 
     202          77 :     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          94 :         CQuorum::StartCachePopulatorThread(quorum);
     207             :     }
     208             : 
     209         124 :     return true;
     210             : }
     211             : 
     212         123 : bool CQuorumManager::BuildQuorumContributions(const CFinalCommitment& fqc, std::shared_ptr<CQuorum>& quorum) const
     213             : {
     214         246 :     std::vector<uint16_t> memberIndexes;
     215         123 :     std::vector<BLSVerificationVectorPtr> vvecs;
     216           0 :     BLSSecretKeyVector skContributions;
     217         123 :     if (!dkgManager.GetVerifiedContributions((Consensus::LLMQType)fqc.llmqType, quorum->pindexQuorum, fqc.validMembers, memberIndexes, vvecs, skContributions)) {
     218             :         return false;
     219             :     }
     220             : 
     221         123 :     BLSVerificationVectorPtr quorumVvec;
     222         246 :     CBLSSecretKey skShare;
     223             : 
     224         246 :     cxxtimer::Timer t2(true);
     225         246 :     quorumVvec = blsWorker.BuildQuorumVerificationVector(vvecs);
     226         123 :     if (quorumVvec == nullptr) {
     227          77 :         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          77 :         return false;
     231             :     }
     232          92 :     skShare = blsWorker.AggregateSecretKeys(skContributions);
     233          46 :     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          46 :     t2.stop();
     239             : 
     240          46 :     LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- built quorum vvec and skShare. time=%d\n", __func__, t2.count());
     241             : 
     242          46 :     quorum->quorumVvec = quorumVvec;
     243          46 :     quorum->skShare = skShare;
     244             : 
     245          46 :     return true;
     246             : }
     247             : 
     248       15960 : bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
     249             : {
     250       15960 :     return quorumBlockProcessor->HasMinedCommitment(llmqType, quorumHash);
     251             : }
     252             : 
     253        5453 : std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t maxCount)
     254             : {
     255       16359 :     const CBlockIndex* pindex = WITH_LOCK(cs_main, return chainActive.Tip());
     256        5453 :     return ScanQuorums(llmqType, pindex, maxCount);
     257             : }
     258             : 
     259       13762 : std::vector<CQuorumCPtr> CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, const CBlockIndex* pindexStart, size_t maxCount)
     260             : {
     261       13762 :     if (pindexStart == nullptr || maxCount == 0) {
     262       13762 :         return {};
     263             :     }
     264             : 
     265       13762 :     bool fCacheExists{false};
     266       13762 :     void* pIndexScanCommitments{(void*)pindexStart};
     267       13762 :     size_t nScanCommitments{maxCount};
     268       13762 :     std::vector<CQuorumCPtr> vecResultQuorums;
     269             : 
     270       13762 :     {
     271       13762 :         LOCK(quorumsCacheCs);
     272       13762 :         auto& cache = scanQuorumsCache.at(llmqType);
     273       13762 :         fCacheExists = cache.get(pindexStart->GetBlockHash(), vecResultQuorums);
     274       13762 :         if (fCacheExists) {
     275             :             // We have exactly what requested so just return it
     276        6010 :             if (vecResultQuorums.size() == maxCount) {
     277        7140 :                 return vecResultQuorums;
     278             :             }
     279             :             // If we have more cached than requested return only a subvector
     280        2683 :             if (vecResultQuorums.size() > maxCount) {
     281         243 :                 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        2440 :             if (vecResultQuorums.size() > 0) {
     286        2440 :                 nScanCommitments -= vecResultQuorums.size();
     287        2440 :                 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       11196 :             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       23954 :     auto quorumIndexes = quorumBlockProcessor->GetMinedCommitmentsUntilBlock(llmqType, (const CBlockIndex*)pIndexScanCommitments, nScanCommitments);
     296       10192 :     vecResultQuorums.reserve(vecResultQuorums.size() + quorumIndexes.size());
     297             : 
     298       21995 :     for (auto& quorumIndex : quorumIndexes) {
     299       11803 :         assert(quorumIndex);
     300       23606 :         auto quorum = GetQuorum(llmqType, quorumIndex);
     301       11803 :         assert(quorum != nullptr);
     302       11803 :         vecResultQuorums.emplace_back(quorum);
     303             :     }
     304             : 
     305       10192 :     size_t nCountResult{vecResultQuorums.size()};
     306       10192 :     if (nCountResult > 0 && !fCacheExists) {
     307       10584 :         LOCK(quorumsCacheCs);
     308             :         // Don't cache more than cache.max_size() elements
     309        5292 :         auto& cache = scanQuorumsCache.at(llmqType);
     310        5292 :         size_t nCacheEndIndex = std::min(nCountResult, cache.max_size());
     311       10584 :         cache.emplace(pindexStart->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex});
     312             :     }
     313             :     // Don't return more than nCountRequested elements
     314       10192 :     size_t nResultEndIndex = std::min(nCountResult, maxCount);
     315       10192 :     return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex};
     316             : }
     317             : 
     318        4157 : CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash)
     319             : {
     320        4157 :     CBlockIndex* pindexQuorum;
     321        4157 :     {
     322        4157 :         LOCK(cs_main);
     323        4157 :         pindexQuorum = LookupBlockIndex(quorumHash);
     324             : 
     325        4157 :         if (!pindexQuorum) {
     326           0 :             LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found.\n", __func__, quorumHash.ToString());
     327           0 :             return nullptr;
     328             :         }
     329             :     }
     330        4157 :     return GetQuorum(llmqType, pindexQuorum);
     331             : }
     332             : 
     333       15960 : CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const CBlockIndex* pindexQuorum)
     334             : {
     335       15960 :     assert(pindexQuorum);
     336             : 
     337       15960 :     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       15960 :     if (!HasQuorum(llmqType, quorumHash)) {
     341           0 :         return nullptr;
     342             :     }
     343             : 
     344       31920 :     LOCK(quorumsCacheCs);
     345       15960 :     CQuorumCPtr pQuorum;
     346       15960 :     if (mapQuorumsCache.at(llmqType).get(quorumHash, pQuorum)) {
     347       15836 :         return pQuorum;
     348             :     }
     349             : 
     350       16084 :     CFinalCommitment qc;
     351         124 :     uint256 retMinedBlockHash;
     352         124 :     if (!quorumBlockProcessor->GetMinedCommitment(llmqType, quorumHash, qc, retMinedBlockHash)) {
     353           0 :         return nullptr;
     354             :     }
     355             : 
     356         124 :     auto& params = Params().GetConsensus().llmqs.at(llmqType);
     357             : 
     358         248 :     auto quorum = std::make_shared<CQuorum>(params, blsWorker);
     359         124 :     if (!BuildQuorumFromCommitment(qc, pindexQuorum, retMinedBlockHash, quorum)) {
     360           0 :         return nullptr;
     361             :     }
     362             : 
     363         248 :     mapQuorumsCache.at(llmqType).emplace(quorumHash, quorum);
     364             : 
     365         124 :     return quorum;
     366             : }
     367             : 
     368             : } // namespace llmq

Generated by: LCOV version 1.14