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

Generated by: LCOV version 1.14