LCOV - code coverage report
Current view: top level - src/llmq - blockprocessor.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 373 436 85.6 %
Date: 2026-06-25 07:23:43 Functions: 29 29 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2018-2025 The Dash Core developers
       2             : // Distributed under the MIT/X11 software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <llmq/blockprocessor.h>
       6             : 
       7             : #include <evo/evodb.h>
       8             : #include <evo/specialtx.h>
       9             : #include <llmq/commitment.h>
      10             : #include <llmq/options.h>
      11             : #include <llmq/utils.h>
      12             : #include <util/helpers.h>
      13             : #include <util/std23.h>
      14             : 
      15             : #include <chain.h>
      16             : #include <chainparams.h>
      17             : #include <checkqueue.h>
      18             : #include <consensus/params.h>
      19             : #include <consensus/validation.h>
      20             : #include <deploymentstatus.h>
      21             : #include <net.h>
      22             : #include <primitives/block.h>
      23             : #include <primitives/transaction.h>
      24             : #include <saltedhasher.h>
      25             : #include <sync.h>
      26             : #include <validation.h>
      27             : 
      28             : #include <map>
      29             : 
      30      194079 : static void PreComputeQuorumMembers(CDeterministicMNManager& dmnman, llmq::CQuorumSnapshotManager& qsnapman,
      31             :                                     const ChainstateManager& chainman, const CBlockIndex* pindex, bool reset_cache)
      32             : {
      33      826478 :     for (const Consensus::LLMQParams& params : llmq::GetEnabledQuorumParams(chainman, pindex->pprev)) {
      34      632399 :         if (llmq::IsQuorumRotationEnabled(params, pindex) && (pindex->nHeight % params.dkgInterval == 0)) {
      35        7236 :             llmq::utils::GetAllQuorumMembers(params.type, {dmnman, qsnapman, chainman, pindex}, reset_cache);
      36        7236 :         }
      37             :     }
      38      194079 : }
      39             : 
      40             : namespace llmq
      41             : {
      42             : static const std::string DB_MINED_COMMITMENT = "q_mc";
      43             : static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT = "q_mcih";
      44             : static const std::string DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED = "q_mcihi";
      45             : 
      46             : static const std::string DB_BEST_BLOCK_UPGRADE = "q_bbu2";
      47             : 
      48        6126 : CQuorumBlockProcessor::CQuorumBlockProcessor(CChainState& chainstate, CDeterministicMNManager& dmnman, CEvoDB& evoDb,
      49             :                                              CQuorumSnapshotManager& qsnapman, int8_t bls_threads) :
      50        3063 :     m_chainstate{chainstate},
      51        3063 :     m_dmnman{dmnman},
      52        3063 :     m_evoDb{evoDb},
      53        3063 :     m_qsnapman{qsnapman}
      54        3063 : {
      55             :     utils::InitQuorumsCache(mapHasMinedCommitmentCache, m_chainstate.m_chainman.GetConsensus());
      56             :     LogPrintf("BLS verification uses %d additional threads\n", bls_threads);
      57             :     m_bls_queue.StartWorkerThreads(bls_threads);
      58        3063 : }
      59             : 
      60        6126 : CQuorumBlockProcessor::~CQuorumBlockProcessor()
      61        3063 : {
      62        3063 :     m_bls_queue.StopWorkerThreads();
      63        6126 : }
      64             : 
      65      105757 : MessageProcessingResult CQuorumBlockProcessor::ProcessMessage(const CNode& peer, std::string_view msg_type,
      66             :                                                               CDataStream& vRecv)
      67             : {
      68      105757 :     if (msg_type != NetMsgType::QFCOMMITMENT) {
      69      103190 :         return {};
      70             :     }
      71             : 
      72        2567 :     CFinalCommitment qc;
      73        2567 :     vRecv >> qc;
      74             : 
      75        2567 :     MessageProcessingResult ret;
      76        2567 :     ret.m_to_erase = CInv{MSG_QUORUM_FINAL_COMMITMENT, ::SerializeHash(qc)};
      77             : 
      78        2567 :     if (qc.IsNull()) {
      79           0 :         LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- null commitment from peer=%d\n", __func__, peer.GetId());
      80           0 :         ret.m_error = MisbehavingError{100};
      81           0 :         return ret;
      82             :     }
      83             : 
      84        2567 :     const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
      85        2567 :     if (!llmq_params_opt.has_value()) {
      86           0 :         LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- invalid commitment type %d from peer=%d\n", __func__,
      87             :                  std23::to_underlying(qc.llmqType), peer.GetId());
      88           0 :         ret.m_error = MisbehavingError{100};
      89           0 :         return ret;
      90             :     }
      91        2567 :     auto type = qc.llmqType;
      92             : 
      93             :     // Verify that quorumHash is part of the active chain and that it's the first block in the DKG interval
      94             :     const CBlockIndex* pQuorumBaseBlockIndex;
      95             :     {
      96        2567 :         LOCK(::cs_main);
      97        2567 :         pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash);
      98        2567 :         if (pQuorumBaseBlockIndex == nullptr) {
      99           0 :             LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- unknown block %s in commitment, peer=%d\n", __func__,
     100             :                      qc.quorumHash.ToString(), peer.GetId());
     101             :             // can't really punish the node here, as we might simply be the one that is on the wrong chain or not
     102             :             // fully synced
     103           0 :             return ret;
     104             :         }
     105        2567 :         if (m_chainstate.m_chain.Tip()->GetAncestor(pQuorumBaseBlockIndex->nHeight) != pQuorumBaseBlockIndex) {
     106           0 :             LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- block %s not in active chain, peer=%d\n", __func__,
     107             :                      qc.quorumHash.ToString(), peer.GetId());
     108             :             // same, can't punish
     109           0 :             return ret;
     110             :         }
     111        5134 :         if (int quorumHeight = pQuorumBaseBlockIndex->nHeight - (pQuorumBaseBlockIndex->nHeight % llmq_params_opt->dkgInterval) + int(qc.quorumIndex);
     112        2567 :                 quorumHeight != pQuorumBaseBlockIndex->nHeight) {
     113           0 :             LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- block %s is not the first block in the DKG interval, peer=%d\n", __func__,
     114             :                      qc.quorumHash.ToString(), peer.GetId());
     115           0 :             ret.m_error = MisbehavingError{100};
     116           0 :             return ret;
     117             :         }
     118        2567 :         if (pQuorumBaseBlockIndex->nHeight < (m_chainstate.m_chain.Height() - llmq_params_opt->dkgInterval)) {
     119           0 :             LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- block %s is too old, peer=%d\n", __func__,
     120             :                      qc.quorumHash.ToString(), peer.GetId());
     121           0 :             if (peer.GetCommonVersion() >= QFCOMMIT_STALE_REPROP_BAN_VERSION) {
     122           0 :                 ret.m_error = MisbehavingError{100};
     123           0 :             }
     124           0 :             return ret;
     125             :         }
     126        2567 :         if (HasMinedCommitment(type, qc.quorumHash)) {
     127           0 :             LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- commitment for quorum hash[%s], type[%d], quorumIndex[%d] is already mined, peer=%d\n",
     128             :                      __func__, qc.quorumHash.ToString(), std23::to_underlying(type), qc.quorumIndex, peer.GetId());
     129             :             // NOTE: do not punish here
     130           0 :             return ret;
     131             :         }
     132        2567 :     }
     133             : 
     134             :     {
     135             :         // Check if we already got a better one locally
     136             :         // We do this before verifying the commitment to avoid DoS
     137        2567 :         LOCK(minableCommitmentsCs);
     138        2567 :         auto k = std::make_pair(type, qc.quorumHash);
     139        2567 :         auto it = minableCommitmentsByQuorum.find(k);
     140        2567 :         if (it != minableCommitmentsByQuorum.end()) {
     141          76 :             auto jt = minableCommitments.find(it->second);
     142          76 :             if (jt != minableCommitments.end() && jt->second.CountSigners() <= qc.CountSigners()) {
     143          62 :                 return ret;
     144             :             }
     145          14 :         }
     146        2567 :     }
     147             : 
     148        2505 :     if (!qc.Verify({m_dmnman, m_qsnapman, m_chainstate.m_chainman, pQuorumBaseBlockIndex}, /*checkSigs=*/true)) {
     149           0 :         LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- commitment for quorum %s:%d is not valid quorumIndex[%d] nversion[%d], peer=%d\n",
     150             :                  __func__, qc.quorumHash.ToString(),
     151             :                  std23::to_underlying(qc.llmqType), qc.quorumIndex, qc.nVersion, peer.GetId());
     152           0 :         ret.m_error = MisbehavingError{100};
     153           0 :         return ret;
     154             :     }
     155             : 
     156        2505 :     LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s -- received commitment for quorum %s:%d, validMembers=%d, signers=%d, peer=%d\n", __func__,
     157             :              qc.quorumHash.ToString(), std23::to_underlying(qc.llmqType), qc.CountValidMembers(), qc.CountSigners(), peer.GetId());
     158             : 
     159        2505 :     if (auto inv_opt = AddMineableCommitment(qc)) {
     160        2451 :         ret.m_inventory.emplace_back(inv_opt.value());
     161        2451 :     }
     162        2505 :     return ret;
     163      105757 : }
     164             : 
     165      341228 : bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, BlockValidationState& state, bool fJustCheck, bool fBLSChecks)
     166             : {
     167      341228 :     AssertLockHeld(::cs_main);
     168             : 
     169      341228 :     const auto blockHash = pindex->GetBlockHash();
     170             : 
     171      341228 :     if (!DeploymentActiveAt(*pindex, m_chainstate.m_chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
     172      173715 :         m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, blockHash);
     173      173715 :         return true;
     174             :     }
     175             : 
     176      167513 :     PreComputeQuorumMembers(m_dmnman, m_qsnapman, m_chainstate.m_chainman, pindex, /*reset_cache=*/false);
     177             : 
     178      167513 :     std::multimap<Consensus::LLMQType, CFinalCommitment> qcs;
     179      167513 :     if (!GetCommitmentsFromBlock(block, pindex, qcs, state)) {
     180           0 :         return false;
     181             :     }
     182             : 
     183             :     // The following checks make sure that there is always a (possibly null) commitment while in the mining phase
     184             :     // until the first non-null commitment has been mined. After the non-null commitment, no other commitments are
     185             :     // allowed, including null commitments.
     186             :     // Note: must only check quorums that were enabled at the _previous_ block height to match mining logic
     187      717869 :     for (const Consensus::LLMQParams& params : GetEnabledQuorumParams(m_chainstate.m_chainman, pindex->pprev)) {
     188             :         // skip these checks when replaying blocks after the crash
     189      550356 :         if (m_chainstate.m_chain.Tip() == nullptr) {
     190           0 :             break;
     191             :         }
     192             : 
     193      550356 :         const size_t numCommitmentsRequired = GetNumCommitmentsRequired(params, pindex->nHeight);
     194      550356 :         const auto numCommitmentsInNewBlock = qcs.count(params.type);
     195             : 
     196      550356 :         if (numCommitmentsRequired < numCommitmentsInNewBlock) {
     197           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-not-allowed");
     198             :         }
     199             : 
     200      550356 :         if (numCommitmentsRequired > numCommitmentsInNewBlock) {
     201           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-missing");
     202             :         }
     203      550356 :         if (IsQuorumRotationEnabled(params, pindex)) {
     204      155043 :             LogPrintf("[ProcessBlock] h[%d] numCommitmentsRequired[%d] numCommitmentsInNewBlock[%d]\n", pindex->nHeight, numCommitmentsRequired, numCommitmentsInNewBlock);
     205      155043 :         }
     206             :     }
     207             : 
     208      167513 :     if (fBLSChecks) {
     209      167513 :         CCheckQueueControl<utils::BlsCheck> queue_control(&m_bls_queue);
     210      410748 :         for (const auto& [_, qc] : qcs) {
     211      233393 :             if (qc.IsNull()) continue;
     212        9842 :             const auto* pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash);
     213        4921 :             if (pQuorumBaseBlockIndex == nullptr) {
     214           0 :                 LogPrint(BCLog::LLMQ, "[ProcessBlock] h[%d] unexpectedly failed due to no known pindex for hash[%s]\n",
     215             :                          pindex->nHeight, qc.quorumHash.ToString());
     216           0 :                 return false;
     217             :             }
     218        4921 :             qc.VerifySignatureAsync({m_dmnman, m_qsnapman, m_chainstate.m_chainman, pQuorumBaseBlockIndex}, &queue_control);
     219             :         }
     220             : 
     221      167513 :         if (!queue_control.Wait()) {
     222             :             // at least one check failed
     223           4 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid");
     224             :         }
     225      167513 :     }
     226      400894 :     for (const auto& [_, qc] : qcs) {
     227      233385 :         if (!ProcessCommitment(pindex->nHeight, blockHash, qc, state, fJustCheck)) {
     228           0 :             LogPrintf("[ProcessBlock] failed h[%d] llmqType[%d] version[%d] quorumIndex[%d] quorumHash[%s]\n", pindex->nHeight, std23::to_underlying(qc.llmqType), qc.nVersion, qc.quorumIndex, qc.quorumHash.ToString());
     229           0 :             return false;
     230             :         }
     231             :     }
     232             : 
     233      167509 :     m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, blockHash);
     234             : 
     235      167509 :     return true;
     236      341228 : }
     237             : 
     238             : // We store a mapping from minedHeight->quorumHeight in the DB
     239             : // minedHeight is inversed so that entries are traversable in reversed order
     240     2397967 : static std::tuple<std::string, Consensus::LLMQType, uint32_t> BuildInversedHeightKey(Consensus::LLMQType llmqType, int nMinedHeight)
     241             : {
     242             :     // nMinedHeight must be converted to big endian to make it comparable when serialized
     243     2397967 :     return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, llmqType, htobe32_internal(std::numeric_limits<uint32_t>::max() - nMinedHeight));
     244             : }
     245             : 
     246     1424209 : static std::tuple<std::string, Consensus::LLMQType, int, uint32_t> BuildInversedHeightKeyIndexed(Consensus::LLMQType llmqType, int nMinedHeight, int quorumIndex)
     247             : {
     248             :     // nMinedHeight must be converted to big endian to make it comparable when serialized
     249     1424209 :     return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED, llmqType, quorumIndex, htobe32_internal(std::numeric_limits<uint32_t>::max() - nMinedHeight));
     250             : }
     251             : 
     252      728981 : static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight)
     253             :     EXCLUSIVE_LOCKS_REQUIRED(cs_main)
     254             : {
     255      728981 :     AssertLockHeld(cs_main);
     256             : 
     257             :     // Note: This function can be called for new blocks
     258      728981 :     assert(nHeight <= active_chain.Height() + 1);
     259             : 
     260      728981 :     int quorumCycleStartHeight = nHeight - (nHeight % llmqParams.dkgInterval);
     261      728981 :     int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowStart;
     262      728981 :     int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowEnd;
     263             : 
     264      728981 :     return nHeight >= quorumCycleMiningStartHeight && nHeight <= quorumCycleMiningEndHeight;
     265             : }
     266             : 
     267      233385 : bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc,
     268             :                                               BlockValidationState& state, bool fJustCheck)
     269             : {
     270      233385 :     AssertLockHeld(::cs_main);
     271             : 
     272      233385 :     const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
     273      233385 :     if (!llmq_params_opt.has_value()) {
     274           0 :         LogPrint(BCLog::LLMQ, "%s -- invalid commitment type %d\n", __func__, std23::to_underlying(qc.llmqType));
     275           0 :         return false;
     276             :     }
     277      233385 :     const auto& llmq_params = llmq_params_opt.value();
     278             : 
     279      233385 :     uint256 quorumHash = GetQuorumBlockHash(llmq_params, m_chainstate.m_chain, nHeight, qc.quorumIndex);
     280             : 
     281      233385 :     LogPrint(BCLog::LLMQ, /* Continued */
     282             :              "%s -- processing commitment for block height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, "
     283             :              "validMembers=%d, quorumPublicKey=%s "
     284             :              "fJustCheck[%d] processing commitment from block.\n",
     285             :              __func__, nHeight, std23::to_underlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(),
     286             :              qc.CountValidMembers(), qc.quorumPublicKey.ToString(), fJustCheck);
     287             : 
     288             :     // skip `bad-qc-block` checks below when replaying blocks after the crash
     289      233385 :     if (m_chainstate.m_chain.Tip() == nullptr) {
     290           0 :         quorumHash = qc.quorumHash;
     291           0 :     }
     292             : 
     293      233385 :     if (quorumHash.IsNull()) {
     294           0 :         LogPrint(BCLog::LLMQ, /* Continued */
     295             :                  "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, "
     296             :                  "quorumPublicKey=%s quorumHash is null.\n",
     297             :                  __func__, nHeight, std23::to_underlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(),
     298             :                  qc.CountValidMembers(), qc.quorumPublicKey.ToString());
     299           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-block");
     300             :     }
     301      233385 :     if (quorumHash != qc.quorumHash) {
     302           0 :         LogPrint(BCLog::LLMQ, /* Continued */
     303             :                  "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, qc.quorumHash=%s signers=%s, "
     304             :                  "validMembers=%d, quorumPublicKey=%s non equal quorumHash.\n",
     305             :                  __func__, nHeight, std23::to_underlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(),
     306             :                  qc.quorumHash.ToString(), qc.CountSigners(), qc.CountValidMembers(), qc.quorumPublicKey.ToString());
     307           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-block");
     308             :     }
     309             : 
     310      233385 :     if (qc.IsNull()) {
     311      228468 :         if (!qc.VerifyNull()) {
     312           0 :             LogPrint(BCLog::LLMQ, /* Continued */
     313             :                      "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%dqc "
     314             :                      "verifynull failed.\n",
     315             :                      __func__, nHeight, std23::to_underlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(),
     316             :                      qc.CountSigners(), qc.CountValidMembers());
     317           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid-null");
     318             :         }
     319      228468 :         return true;
     320             :     }
     321             : 
     322        4917 :     if (HasMinedCommitment(llmq_params.type, quorumHash)) {
     323             :         // should not happen as it's already handled in ProcessBlock
     324           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-dup");
     325             :     }
     326             : 
     327        4917 :     if (!IsMiningPhase(llmq_params, m_chainstate.m_chain, nHeight)) {
     328             :         // should not happen as it's already handled in ProcessBlock
     329           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-height");
     330             :     }
     331             : 
     332        4917 :     const auto* pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash);
     333        4917 :     if (pQuorumBaseBlockIndex == nullptr) {
     334           0 :         LogPrint(BCLog::LLMQ, "%s -- unexpectedly failed due to no known pindex for hash[%s]\n", __func__,
     335             :                  qc.quorumHash.ToString());
     336           0 :         return false;
     337             :     }
     338             : 
     339             :     // we don't validate signatures here; they already validated on previous step
     340        4917 :     if (!qc.Verify({m_dmnman, m_qsnapman, m_chainstate.m_chainman, pQuorumBaseBlockIndex}, /*checksigs=*/false)) {
     341           0 :         LogPrint(BCLog::LLMQ, /* Continued */
     342             :                  "%s -- height=%d, type=%d, quorumIndex=%d, quorumHash=%s, signers=%s, validMembers=%d, "
     343             :                  "quorumPublicKey=%s qc verify failed.\n",
     344             :                  __func__, nHeight, std23::to_underlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString(), qc.CountSigners(),
     345             :                  qc.CountValidMembers(), qc.quorumPublicKey.ToString());
     346           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid");
     347             :     }
     348             : 
     349        4917 :     if (fJustCheck) {
     350         934 :         return true;
     351             :     }
     352             : 
     353        3983 :     bool rotation_enabled = IsQuorumRotationEnabled(llmq_params, pQuorumBaseBlockIndex);
     354             : 
     355        3983 :     if (rotation_enabled) {
     356        1573 :         LogPrint(BCLog::LLMQ, "%s -- height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n",
     357             :                  __func__, nHeight, pQuorumBaseBlockIndex->nHeight, qc.quorumIndex, qc.nVersion);
     358        1573 :     }
     359             : 
     360             :     // Store commitment in DB
     361        3983 :     auto cacheKey = std::make_pair(llmq_params.type, quorumHash);
     362        3983 :     m_evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, cacheKey), std::make_pair(qc, blockHash));
     363             : 
     364        3983 :     if (rotation_enabled) {
     365        1573 :         m_evoDb.Write(BuildInversedHeightKeyIndexed(llmq_params.type, nHeight, int(qc.quorumIndex)), pQuorumBaseBlockIndex->nHeight);
     366        1573 :     } else {
     367        2410 :         m_evoDb.Write(BuildInversedHeightKey(llmq_params.type, nHeight), pQuorumBaseBlockIndex->nHeight);
     368             :     }
     369             : 
     370             :     {
     371        3983 :         LOCK(minableCommitmentsCs);
     372        3983 :         mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
     373        3983 :         minableCommitmentsByQuorum.erase(cacheKey);
     374        3983 :         minableCommitments.erase(::SerializeHash(qc));
     375        3983 :     }
     376             : 
     377        3983 :     LogPrint(BCLog::LLMQ, "%s -- processed commitment from block. type=%d, quorumIndex=%d, quorumHash=%s\n", __func__,
     378             :              std23::to_underlying(qc.llmqType), qc.quorumIndex, quorumHash.ToString());
     379             : 
     380        3983 :     return true;
     381      233385 : }
     382             : 
     383       26566 : bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex)
     384             : {
     385       26566 :     AssertLockHeld(::cs_main);
     386             : 
     387       26566 :     PreComputeQuorumMembers(m_dmnman, m_qsnapman, m_chainstate.m_chainman, pindex, /*reset_cache=*/true);
     388             : 
     389       26566 :     std::multimap<Consensus::LLMQType, CFinalCommitment> qcs;
     390       26566 :     if (BlockValidationState dummy; !GetCommitmentsFromBlock(block, pindex, qcs, dummy)) {
     391           0 :         return false;
     392             :     }
     393             : 
     394       37292 :     for (auto& [_, qc2] : qcs) {
     395       10726 :         auto& qc = qc2; // cannot capture structured binding into lambda
     396       10726 :         if (qc.IsNull()) {
     397       10635 :             continue;
     398             :         }
     399             : 
     400          91 :         m_evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash)));
     401             : 
     402          91 :         const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
     403          91 :         assert(llmq_params_opt.has_value());
     404             : 
     405          91 :         if (IsQuorumRotationEnabled(llmq_params_opt.value(), pindex)) {
     406          16 :             m_evoDb.Erase(BuildInversedHeightKeyIndexed(qc.llmqType, pindex->nHeight, int(qc.quorumIndex)));
     407          16 :         } else {
     408          75 :             m_evoDb.Erase(BuildInversedHeightKey(qc.llmqType, pindex->nHeight));
     409             :         }
     410             : 
     411         182 :         WITH_LOCK(minableCommitmentsCs, mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash));
     412             : 
     413             :         // if a reorg happened, we should allow to mine this commitment later
     414          91 :         AddMineableCommitment(qc);
     415             :     }
     416             : 
     417       26566 :     m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, pindex->pprev->GetBlockHash());
     418             : 
     419       26566 :     return true;
     420       26566 : }
     421             : 
     422      194079 : bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, std::multimap<Consensus::LLMQType, CFinalCommitment>& ret, BlockValidationState& state)
     423             : {
     424      194079 :     AssertLockHeld(::cs_main);
     425             : 
     426      194079 :     const auto& consensus = Params().GetConsensus();
     427             : 
     428      194079 :     ret.clear();
     429             : 
     430      673680 :     for (const auto& tx : block.vtx) {
     431      479601 :         if (tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
     432      244119 :             auto opt_qc = GetTxPayload<CFinalCommitmentTxPayload>(*tx);
     433      244119 :             if (!opt_qc) {
     434             :                 // should not happen as it was verified before processing the block
     435           0 :                 LogPrint(BCLog::LLMQ, "CQuorumBlockProcessor::%s height=%d GetTxPayload fails\n", __func__, pindex->nHeight);
     436           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-payload");
     437             :             }
     438      244119 :             auto& qc = *opt_qc;
     439             : 
     440      244119 :             const auto& llmq_params_opt = Params().GetLLMQ(qc.commitment.llmqType);
     441      244119 :             if (!llmq_params_opt.has_value()) {
     442             :                 // should not happen as it was verified before processing the block
     443           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-commitment-type");
     444             :             }
     445             : 
     446             :             // only allow one commitment per type and per block (This was changed with rotation)
     447      244119 :             if (!IsQuorumRotationEnabled(llmq_params_opt.value(), pindex)) {
     448      136916 :                 if (ret.count(qc.commitment.llmqType) != 0) {
     449           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-dup");
     450             :                 }
     451      136916 :             }
     452             : 
     453      244119 :             ret.emplace(qc.commitment.llmqType, std::move(qc.commitment));
     454      244119 :         }
     455             :     }
     456             : 
     457      194079 :     if (!DeploymentActiveAt(*pindex, consensus, Consensus::DEPLOYMENT_DIP0003) && !ret.empty()) {
     458           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-premature");
     459             :     }
     460             : 
     461      194079 :     return true;
     462      194079 : }
     463             : 
     464      724064 : size_t CQuorumBlockProcessor::GetNumCommitmentsRequired(const Consensus::LLMQParams& llmqParams, int nHeight) const
     465             : {
     466      724064 :     AssertLockHeld(::cs_main);
     467             : 
     468      724064 :     if (!IsMiningPhase(llmqParams, m_chainstate.m_chain, nHeight)) return 0;
     469             : 
     470             :     // Note: This function can be called for new blocks
     471      261603 :     assert(nHeight <= m_chainstate.m_chain.Height() + 1);
     472      261603 :     const auto *const pindex = m_chainstate.m_chain.Height() < nHeight ? m_chainstate.m_chain.Tip() : m_chainstate.m_chain.Tip()->GetAncestor(nHeight);
     473             : 
     474      261603 :     bool rotation_enabled = IsQuorumRotationEnabled(llmqParams, pindex);
     475      261603 :     size_t quorums_num = rotation_enabled ? llmqParams.signingActiveQuorumCount : 1;
     476      261603 :     size_t ret{0};
     477             : 
     478      594228 :     for (const auto quorumIndex : util::irange(quorums_num)) {
     479      332625 :         uint256 quorumHash = GetQuorumBlockHash(llmqParams, m_chainstate.m_chain, nHeight, quorumIndex);
     480      332625 :         if (!quorumHash.IsNull() && !HasMinedCommitment(llmqParams.type, quorumHash)) ++ret;
     481             :     }
     482             : 
     483      261603 :     return ret;
     484      724064 : }
     485             : 
     486             : // WARNING: This method returns uint256() on the first block of the DKG interval (because the block hash is not known yet)
     487      628656 : uint256 CQuorumBlockProcessor::GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight, int quorumIndex)
     488             : {
     489      628656 :     AssertLockHeld(::cs_main);
     490             : 
     491      628656 :     int quorumStartHeight = nHeight - (nHeight % llmqParams.dkgInterval) + quorumIndex;
     492             : 
     493      628656 :     const CBlockIndex* pindex = active_chain[quorumStartHeight];
     494      628656 :     if (pindex == nullptr) {
     495           0 :         LogPrint(BCLog::LLMQ, "[GetQuorumBlockHash] llmqType[%d] h[%d] qi[%d] quorumStartHeight[%d] quorumHash[EMPTY]\n", std23::to_underlying(llmqParams.type), nHeight, quorumIndex, quorumStartHeight);
     496           0 :         return {};
     497             :     }
     498             : 
     499      628656 :     return pindex->GetBlockHash();
     500      628656 : }
     501             : 
     502      602919 : bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const
     503             : {
     504             :     bool fExists;
     505     1176223 :     if (LOCK(minableCommitmentsCs); mapHasMinedCommitmentCache[llmqType].get(quorumHash, fExists)) {
     506      573304 :         return fExists;
     507             :     }
     508             : 
     509       29615 :     fExists = m_evoDb.Exists(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)));
     510             : 
     511       29615 :     LOCK(minableCommitmentsCs);
     512       29615 :     mapHasMinedCommitmentCache[llmqType].insert(quorumHash, fExists);
     513             : 
     514       29615 :     return fExists;
     515      602919 : }
     516             : 
     517       10450 : std::pair<CFinalCommitment, uint256> CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType,
     518             :                                                                                const uint256& quorumHash) const
     519             : {
     520       10450 :     auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash));
     521       10450 :     std::pair<CFinalCommitment, uint256> ret;
     522       10450 :     if (!m_evoDb.Read(key, ret)) {
     523           0 :         return {CFinalCommitment{}, uint256::ZERO};
     524             :     }
     525       10450 :     return ret;
     526       10450 : }
     527             : 
     528             : // The returned quorums are in reversed order, so the most recent one is at index 0
     529     1197741 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null<const CBlockIndex*> pindex, size_t maxCount) const
     530             : {
     531     1197741 :     AssertLockNotHeld(m_evoDb.cs);
     532     1197741 :     LOCK(m_evoDb.cs);
     533             : 
     534     1197741 :     auto dbIt = m_evoDb.GetCurTransaction().NewIteratorUniquePtr();
     535             : 
     536     1197741 :     auto firstKey = BuildInversedHeightKey(llmqType, pindex->nHeight);
     537     1197741 :     auto lastKey = BuildInversedHeightKey(llmqType, 0);
     538             : 
     539     1197741 :     dbIt->Seek(firstKey);
     540             : 
     541     1197741 :     std::vector<const CBlockIndex*> ret;
     542     1197741 :     ret.reserve(maxCount);
     543             : 
     544     1491887 :     while (dbIt->Valid() && ret.size() < maxCount) {
     545     1049591 :         decltype(firstKey) curKey;
     546             :         int quorumHeight;
     547     1049591 :         if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
     548      115952 :             break;
     549             :         }
     550      933639 :         if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT || std::get<1>(curKey) != llmqType) {
     551      639493 :             break;
     552             :         }
     553             : 
     554      294146 :         if (uint32_t nMinedHeight = std::numeric_limits<uint32_t>::max() - be32toh_internal(std::get<2>(curKey));
     555      294146 :                 nMinedHeight > static_cast<uint32_t>(pindex->nHeight)) {
     556           0 :             break;
     557             :         }
     558             : 
     559      294146 :         if (!dbIt->GetValue(quorumHeight)) {
     560           0 :             break;
     561             :         }
     562             : 
     563      294146 :         const auto* pQuorumBaseBlockIndex = pindex->GetAncestor(quorumHeight);
     564      294146 :         assert(pQuorumBaseBlockIndex);
     565      294146 :         ret.emplace_back(pQuorumBaseBlockIndex);
     566             : 
     567      294146 :         dbIt->Next();
     568     1049591 :     }
     569             : 
     570     1197741 :     return ret;
     571     1197741 : }
     572             : 
     573      711310 : std::optional<const CBlockIndex*> CQuorumBlockProcessor::GetLastMinedCommitmentsByQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, int quorumIndex, size_t cycle) const
     574             : {
     575      711310 :     AssertLockNotHeld(m_evoDb.cs);
     576      711310 :     LOCK(m_evoDb.cs);
     577             : 
     578      711310 :     auto dbIt = m_evoDb.GetCurTransaction().NewIteratorUniquePtr();
     579             : 
     580      711310 :     auto firstKey = BuildInversedHeightKeyIndexed(llmqType, pindex->nHeight, quorumIndex);
     581      711310 :     auto lastKey = BuildInversedHeightKeyIndexed(llmqType, 0, quorumIndex);
     582             : 
     583      711310 :     size_t currentCycle = 0;
     584             : 
     585      711310 :     dbIt->Seek(firstKey);
     586             : 
     587      736809 :     while (dbIt->Valid()) {
     588      142009 :         decltype(firstKey) curKey;
     589             :         int quorumHeight;
     590      142009 :         if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
     591       25797 :             return std::nullopt;
     592             :         }
     593      116212 :         if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT_Q_INDEXED || std::get<1>(curKey) != llmqType) {
     594           0 :             return std::nullopt;
     595             :         }
     596             : 
     597      116212 :         if (uint32_t nMinedHeight = std::numeric_limits<uint32_t>::max() - be32toh_internal(std::get<3>(curKey));
     598      116212 :                 nMinedHeight > static_cast<uint32_t>(pindex->nHeight)) {
     599           0 :             return std::nullopt;
     600             :         }
     601             : 
     602      116212 :         if (!dbIt->GetValue(quorumHeight)) {
     603           0 :             return std::nullopt;
     604             :         }
     605             : 
     606      116212 :         const auto* pQuorumBaseBlockIndex = pindex->GetAncestor(quorumHeight);
     607      116212 :         assert(pQuorumBaseBlockIndex);
     608             : 
     609      116212 :         if (currentCycle == cycle) {
     610       90713 :             return std::make_optional(pQuorumBaseBlockIndex);
     611             :         }
     612             : 
     613       25499 :         currentCycle++;
     614             : 
     615       25499 :         dbIt->Next();
     616      142009 :     }
     617             : 
     618      594800 :     return std::nullopt;
     619      711310 : }
     620             : 
     621      355610 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetLastMinedCommitmentsPerQuorumIndexUntilBlock(
     622             :     Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const
     623             : {
     624      355610 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     625      355610 :     assert(llmq_params_opt.has_value());
     626      355610 :     std::vector<const CBlockIndex*> ret;
     627             : 
     628     1066830 :     for (const auto quorumIndex : util::irange(llmq_params_opt->signingActiveQuorumCount)) {
     629      711220 :         std::optional<const CBlockIndex*> q = GetLastMinedCommitmentsByQuorumIndexUntilBlock(llmqType, pindex, quorumIndex, cycle);
     630      711220 :         if (q.has_value()) {
     631       90667 :             ret.emplace_back(q.value());
     632       90667 :         }
     633             :     }
     634             : 
     635      355610 :     return ret;
     636      355610 : }
     637             : 
     638      144190 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsIndexedUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount) const
     639             : {
     640      144190 :     std::vector<const CBlockIndex*> ret;
     641             : 
     642      144190 :     size_t cycle = 0;
     643             : 
     644      162064 :     while (ret.size() < maxCount) {
     645      157172 :         std::vector<const CBlockIndex*> cycleRet = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, pindex, cycle);
     646             : 
     647      157172 :         if (cycleRet.empty()) {
     648      139298 :             return ret;
     649             :         }
     650             : 
     651       17874 :         size_t needToCopy = maxCount - ret.size();
     652       17874 :         std::copy_n(cycleRet.begin(), std::min(needToCopy, cycleRet.size()), std::back_inserter(ret));
     653       17874 :         cycle++;
     654      157172 :     }
     655             : 
     656        4892 :     return ret;
     657      144190 : }
     658             : 
     659             : // The returned quorums are in reversed order, so the most recent one is at index 0
     660      206577 : std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> CQuorumBlockProcessor::GetMinedAndActiveCommitmentsUntilBlock(gsl::not_null<const CBlockIndex*> pindex) const
     661             : {
     662      206577 :     std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> ret;
     663             : 
     664     1239462 :     for (const auto& params : Params().GetConsensus().llmqs) {
     665     1032885 :         auto& commitments = ret[params.type];
     666     1032885 :         if (IsQuorumRotationEnabled(params, pindex)) {
     667      198434 :             commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, pindex, 0);
     668      198434 :         } else {
     669      834451 :             commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount);
     670             :         }
     671             :     }
     672             : 
     673      206577 :     return ret;
     674      206577 : }
     675             : 
     676       15528 : bool CQuorumBlockProcessor::HasMineableCommitment(const uint256& hash) const
     677             : {
     678       15528 :     LOCK(minableCommitmentsCs);
     679       15528 :     return minableCommitments.count(hash) != 0;
     680       15528 : }
     681             : 
     682        4480 : std::optional<CInv> CQuorumBlockProcessor::AddMineableCommitment(const CFinalCommitment& fqc)
     683             : {
     684        4480 :     const uint256 commitmentHash = ::SerializeHash(fqc);
     685             : 
     686        8960 :     const bool relay = [&]() {
     687        4480 :         LOCK(minableCommitmentsCs);
     688             : 
     689        4480 :         auto k = std::make_pair(fqc.llmqType, fqc.quorumHash);
     690        4480 :         auto [itInserted, successfullyInserted] = minableCommitmentsByQuorum.try_emplace(k, commitmentHash);
     691        4480 :         if (successfullyInserted) {
     692        3954 :             minableCommitments.try_emplace(commitmentHash, fqc);
     693        3954 :             return true;
     694             :         } else {
     695         526 :             auto& insertedQuorumHash = itInserted->second;
     696         526 :             const auto& oldFqc = minableCommitments.at(insertedQuorumHash);
     697         526 :             if (fqc.CountSigners() > oldFqc.CountSigners()) {
     698             :                 // new commitment has more signers, so override the known one
     699           4 :                 insertedQuorumHash = commitmentHash;
     700           4 :                 minableCommitments.erase(insertedQuorumHash);
     701           4 :                 minableCommitments.try_emplace(commitmentHash, fqc);
     702           4 :                 return true;
     703             :             }
     704             :         }
     705         522 :         return false;
     706        4480 :     }();
     707             : 
     708        4480 :     return relay ? std::make_optional(CInv{MSG_QUORUM_FINAL_COMMITMENT, commitmentHash}) : std::nullopt;
     709             : }
     710             : 
     711        2715 : bool CQuorumBlockProcessor::GetMineableCommitmentByHash(const uint256& commitmentHash, llmq::CFinalCommitment& ret) const
     712             : {
     713        2715 :     LOCK(minableCommitmentsCs);
     714        2715 :     auto it = minableCommitments.find(commitmentHash);
     715        2715 :     if (it == minableCommitments.end()) {
     716           2 :         return false;
     717             :     }
     718        2713 :     ret = it->second;
     719        2713 :     return true;
     720        2715 : }
     721             : 
     722             : // Will return nullopt if no commitment should be mined
     723             : // Will return a null commitment if no mineable commitment is known and none was mined yet
     724      173708 : std::optional<std::vector<CFinalCommitment>> CQuorumBlockProcessor::GetMineableCommitments(const Consensus::LLMQParams& llmqParams, int nHeight) const
     725             : {
     726      173708 :     AssertLockHeld(::cs_main);
     727             : 
     728      173708 :     std::vector<CFinalCommitment> ret;
     729             : 
     730      173708 :     if (GetNumCommitmentsRequired(llmqParams, nHeight) == 0) {
     731             :         // no commitment required
     732      123389 :         return std::nullopt;
     733             :     }
     734             : 
     735             :     // Note: This function can be called for new blocks
     736       50319 :     assert(nHeight <= m_chainstate.m_chain.Height() + 1);
     737       50319 :     const auto *const pindex = m_chainstate.m_chain.Height() < nHeight ? m_chainstate.m_chain.Tip() : m_chainstate.m_chain.Tip()->GetAncestor(nHeight);
     738             : 
     739       50319 :     bool rotation_enabled = IsQuorumRotationEnabled(llmqParams, pindex);
     740       50319 :     bool basic_bls_enabled{DeploymentActiveAfter(pindex, m_chainstate.m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V19)};
     741       50319 :     size_t quorums_num = rotation_enabled ? llmqParams.signingActiveQuorumCount : 1;
     742             : 
     743       50319 :     std::stringstream ss;
     744      112965 :     for (const auto quorumIndex : util::irange(quorums_num)) {
     745       62646 :         CFinalCommitment cf;
     746             : 
     747       62646 :         uint256 quorumHash = GetQuorumBlockHash(llmqParams, m_chainstate.m_chain, nHeight, quorumIndex);
     748       62646 :         if (quorumHash.IsNull()) {
     749           0 :             break;
     750             :         }
     751             : 
     752       62646 :         if (HasMinedCommitment(llmqParams.type, quorumHash)) continue;
     753             : 
     754       62178 :         LOCK(minableCommitmentsCs);
     755             : 
     756       62178 :         auto k = std::make_pair(llmqParams.type, quorumHash);
     757       62178 :         if (auto it = minableCommitmentsByQuorum.find(k); it == minableCommitmentsByQuorum.end()) {
     758             :             // null commitment required
     759       58063 :             cf = CFinalCommitment(llmqParams, quorumHash);
     760       58063 :             cf.quorumIndex = static_cast<int16_t>(quorumIndex);
     761       58063 :             cf.nVersion = CFinalCommitment::GetVersion(rotation_enabled, basic_bls_enabled);
     762       58063 :             ss << "{ created nversion[" << cf.nVersion << "] quorumIndex[" << cf.quorumIndex << "] }";
     763       58063 :         } else {
     764        4115 :             cf = minableCommitments.at(it->second);
     765        4115 :             ss << "{ cached nversion[" << cf.nVersion << "] quorumIndex[" << cf.quorumIndex << "] }";
     766             :         }
     767             : 
     768       62178 :         ret.push_back(std::move(cf));
     769       62646 :     }
     770             : 
     771       50319 :     LogPrint(BCLog::LLMQ, "GetMineableCommitments cf height[%d] content: %s\n", nHeight, ss.str());
     772             : 
     773       50319 :     if (ret.empty()) {
     774           0 :         return std::nullopt;
     775             :     }
     776       50319 :     return std::make_optional(ret);
     777      173708 : }
     778             : 
     779      125388 : bool CQuorumBlockProcessor::GetMineableCommitmentsTx(const Consensus::LLMQParams& llmqParams, int nHeight, std::vector<CTransactionRef>& ret) const
     780             : {
     781      125388 :     AssertLockHeld(::cs_main);
     782      125388 :     std::optional<std::vector<CFinalCommitment>> qcs = GetMineableCommitments(llmqParams, nHeight);
     783      125388 :     if (!qcs.has_value()) {
     784       81239 :         return false;
     785             :     }
     786             : 
     787      100157 :     for (const auto& f : qcs.value()) {
     788       56008 :         CFinalCommitmentTxPayload qc;
     789       56008 :         qc.nHeight = nHeight;
     790       56008 :         qc.commitment = f;
     791       56008 :         CMutableTransaction tx;
     792       56008 :         tx.nVersion = 3;
     793       56008 :         tx.nType = TRANSACTION_QUORUM_COMMITMENT;
     794       56008 :         SetTxPayload(tx, qc);
     795       56008 :         ret.push_back(MakeTransactionRef(tx));
     796       56008 :     }
     797             : 
     798       44149 :     return true;
     799      125388 : }
     800             : 
     801             : } // namespace llmq

Generated by: LCOV version 1.16