LCOV - code coverage report
Current view: top level - src/llmq - blockprocessor.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 221 436 50.7 %
Date: 2026-06-25 07:23:51 Functions: 21 29 72.4 %

          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       18625 : static void PreComputeQuorumMembers(CDeterministicMNManager& dmnman, llmq::CQuorumSnapshotManager& qsnapman,
      31             :                                     const ChainstateManager& chainman, const CBlockIndex* pindex, bool reset_cache)
      32             : {
      33       79439 :     for (const Consensus::LLMQParams& params : llmq::GetEnabledQuorumParams(chainman, pindex->pprev)) {
      34       60814 :         if (llmq::IsQuorumRotationEnabled(params, pindex) && (pindex->nHeight % params.dkgInterval == 0)) {
      35         792 :             llmq::utils::GetAllQuorumMembers(params.type, {dmnman, qsnapman, chainman, pindex}, reset_cache);
      36         792 :         }
      37             :     }
      38       18625 : }
      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         360 : CQuorumBlockProcessor::CQuorumBlockProcessor(CChainState& chainstate, CDeterministicMNManager& dmnman, CEvoDB& evoDb,
      49             :                                              CQuorumSnapshotManager& qsnapman, int8_t bls_threads) :
      50         180 :     m_chainstate{chainstate},
      51         180 :     m_dmnman{dmnman},
      52         180 :     m_evoDb{evoDb},
      53         180 :     m_qsnapman{qsnapman}
      54         180 : {
      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         180 : }
      59             : 
      60         360 : CQuorumBlockProcessor::~CQuorumBlockProcessor()
      61         180 : {
      62         180 :     m_bls_queue.StopWorkerThreads();
      63         360 : }
      64             : 
      65           0 : MessageProcessingResult CQuorumBlockProcessor::ProcessMessage(const CNode& peer, std::string_view msg_type,
      66             :                                                               CDataStream& vRecv)
      67             : {
      68           0 :     if (msg_type != NetMsgType::QFCOMMITMENT) {
      69           0 :         return {};
      70             :     }
      71             : 
      72           0 :     CFinalCommitment qc;
      73           0 :     vRecv >> qc;
      74             : 
      75           0 :     MessageProcessingResult ret;
      76           0 :     ret.m_to_erase = CInv{MSG_QUORUM_FINAL_COMMITMENT, ::SerializeHash(qc)};
      77             : 
      78           0 :     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           0 :     const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
      85           0 :     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           0 :     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           0 :         LOCK(::cs_main);
      97           0 :         pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash);
      98           0 :         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           0 :         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           0 :         if (int quorumHeight = pQuorumBaseBlockIndex->nHeight - (pQuorumBaseBlockIndex->nHeight % llmq_params_opt->dkgInterval) + int(qc.quorumIndex);
     112           0 :                 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           0 :         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           0 :         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           0 :     }
     133             : 
     134             :     {
     135             :         // Check if we already got a better one locally
     136             :         // We do this before verifying the commitment to avoid DoS
     137           0 :         LOCK(minableCommitmentsCs);
     138           0 :         auto k = std::make_pair(type, qc.quorumHash);
     139           0 :         auto it = minableCommitmentsByQuorum.find(k);
     140           0 :         if (it != minableCommitmentsByQuorum.end()) {
     141           0 :             auto jt = minableCommitments.find(it->second);
     142           0 :             if (jt != minableCommitments.end() && jt->second.CountSigners() <= qc.CountSigners()) {
     143           0 :                 return ret;
     144             :             }
     145           0 :         }
     146           0 :     }
     147             : 
     148           0 :     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           0 :     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           0 :     if (auto inv_opt = AddMineableCommitment(qc)) {
     160           0 :         ret.m_inventory.emplace_back(inv_opt.value());
     161           0 :     }
     162           0 :     return ret;
     163           0 : }
     164             : 
     165       49014 : bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, BlockValidationState& state, bool fJustCheck, bool fBLSChecks)
     166             : {
     167       49014 :     AssertLockHeld(::cs_main);
     168             : 
     169       49014 :     const auto blockHash = pindex->GetBlockHash();
     170             : 
     171       49014 :     if (!DeploymentActiveAt(*pindex, m_chainstate.m_chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
     172       30778 :         m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, blockHash);
     173       30778 :         return true;
     174             :     }
     175             : 
     176       18236 :     PreComputeQuorumMembers(m_dmnman, m_qsnapman, m_chainstate.m_chainman, pindex, /*reset_cache=*/false);
     177             : 
     178       18236 :     std::multimap<Consensus::LLMQType, CFinalCommitment> qcs;
     179       18236 :     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       77880 :     for (const Consensus::LLMQParams& params : GetEnabledQuorumParams(m_chainstate.m_chainman, pindex->pprev)) {
     188             :         // skip these checks when replaying blocks after the crash
     189       59644 :         if (m_chainstate.m_chain.Tip() == nullptr) {
     190           0 :             break;
     191             :         }
     192             : 
     193       59644 :         const size_t numCommitmentsRequired = GetNumCommitmentsRequired(params, pindex->nHeight);
     194       59644 :         const auto numCommitmentsInNewBlock = qcs.count(params.type);
     195             : 
     196       59644 :         if (numCommitmentsRequired < numCommitmentsInNewBlock) {
     197           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-not-allowed");
     198             :         }
     199             : 
     200       59644 :         if (numCommitmentsRequired > numCommitmentsInNewBlock) {
     201           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-missing");
     202             :         }
     203       59644 :         if (IsQuorumRotationEnabled(params, pindex)) {
     204       18236 :             LogPrintf("[ProcessBlock] h[%d] numCommitmentsRequired[%d] numCommitmentsInNewBlock[%d]\n", pindex->nHeight, numCommitmentsRequired, numCommitmentsInNewBlock);
     205       18236 :         }
     206             :     }
     207             : 
     208       18236 :     if (fBLSChecks) {
     209       18236 :         CCheckQueueControl<utils::BlsCheck> queue_control(&m_bls_queue);
     210       47286 :         for (const auto& [_, qc] : qcs) {
     211       29050 :             if (qc.IsNull()) continue;
     212           0 :             const auto* pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash);
     213           0 :             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           0 :             qc.VerifySignatureAsync({m_dmnman, m_qsnapman, m_chainstate.m_chainman, pQuorumBaseBlockIndex}, &queue_control);
     219             :         }
     220             : 
     221       18236 :         if (!queue_control.Wait()) {
     222             :             // at least one check failed
     223           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-invalid");
     224             :         }
     225       18236 :     }
     226       47286 :     for (const auto& [_, qc] : qcs) {
     227       29050 :         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       18236 :     m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, blockHash);
     234             : 
     235       18236 :     return true;
     236       49014 : }
     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      291760 : 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      291760 :     return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, llmqType, htobe32_internal(std::numeric_limits<uint32_t>::max() - nMinedHeight));
     244             : }
     245             : 
     246      145880 : 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      145880 :     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       89606 : static bool IsMiningPhase(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight)
     253             :     EXCLUSIVE_LOCKS_REQUIRED(cs_main)
     254             : {
     255       89606 :     AssertLockHeld(cs_main);
     256             : 
     257             :     // Note: This function can be called for new blocks
     258       89606 :     assert(nHeight <= active_chain.Height() + 1);
     259             : 
     260       89606 :     int quorumCycleStartHeight = nHeight - (nHeight % llmqParams.dkgInterval);
     261       89606 :     int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowStart;
     262       89606 :     int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmqParams.dkgMiningWindowEnd;
     263             : 
     264       89606 :     return nHeight >= quorumCycleMiningStartHeight && nHeight <= quorumCycleMiningEndHeight;
     265             : }
     266             : 
     267       29050 : bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc,
     268             :                                               BlockValidationState& state, bool fJustCheck)
     269             : {
     270       29050 :     AssertLockHeld(::cs_main);
     271             : 
     272       29050 :     const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
     273       29050 :     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       29050 :     const auto& llmq_params = llmq_params_opt.value();
     278             : 
     279       29050 :     uint256 quorumHash = GetQuorumBlockHash(llmq_params, m_chainstate.m_chain, nHeight, qc.quorumIndex);
     280             : 
     281       29050 :     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       29050 :     if (m_chainstate.m_chain.Tip() == nullptr) {
     290           0 :         quorumHash = qc.quorumHash;
     291           0 :     }
     292             : 
     293       29050 :     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       29050 :     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       29050 :     if (qc.IsNull()) {
     311       29050 :         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       29050 :         return true;
     320             :     }
     321             : 
     322           0 :     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           0 :     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           0 :     const auto* pQuorumBaseBlockIndex = m_chainstate.m_blockman.LookupBlockIndex(qc.quorumHash);
     333           0 :     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           0 :     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           0 :     if (fJustCheck) {
     350           0 :         return true;
     351             :     }
     352             : 
     353           0 :     bool rotation_enabled = IsQuorumRotationEnabled(llmq_params, pQuorumBaseBlockIndex);
     354             : 
     355           0 :     if (rotation_enabled) {
     356           0 :         LogPrint(BCLog::LLMQ, "%s -- height[%d] pQuorumBaseBlockIndex[%d] quorumIndex[%d] qversion[%d] Built\n",
     357             :                  __func__, nHeight, pQuorumBaseBlockIndex->nHeight, qc.quorumIndex, qc.nVersion);
     358           0 :     }
     359             : 
     360             :     // Store commitment in DB
     361           0 :     auto cacheKey = std::make_pair(llmq_params.type, quorumHash);
     362           0 :     m_evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, cacheKey), std::make_pair(qc, blockHash));
     363             : 
     364           0 :     if (rotation_enabled) {
     365           0 :         m_evoDb.Write(BuildInversedHeightKeyIndexed(llmq_params.type, nHeight, int(qc.quorumIndex)), pQuorumBaseBlockIndex->nHeight);
     366           0 :     } else {
     367           0 :         m_evoDb.Write(BuildInversedHeightKey(llmq_params.type, nHeight), pQuorumBaseBlockIndex->nHeight);
     368             :     }
     369             : 
     370             :     {
     371           0 :         LOCK(minableCommitmentsCs);
     372           0 :         mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash);
     373           0 :         minableCommitmentsByQuorum.erase(cacheKey);
     374           0 :         minableCommitments.erase(::SerializeHash(qc));
     375           0 :     }
     376             : 
     377           0 :     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           0 :     return true;
     381       29050 : }
     382             : 
     383         389 : bool CQuorumBlockProcessor::UndoBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex)
     384             : {
     385         389 :     AssertLockHeld(::cs_main);
     386             : 
     387         389 :     PreComputeQuorumMembers(m_dmnman, m_qsnapman, m_chainstate.m_chainman, pindex, /*reset_cache=*/true);
     388             : 
     389         389 :     std::multimap<Consensus::LLMQType, CFinalCommitment> qcs;
     390         389 :     if (BlockValidationState dummy; !GetCommitmentsFromBlock(block, pindex, qcs, dummy)) {
     391           0 :         return false;
     392             :     }
     393             : 
     394         389 :     for (auto& [_, qc2] : qcs) {
     395           0 :         auto& qc = qc2; // cannot capture structured binding into lambda
     396           0 :         if (qc.IsNull()) {
     397           0 :             continue;
     398             :         }
     399             : 
     400           0 :         m_evoDb.Erase(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash)));
     401             : 
     402           0 :         const auto& llmq_params_opt = Params().GetLLMQ(qc.llmqType);
     403           0 :         assert(llmq_params_opt.has_value());
     404             : 
     405           0 :         if (IsQuorumRotationEnabled(llmq_params_opt.value(), pindex)) {
     406           0 :             m_evoDb.Erase(BuildInversedHeightKeyIndexed(qc.llmqType, pindex->nHeight, int(qc.quorumIndex)));
     407           0 :         } else {
     408           0 :             m_evoDb.Erase(BuildInversedHeightKey(qc.llmqType, pindex->nHeight));
     409             :         }
     410             : 
     411           0 :         WITH_LOCK(minableCommitmentsCs, mapHasMinedCommitmentCache[qc.llmqType].erase(qc.quorumHash));
     412             : 
     413             :         // if a reorg happened, we should allow to mine this commitment later
     414           0 :         AddMineableCommitment(qc);
     415             :     }
     416             : 
     417         389 :     m_evoDb.Write(DB_BEST_BLOCK_UPGRADE, pindex->pprev->GetBlockHash());
     418             : 
     419         389 :     return true;
     420         389 : }
     421             : 
     422       18625 : bool CQuorumBlockProcessor::GetCommitmentsFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindex, std::multimap<Consensus::LLMQType, CFinalCommitment>& ret, BlockValidationState& state)
     423             : {
     424       18625 :     AssertLockHeld(::cs_main);
     425             : 
     426       18625 :     const auto& consensus = Params().GetConsensus();
     427             : 
     428       18625 :     ret.clear();
     429             : 
     430       66352 :     for (const auto& tx : block.vtx) {
     431       47727 :         if (tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
     432       29050 :             auto opt_qc = GetTxPayload<CFinalCommitmentTxPayload>(*tx);
     433       29050 :             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       29050 :             auto& qc = *opt_qc;
     439             : 
     440       29050 :             const auto& llmq_params_opt = Params().GetLLMQ(qc.commitment.llmqType);
     441       29050 :             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       29050 :             if (!IsQuorumRotationEnabled(llmq_params_opt.value(), pindex)) {
     448       15466 :                 if (ret.count(qc.commitment.llmqType) != 0) {
     449           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-dup");
     450             :                 }
     451       15466 :             }
     452             : 
     453       29050 :             ret.emplace(qc.commitment.llmqType, std::move(qc.commitment));
     454       29050 :         }
     455             :     }
     456             : 
     457       18625 :     if (!DeploymentActiveAt(*pindex, consensus, Consensus::DEPLOYMENT_DIP0003) && !ret.empty()) {
     458           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-premature");
     459             :     }
     460             : 
     461       18625 :     return true;
     462       18625 : }
     463             : 
     464       89606 : size_t CQuorumBlockProcessor::GetNumCommitmentsRequired(const Consensus::LLMQParams& llmqParams, int nHeight) const
     465             : {
     466       89606 :     AssertLockHeld(::cs_main);
     467             : 
     468       89606 :     if (!IsMiningPhase(llmqParams, m_chainstate.m_chain, nHeight)) return 0;
     469             : 
     470             :     // Note: This function can be called for new blocks
     471       33435 :     assert(nHeight <= m_chainstate.m_chain.Height() + 1);
     472       33435 :     const auto *const pindex = m_chainstate.m_chain.Height() < nHeight ? m_chainstate.m_chain.Tip() : m_chainstate.m_chain.Tip()->GetAncestor(nHeight);
     473             : 
     474       33435 :     bool rotation_enabled = IsQuorumRotationEnabled(llmqParams, pindex);
     475       33435 :     size_t quorums_num = rotation_enabled ? llmqParams.signingActiveQuorumCount : 1;
     476       33435 :     size_t ret{0};
     477             : 
     478       77070 :     for (const auto quorumIndex : util::irange(quorums_num)) {
     479       43635 :         uint256 quorumHash = GetQuorumBlockHash(llmqParams, m_chainstate.m_chain, nHeight, quorumIndex);
     480       43635 :         if (!quorumHash.IsNull() && !HasMinedCommitment(llmqParams.type, quorumHash)) ++ret;
     481             :     }
     482             : 
     483       33435 :     return ret;
     484       89606 : }
     485             : 
     486             : // WARNING: This method returns uint256() on the first block of the DKG interval (because the block hash is not known yet)
     487       87270 : uint256 CQuorumBlockProcessor::GetQuorumBlockHash(const Consensus::LLMQParams& llmqParams, const CChain& active_chain, int nHeight, int quorumIndex)
     488             : {
     489       87270 :     AssertLockHeld(::cs_main);
     490             : 
     491       87270 :     int quorumStartHeight = nHeight - (nHeight % llmqParams.dkgInterval) + quorumIndex;
     492             : 
     493       87270 :     const CBlockIndex* pindex = active_chain[quorumStartHeight];
     494       87270 :     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       87270 :     return pindex->GetBlockHash();
     500       87270 : }
     501             : 
     502       58220 : bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash) const
     503             : {
     504             :     bool fExists;
     505      114827 :     if (LOCK(minableCommitmentsCs); mapHasMinedCommitmentCache[llmqType].get(quorumHash, fExists)) {
     506       56607 :         return fExists;
     507             :     }
     508             : 
     509        1613 :     fExists = m_evoDb.Exists(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash)));
     510             : 
     511        1613 :     LOCK(minableCommitmentsCs);
     512        1613 :     mapHasMinedCommitmentCache[llmqType].insert(quorumHash, fExists);
     513             : 
     514        1613 :     return fExists;
     515       58220 : }
     516             : 
     517           0 : std::pair<CFinalCommitment, uint256> CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType,
     518             :                                                                                const uint256& quorumHash) const
     519             : {
     520           0 :     auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash));
     521           0 :     std::pair<CFinalCommitment, uint256> ret;
     522           0 :     if (!m_evoDb.Read(key, ret)) {
     523           0 :         return {CFinalCommitment{}, uint256::ZERO};
     524             :     }
     525           0 :     return ret;
     526           0 : }
     527             : 
     528             : // The returned quorums are in reversed order, so the most recent one is at index 0
     529      145880 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsUntilBlock(Consensus::LLMQType llmqType, gsl::not_null<const CBlockIndex*> pindex, size_t maxCount) const
     530             : {
     531      145880 :     AssertLockNotHeld(m_evoDb.cs);
     532      145880 :     LOCK(m_evoDb.cs);
     533             : 
     534      145880 :     auto dbIt = m_evoDb.GetCurTransaction().NewIteratorUniquePtr();
     535             : 
     536      145880 :     auto firstKey = BuildInversedHeightKey(llmqType, pindex->nHeight);
     537      145880 :     auto lastKey = BuildInversedHeightKey(llmqType, 0);
     538             : 
     539      145880 :     dbIt->Seek(firstKey);
     540             : 
     541      145880 :     std::vector<const CBlockIndex*> ret;
     542      145880 :     ret.reserve(maxCount);
     543             : 
     544      145880 :     while (dbIt->Valid() && ret.size() < maxCount) {
     545      121320 :         decltype(firstKey) curKey;
     546             :         int quorumHeight;
     547      121320 :         if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
     548           0 :             break;
     549             :         }
     550      121320 :         if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT || std::get<1>(curKey) != llmqType) {
     551      121320 :             break;
     552             :         }
     553             : 
     554           0 :         if (uint32_t nMinedHeight = std::numeric_limits<uint32_t>::max() - be32toh_internal(std::get<2>(curKey));
     555           0 :                 nMinedHeight > static_cast<uint32_t>(pindex->nHeight)) {
     556           0 :             break;
     557             :         }
     558             : 
     559           0 :         if (!dbIt->GetValue(quorumHeight)) {
     560           0 :             break;
     561             :         }
     562             : 
     563           0 :         const auto* pQuorumBaseBlockIndex = pindex->GetAncestor(quorumHeight);
     564           0 :         assert(pQuorumBaseBlockIndex);
     565           0 :         ret.emplace_back(pQuorumBaseBlockIndex);
     566             : 
     567           0 :         dbIt->Next();
     568      121320 :     }
     569             : 
     570      145880 :     return ret;
     571      145880 : }
     572             : 
     573       72940 : std::optional<const CBlockIndex*> CQuorumBlockProcessor::GetLastMinedCommitmentsByQuorumIndexUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, int quorumIndex, size_t cycle) const
     574             : {
     575       72940 :     AssertLockNotHeld(m_evoDb.cs);
     576       72940 :     LOCK(m_evoDb.cs);
     577             : 
     578       72940 :     auto dbIt = m_evoDb.GetCurTransaction().NewIteratorUniquePtr();
     579             : 
     580       72940 :     auto firstKey = BuildInversedHeightKeyIndexed(llmqType, pindex->nHeight, quorumIndex);
     581       72940 :     auto lastKey = BuildInversedHeightKeyIndexed(llmqType, 0, quorumIndex);
     582             : 
     583       72940 :     size_t currentCycle = 0;
     584             : 
     585       72940 :     dbIt->Seek(firstKey);
     586             : 
     587       72940 :     while (dbIt->Valid()) {
     588           0 :         decltype(firstKey) curKey;
     589             :         int quorumHeight;
     590           0 :         if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
     591           0 :             return std::nullopt;
     592             :         }
     593           0 :         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           0 :         if (uint32_t nMinedHeight = std::numeric_limits<uint32_t>::max() - be32toh_internal(std::get<3>(curKey));
     598           0 :                 nMinedHeight > static_cast<uint32_t>(pindex->nHeight)) {
     599           0 :             return std::nullopt;
     600             :         }
     601             : 
     602           0 :         if (!dbIt->GetValue(quorumHeight)) {
     603           0 :             return std::nullopt;
     604             :         }
     605             : 
     606           0 :         const auto* pQuorumBaseBlockIndex = pindex->GetAncestor(quorumHeight);
     607           0 :         assert(pQuorumBaseBlockIndex);
     608             : 
     609           0 :         if (currentCycle == cycle) {
     610           0 :             return std::make_optional(pQuorumBaseBlockIndex);
     611             :         }
     612             : 
     613           0 :         currentCycle++;
     614             : 
     615           0 :         dbIt->Next();
     616           0 :     }
     617             : 
     618       72940 :     return std::nullopt;
     619       72940 : }
     620             : 
     621       36470 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetLastMinedCommitmentsPerQuorumIndexUntilBlock(
     622             :     Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t cycle) const
     623             : {
     624       36470 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     625       36470 :     assert(llmq_params_opt.has_value());
     626       36470 :     std::vector<const CBlockIndex*> ret;
     627             : 
     628      109410 :     for (const auto quorumIndex : util::irange(llmq_params_opt->signingActiveQuorumCount)) {
     629       72940 :         std::optional<const CBlockIndex*> q = GetLastMinedCommitmentsByQuorumIndexUntilBlock(llmqType, pindex, quorumIndex, cycle);
     630       72940 :         if (q.has_value()) {
     631           0 :             ret.emplace_back(q.value());
     632           0 :         }
     633             :     }
     634             : 
     635       36470 :     return ret;
     636       36470 : }
     637             : 
     638           0 : std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsIndexedUntilBlock(Consensus::LLMQType llmqType, const CBlockIndex* pindex, size_t maxCount) const
     639             : {
     640           0 :     std::vector<const CBlockIndex*> ret;
     641             : 
     642           0 :     size_t cycle = 0;
     643             : 
     644           0 :     while (ret.size() < maxCount) {
     645           0 :         std::vector<const CBlockIndex*> cycleRet = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(llmqType, pindex, cycle);
     646             : 
     647           0 :         if (cycleRet.empty()) {
     648           0 :             return ret;
     649             :         }
     650             : 
     651           0 :         size_t needToCopy = maxCount - ret.size();
     652           0 :         std::copy_n(cycleRet.begin(), std::min(needToCopy, cycleRet.size()), std::back_inserter(ret));
     653           0 :         cycle++;
     654           0 :     }
     655             : 
     656           0 :     return ret;
     657           0 : }
     658             : 
     659             : // The returned quorums are in reversed order, so the most recent one is at index 0
     660       36470 : std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> CQuorumBlockProcessor::GetMinedAndActiveCommitmentsUntilBlock(gsl::not_null<const CBlockIndex*> pindex) const
     661             : {
     662       36470 :     std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> ret;
     663             : 
     664      218820 :     for (const auto& params : Params().GetConsensus().llmqs) {
     665      182350 :         auto& commitments = ret[params.type];
     666      182350 :         if (IsQuorumRotationEnabled(params, pindex)) {
     667       36470 :             commitments = GetLastMinedCommitmentsPerQuorumIndexUntilBlock(params.type, pindex, 0);
     668       36470 :         } else {
     669      145880 :             commitments = GetMinedCommitmentsUntilBlock(params.type, pindex, params.signingActiveQuorumCount);
     670             :         }
     671             :     }
     672             : 
     673       36470 :     return ret;
     674       36470 : }
     675             : 
     676           0 : bool CQuorumBlockProcessor::HasMineableCommitment(const uint256& hash) const
     677             : {
     678           0 :     LOCK(minableCommitmentsCs);
     679           0 :     return minableCommitments.count(hash) != 0;
     680           0 : }
     681             : 
     682           0 : std::optional<CInv> CQuorumBlockProcessor::AddMineableCommitment(const CFinalCommitment& fqc)
     683             : {
     684           0 :     const uint256 commitmentHash = ::SerializeHash(fqc);
     685             : 
     686           0 :     const bool relay = [&]() {
     687           0 :         LOCK(minableCommitmentsCs);
     688             : 
     689           0 :         auto k = std::make_pair(fqc.llmqType, fqc.quorumHash);
     690           0 :         auto [itInserted, successfullyInserted] = minableCommitmentsByQuorum.try_emplace(k, commitmentHash);
     691           0 :         if (successfullyInserted) {
     692           0 :             minableCommitments.try_emplace(commitmentHash, fqc);
     693           0 :             return true;
     694             :         } else {
     695           0 :             auto& insertedQuorumHash = itInserted->second;
     696           0 :             const auto& oldFqc = minableCommitments.at(insertedQuorumHash);
     697           0 :             if (fqc.CountSigners() > oldFqc.CountSigners()) {
     698             :                 // new commitment has more signers, so override the known one
     699           0 :                 insertedQuorumHash = commitmentHash;
     700           0 :                 minableCommitments.erase(insertedQuorumHash);
     701           0 :                 minableCommitments.try_emplace(commitmentHash, fqc);
     702           0 :                 return true;
     703             :             }
     704             :         }
     705           0 :         return false;
     706           0 :     }();
     707             : 
     708           0 :     return relay ? std::make_optional(CInv{MSG_QUORUM_FINAL_COMMITMENT, commitmentHash}) : std::nullopt;
     709             : }
     710             : 
     711           0 : bool CQuorumBlockProcessor::GetMineableCommitmentByHash(const uint256& commitmentHash, llmq::CFinalCommitment& ret) const
     712             : {
     713           0 :     LOCK(minableCommitmentsCs);
     714           0 :     auto it = minableCommitments.find(commitmentHash);
     715           0 :     if (it == minableCommitments.end()) {
     716           0 :         return false;
     717             :     }
     718           0 :     ret = it->second;
     719           0 :     return true;
     720           0 : }
     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       29962 : std::optional<std::vector<CFinalCommitment>> CQuorumBlockProcessor::GetMineableCommitments(const Consensus::LLMQParams& llmqParams, int nHeight) const
     725             : {
     726       29962 :     AssertLockHeld(::cs_main);
     727             : 
     728       29962 :     std::vector<CFinalCommitment> ret;
     729             : 
     730       29962 :     if (GetNumCommitmentsRequired(llmqParams, nHeight) == 0) {
     731             :         // no commitment required
     732       18785 :         return std::nullopt;
     733             :     }
     734             : 
     735             :     // Note: This function can be called for new blocks
     736       11177 :     assert(nHeight <= m_chainstate.m_chain.Height() + 1);
     737       11177 :     const auto *const pindex = m_chainstate.m_chain.Height() < nHeight ? m_chainstate.m_chain.Tip() : m_chainstate.m_chain.Tip()->GetAncestor(nHeight);
     738             : 
     739       11177 :     bool rotation_enabled = IsQuorumRotationEnabled(llmqParams, pindex);
     740       11177 :     bool basic_bls_enabled{DeploymentActiveAfter(pindex, m_chainstate.m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V19)};
     741       11177 :     size_t quorums_num = rotation_enabled ? llmqParams.signingActiveQuorumCount : 1;
     742             : 
     743       11177 :     std::stringstream ss;
     744       25762 :     for (const auto quorumIndex : util::irange(quorums_num)) {
     745       14585 :         CFinalCommitment cf;
     746             : 
     747       14585 :         uint256 quorumHash = GetQuorumBlockHash(llmqParams, m_chainstate.m_chain, nHeight, quorumIndex);
     748       14585 :         if (quorumHash.IsNull()) {
     749           0 :             break;
     750             :         }
     751             : 
     752       14585 :         if (HasMinedCommitment(llmqParams.type, quorumHash)) continue;
     753             : 
     754       14585 :         LOCK(minableCommitmentsCs);
     755             : 
     756       14585 :         auto k = std::make_pair(llmqParams.type, quorumHash);
     757       14585 :         if (auto it = minableCommitmentsByQuorum.find(k); it == minableCommitmentsByQuorum.end()) {
     758             :             // null commitment required
     759       14585 :             cf = CFinalCommitment(llmqParams, quorumHash);
     760       14585 :             cf.quorumIndex = static_cast<int16_t>(quorumIndex);
     761       14585 :             cf.nVersion = CFinalCommitment::GetVersion(rotation_enabled, basic_bls_enabled);
     762       14585 :             ss << "{ created nversion[" << cf.nVersion << "] quorumIndex[" << cf.quorumIndex << "] }";
     763       14585 :         } else {
     764           0 :             cf = minableCommitments.at(it->second);
     765           0 :             ss << "{ cached nversion[" << cf.nVersion << "] quorumIndex[" << cf.quorumIndex << "] }";
     766             :         }
     767             : 
     768       14585 :         ret.push_back(std::move(cf));
     769       14585 :     }
     770             : 
     771       11177 :     LogPrint(BCLog::LLMQ, "GetMineableCommitments cf height[%d] content: %s\n", nHeight, ss.str());
     772             : 
     773       11177 :     if (ret.empty()) {
     774           0 :         return std::nullopt;
     775             :     }
     776       11177 :     return std::make_optional(ret);
     777       29962 : }
     778             : 
     779       29962 : bool CQuorumBlockProcessor::GetMineableCommitmentsTx(const Consensus::LLMQParams& llmqParams, int nHeight, std::vector<CTransactionRef>& ret) const
     780             : {
     781       29962 :     AssertLockHeld(::cs_main);
     782       29962 :     std::optional<std::vector<CFinalCommitment>> qcs = GetMineableCommitments(llmqParams, nHeight);
     783       29962 :     if (!qcs.has_value()) {
     784       18785 :         return false;
     785             :     }
     786             : 
     787       25762 :     for (const auto& f : qcs.value()) {
     788       14585 :         CFinalCommitmentTxPayload qc;
     789       14585 :         qc.nHeight = nHeight;
     790       14585 :         qc.commitment = f;
     791       14585 :         CMutableTransaction tx;
     792       14585 :         tx.nVersion = 3;
     793       14585 :         tx.nType = TRANSACTION_QUORUM_COMMITMENT;
     794       14585 :         SetTxPayload(tx, qc);
     795       14585 :         ret.push_back(MakeTransactionRef(tx));
     796       14585 :     }
     797             : 
     798       11177 :     return true;
     799       29962 : }
     800             : 
     801             : } // namespace llmq

Generated by: LCOV version 1.16