LCOV - code coverage report
Current view: top level - src/evo - cbtx.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 118 134 88.1 %
Date: 2026-06-25 07:23:43 Functions: 6 7 85.7 %

          Line data    Source code
       1             : // Copyright (c) 2017-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <evo/cbtx.h>
       6             : 
       7             : #include <evo/specialtx.h>
       8             : #include <llmq/blockprocessor.h>
       9             : #include <llmq/commitment.h>
      10             : #include <llmq/options.h>
      11             : #include <llmq/quorumsman.h>
      12             : #include <llmq/utils.h>
      13             : #include <util/std23.h>
      14             : 
      15             : #include <chain.h>
      16             : #include <chainparams.h>
      17             : #include <consensus/merkle.h>
      18             : #include <consensus/validation.h>
      19             : #include <deploymentstatus.h>
      20             : #include <node/blockstorage.h>
      21             : 
      22             : using node::ReadBlockFromDisk;
      23             : 
      24      279609 : bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationState& state)
      25             : {
      26      279609 :     if (cbTx.nVersion == CCbTx::Version::INVALID || cbTx.nVersion >= CCbTx::Version::UNKNOWN) {
      27      224108 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
      28             :     }
      29             : 
      30      279609 :     if (pindexPrev) {
      31      279609 :         if (pindexPrev->nHeight + 1 != cbTx.nHeight) {
      32           0 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-height");
      33             :         }
      34             : 
      35      279609 :         const bool fDIP0008Active{DeploymentActiveAt(*pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008)};
      36      279609 :         if (fDIP0008Active && cbTx.nVersion < CCbTx::Version::MERKLE_ROOT_QUORUMS) {
      37           0 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
      38             :         }
      39             : 
      40      279609 :         const bool isV20{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)};
      41      279609 :         if ((isV20 && cbTx.nVersion < CCbTx::Version::CLSIG_AND_BALANCE) || (!isV20 && cbTx.nVersion >= CCbTx::Version::CLSIG_AND_BALANCE)) {
      42      112054 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
      43             :         }
      44      167555 :     }
      45             : 
      46      167555 :     return true;
      47      167555 : }
      48             : 
      49             : using QcHashMap = std::map<Consensus::LLMQType, std::vector<uint256>>;
      50             : using QcIndexedHashMap = std::map<Consensus::LLMQType, std::map<int16_t, uint256>>;
      51             : 
      52             : /**
      53             :  * Handles the calculation or caching of qcHashes and qcIndexedHashes
      54             :  * @param pindexPrev The const CBlockIndex* (ie a block) of a block. Both the Quorum list and quorum rotation activation status will be retrieved based on this block.
      55             :  * @return nullopt if quorumCommitment was unable to be found, otherwise returns the qcHashes and qcIndexedHashes that were calculated or cached
      56             :  */
      57      206309 : auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq::CQuorumBlockProcessor& quorum_block_processor) ->
      58             :         std::optional<std::pair<QcHashMap /*qcHashes*/, QcIndexedHashMap /*qcIndexedHashes*/>> {
      59      206309 :     auto quorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(pindexPrev);
      60             : 
      61      206309 :     static Mutex cs_cache;
      62      206309 :     static std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> quorums_cached GUARDED_BY(cs_cache);
      63      206309 :     static std::map<Consensus::LLMQType, Uint256LruHashMap<std::pair<uint256, int>>> qc_hashes_cached GUARDED_BY(cs_cache);
      64      206309 :     static QcHashMap qcHashes_cached GUARDED_BY(cs_cache);
      65      206309 :     static QcIndexedHashMap qcIndexedHashes_cached GUARDED_BY(cs_cache);
      66             : 
      67      206309 :     LOCK(cs_cache);
      68      206309 :     if (quorums == quorums_cached) {
      69      202533 :         return std::make_pair(qcHashes_cached, qcIndexedHashes_cached);
      70             :     }
      71             : 
      72             :     // Quorums set is different, reset cached values
      73        3776 :     quorums_cached.clear();
      74        3776 :     qcHashes_cached.clear();
      75        3776 :     qcIndexedHashes_cached.clear();
      76        3776 :     if (qc_hashes_cached.empty()) {
      77         892 :         llmq::utils::InitQuorumsCache(qc_hashes_cached, Params().GetConsensus());
      78         892 :     }
      79             : 
      80       63037 :     for (const auto& [llmqType, vecBlockIndexes] : quorums) {
      81       18880 :         const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
      82       18880 :         assert(llmq_params_opt.has_value());
      83       18880 :         bool rotation_enabled = llmq::IsQuorumRotationEnabled(llmq_params_opt.value(), pindexPrev);
      84       18880 :         auto& vec_hashes = qcHashes_cached[llmqType];
      85       37760 :         vec_hashes.reserve(vecBlockIndexes.size());
      86       18880 :         auto& map_indexed_hashes = qcIndexedHashes_cached[llmqType];
      87       30443 :         for (const auto& blockIndex : vecBlockIndexes) {
      88       11563 :             uint256 block_hash{blockIndex->GetBlockHash()};
      89             : 
      90       11563 :             std::pair<uint256, int> qc_hash;
      91       11563 :             if (!qc_hashes_cached[llmqType].get(block_hash, qc_hash)) {
      92       16762 :                 auto [pqc, dummy_hash] = quorum_block_processor.GetMinedCommitment(llmqType, block_hash);
      93        4969 :                 if (dummy_hash == uint256::ZERO) {
      94             :                     // this should never happen
      95           0 :                     return std::nullopt;
      96             :                 }
      97        4969 :                 qc_hash.first = ::SerializeHash(pqc);
      98        4969 :                 qc_hash.second = rotation_enabled ? pqc.quorumIndex : 0;
      99        4969 :                 qc_hashes_cached[llmqType].insert(block_hash, qc_hash);
     100        4969 :             }
     101       11563 :             if (rotation_enabled) {
     102        3635 :                 map_indexed_hashes[qc_hash.second] = qc_hash.first;
     103        3635 :             } else {
     104        7928 :                 vec_hashes.emplace_back(qc_hash.first);
     105             :             }
     106             :         }
     107             :     }
     108        3776 :     std::swap(quorums_cached, quorums);
     109        3776 :     return std::make_pair(qcHashes_cached, qcIndexedHashes_cached);
     110      206309 : }
     111             : 
     112      206309 : auto CalcHashCountFromQCHashes(const QcHashMap& qcHashes)
     113             : {
     114     1237854 :     return std23::ranges::fold_left(qcHashes, size_t{0}, [](size_t s, const auto& p) { return s + p.second.size(); });
     115             : }
     116             : 
     117      206309 : bool CalcCbTxMerkleRootQuorums(const CBlock& block, const CBlockIndex* pindexPrev,
     118             :                                const llmq::CQuorumBlockProcessor& quorum_block_processor, uint256& merkleRootRet,
     119             :                                BlockValidationState& state)
     120             : {
     121             :     static int64_t nTimeMined = 0;
     122             :     static int64_t nTimeLoop = 0;
     123             :     static int64_t nTimeMerkle = 0;
     124             : 
     125      206309 :     int64_t nTime1 = GetTimeMicros();
     126             : 
     127      206309 :     auto retVal = CachedGetQcHashesQcIndexedHashes(pindexPrev, quorum_block_processor);
     128      206309 :     if (!retVal) {
     129           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "commitment-not-found");
     130             :     }
     131             :     // The returned quorums are in reversed order, so the most recent one is at index 0
     132     1862600 :     auto [qcHashes, qcIndexedHashes] = retVal.value();
     133             : 
     134      206309 :     int64_t nTime2 = GetTimeMicros(); nTimeMined += nTime2 - nTime1;
     135      206309 :     LogPrint(BCLog::BENCHMARK, "            - CachedGetQcHashesQcIndexedHashes: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1), nTimeMined * 0.000001);
     136             : 
     137             :     // now add the commitments from the current block, which are not returned by GetMinedAndActiveCommitmentsUntilBlock
     138             :     // due to the use of pindexPrev (we don't have the tip index here)
     139      516392 :     for (size_t i = 1; i < block.vtx.size(); i++) {
     140      310083 :         const auto& tx = block.vtx[i];
     141             : 
     142      310083 :         if (tx->IsSpecialTxVersion() && tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
     143      294762 :             const auto opt_qc = GetTxPayload<llmq::CFinalCommitmentTxPayload>(*tx);
     144      294762 :             if (!opt_qc) {
     145           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-payload-calc-cbtx-quorummerkleroot");
     146             :             }
     147      294762 :             if (opt_qc->commitment.IsNull()) {
     148             :                 // having null commitments is ok but we don't use them here, move to the next tx
     149      288943 :                 continue;
     150             :             }
     151        5819 :             const auto& llmq_params_opt = Params().GetLLMQ(opt_qc->commitment.llmqType);
     152        5819 :             if (!llmq_params_opt.has_value()) {
     153           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-commitment-type-calc-cbtx-quorummerkleroot");
     154             :             }
     155        5819 :             const auto& llmq_params = llmq_params_opt.value();
     156        5819 :             const auto qcHash = ::SerializeHash(opt_qc->commitment);
     157        5819 :             if (llmq::IsQuorumRotationEnabled(llmq_params, pindexPrev)) {
     158        2115 :                 auto& map_indexed_hashes = qcIndexedHashes[opt_qc->commitment.llmqType];
     159        2115 :                 map_indexed_hashes[opt_qc->commitment.quorumIndex] = qcHash;
     160        2115 :             } else {
     161        3704 :                 auto& vec_hashes = qcHashes[llmq_params.type];
     162        3704 :                 if (vec_hashes.size() == size_t(llmq_params.signingActiveQuorumCount)) {
     163             :                     // we pop the last entry, which is actually the oldest quorum as GetMinedAndActiveCommitmentsUntilBlock
     164             :                     // returned quorums in reversed order. This pop and later push can only work ONCE, but we rely on the
     165             :                     // fact that a block can only contain a single commitment for one LLMQ type
     166        2193 :                     vec_hashes.pop_back();
     167        2193 :                 }
     168        3704 :                 vec_hashes.emplace_back(qcHash);
     169             :             }
     170      294762 :         }
     171       21140 :     }
     172             : 
     173     1237854 :     for (const auto& [llmqType, map_indexed_hashes] : qcIndexedHashes) {
     174     1031545 :         auto& vec_hashes = qcHashes[llmqType];
     175     1093175 :         for (const auto& [_, hash] : map_indexed_hashes) {
     176      123260 :             vec_hashes.emplace_back(hash);
     177             :         }
     178             :     }
     179             : 
     180      206309 :     std::vector<uint256> vec_hashes_final;
     181      206309 :     vec_hashes_final.reserve(CalcHashCountFromQCHashes(qcHashes));
     182             : 
     183     2269399 :     for (const auto& [llmqType, vec_hashes] : qcHashes) {
     184     1031545 :         const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     185     1031545 :         assert(llmq_params_opt.has_value());
     186     1031545 :         if (vec_hashes.size() > size_t(llmq_params_opt->signingActiveQuorumCount)) {
     187           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "excess-quorums-calc-cbtx-quorummerkleroot");
     188             :         }
     189             :         // Copy vec_hashes into vec_hashes_final
     190     2063090 :         std::copy(vec_hashes.begin(), vec_hashes.end(), std::back_inserter(vec_hashes_final));
     191             :     }
     192      206309 :     std::sort(vec_hashes_final.begin(), vec_hashes_final.end());
     193             : 
     194      206309 :     int64_t nTime3 = GetTimeMicros(); nTimeLoop += nTime3 - nTime2;
     195      206309 :     LogPrint(BCLog::BENCHMARK, "            - Loop: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeLoop * 0.000001);
     196             : 
     197      206309 :     bool mutated = false;
     198      206309 :     merkleRootRet = ComputeMerkleRoot(vec_hashes_final, &mutated);
     199             : 
     200      206309 :     int64_t nTime4 = GetTimeMicros(); nTimeMerkle += nTime4 - nTime3;
     201      206309 :     LogPrint(BCLog::BENCHMARK, "            - ComputeMerkleRoot: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3), nTimeMerkle * 0.000001);
     202             : 
     203      206309 :     if (mutated) {
     204           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "mutated-calc-cbtx-quorummerkleroot");
     205             :     }
     206             : 
     207      206309 :     return true;
     208      206309 : }
     209             : 
     210           0 : std::string CCbTx::ToString() const
     211             : {
     212           0 :     return strprintf("CCbTx(nVersion=%d, nHeight=%d, merkleRootMNList=%s, merkleRootQuorums=%s, bestCLHeightDiff=%d, bestCLSig=%s, creditPoolBalance=%d.%08d)",
     213           0 :         static_cast<uint16_t>(nVersion), nHeight, merkleRootMNList.ToString(), merkleRootQuorums.ToString(), bestCLHeightDiff, bestCLSignature.ToString(),
     214           0 :         creditPoolBalance / COIN, creditPoolBalance % COIN);
     215           0 : }
     216             : 
     217      121709 : std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex)
     218             : {
     219      121709 :     if (pindex == nullptr) {
     220           0 :         return std::nullopt;
     221             :     }
     222             : 
     223             :     // There's no CL in CbTx before v20 activation
     224      121709 :     if (!DeploymentActiveAt(*pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) {
     225         394 :         return std::nullopt;
     226             :     }
     227             : 
     228      121315 :     CBlock block;
     229      121315 :     if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
     230           0 :         return std::nullopt;
     231             :     }
     232             : 
     233      121309 :     const CTransactionRef cbTx = block.vtx[0];
     234      121309 :     const auto opt_cbtx = GetTxPayload<CCbTx>(*cbTx);
     235             : 
     236      121309 :     if (!opt_cbtx.has_value()) {
     237           0 :         return std::nullopt;
     238             :     }
     239             : 
     240      121309 :     if (!opt_cbtx->bestCLSignature.IsValid()) {
     241      105991 :         return std::nullopt;
     242             :     }
     243             : 
     244       15318 :     return std::make_pair(opt_cbtx->bestCLSignature, opt_cbtx->bestCLHeightDiff);
     245      121709 : }

Generated by: LCOV version 1.16