LCOV - code coverage report
Current view: top level - src/evo - cbtx.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 90 134 67.2 %
Date: 2026-06-25 07:23:51 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       24300 : bool CheckCbTx(const CCbTx& cbTx, const CBlockIndex* pindexPrev, TxValidationState& state)
      25             : {
      26       24300 :     if (cbTx.nVersion == CCbTx::Version::INVALID || cbTx.nVersion >= CCbTx::Version::UNKNOWN) {
      27       12128 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
      28             :     }
      29             : 
      30       24300 :     if (pindexPrev) {
      31       24300 :         if (pindexPrev->nHeight + 1 != cbTx.nHeight) {
      32           0 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-height");
      33             :         }
      34             : 
      35       24300 :         const bool fDIP0008Active{DeploymentActiveAt(*pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_DIP0008)};
      36       24300 :         if (fDIP0008Active && cbTx.nVersion < CCbTx::Version::MERKLE_ROOT_QUORUMS) {
      37           0 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
      38             :         }
      39             : 
      40       24300 :         const bool isV20{DeploymentActiveAfter(pindexPrev, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)};
      41       24300 :         if ((isV20 && cbTx.nVersion < CCbTx::Version::CLSIG_AND_BALANCE) || (!isV20 && cbTx.nVersion >= CCbTx::Version::CLSIG_AND_BALANCE)) {
      42        6064 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-version");
      43             :         }
      44       18236 :     }
      45             : 
      46       18236 :     return true;
      47       18236 : }
      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       36470 : auto CachedGetQcHashesQcIndexedHashes(const CBlockIndex* pindexPrev, const llmq::CQuorumBlockProcessor& quorum_block_processor) ->
      58             :         std::optional<std::pair<QcHashMap /*qcHashes*/, QcIndexedHashMap /*qcIndexedHashes*/>> {
      59       36470 :     auto quorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(pindexPrev);
      60             : 
      61       36470 :     static Mutex cs_cache;
      62       36470 :     static std::map<Consensus::LLMQType, std::vector<const CBlockIndex*>> quorums_cached GUARDED_BY(cs_cache);
      63       36470 :     static std::map<Consensus::LLMQType, Uint256LruHashMap<std::pair<uint256, int>>> qc_hashes_cached GUARDED_BY(cs_cache);
      64       36470 :     static QcHashMap qcHashes_cached GUARDED_BY(cs_cache);
      65       36470 :     static QcIndexedHashMap qcIndexedHashes_cached GUARDED_BY(cs_cache);
      66             : 
      67       36470 :     LOCK(cs_cache);
      68       36470 :     if (quorums == quorums_cached) {
      69       36467 :         return std::make_pair(qcHashes_cached, qcIndexedHashes_cached);
      70             :     }
      71             : 
      72             :     // Quorums set is different, reset cached values
      73           3 :     quorums_cached.clear();
      74           3 :     qcHashes_cached.clear();
      75           3 :     qcIndexedHashes_cached.clear();
      76           3 :     if (qc_hashes_cached.empty()) {
      77           3 :         llmq::utils::InitQuorumsCache(qc_hashes_cached, Params().GetConsensus());
      78           3 :     }
      79             : 
      80          33 :     for (const auto& [llmqType, vecBlockIndexes] : quorums) {
      81          15 :         const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
      82          15 :         assert(llmq_params_opt.has_value());
      83          15 :         bool rotation_enabled = llmq::IsQuorumRotationEnabled(llmq_params_opt.value(), pindexPrev);
      84          15 :         auto& vec_hashes = qcHashes_cached[llmqType];
      85          30 :         vec_hashes.reserve(vecBlockIndexes.size());
      86          15 :         auto& map_indexed_hashes = qcIndexedHashes_cached[llmqType];
      87          15 :         for (const auto& blockIndex : vecBlockIndexes) {
      88           0 :             uint256 block_hash{blockIndex->GetBlockHash()};
      89             : 
      90           0 :             std::pair<uint256, int> qc_hash;
      91           0 :             if (!qc_hashes_cached[llmqType].get(block_hash, qc_hash)) {
      92           0 :                 auto [pqc, dummy_hash] = quorum_block_processor.GetMinedCommitment(llmqType, block_hash);
      93           0 :                 if (dummy_hash == uint256::ZERO) {
      94             :                     // this should never happen
      95           0 :                     return std::nullopt;
      96             :                 }
      97           0 :                 qc_hash.first = ::SerializeHash(pqc);
      98           0 :                 qc_hash.second = rotation_enabled ? pqc.quorumIndex : 0;
      99           0 :                 qc_hashes_cached[llmqType].insert(block_hash, qc_hash);
     100           0 :             }
     101           0 :             if (rotation_enabled) {
     102           0 :                 map_indexed_hashes[qc_hash.second] = qc_hash.first;
     103           0 :             } else {
     104           0 :                 vec_hashes.emplace_back(qc_hash.first);
     105             :             }
     106             :         }
     107             :     }
     108           3 :     std::swap(quorums_cached, quorums);
     109           3 :     return std::make_pair(qcHashes_cached, qcIndexedHashes_cached);
     110       36470 : }
     111             : 
     112       36470 : auto CalcHashCountFromQCHashes(const QcHashMap& qcHashes)
     113             : {
     114      218820 :     return std23::ranges::fold_left(qcHashes, size_t{0}, [](size_t s, const auto& p) { return s + p.second.size(); });
     115             : }
     116             : 
     117       36470 : 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       36470 :     int64_t nTime1 = GetTimeMicros();
     126             : 
     127       36470 :     auto retVal = CachedGetQcHashesQcIndexedHashes(pindexPrev, quorum_block_processor);
     128       36470 :     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      328230 :     auto [qcHashes, qcIndexedHashes] = retVal.value();
     133             : 
     134       36470 :     int64_t nTime2 = GetTimeMicros(); nTimeMined += nTime2 - nTime1;
     135       36470 :     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       94668 :     for (size_t i = 1; i < block.vtx.size(); i++) {
     140       58198 :         const auto& tx = block.vtx[i];
     141             : 
     142       58198 :         if (tx->IsSpecialTxVersion() && tx->nType == TRANSACTION_QUORUM_COMMITMENT) {
     143       58100 :             const auto opt_qc = GetTxPayload<llmq::CFinalCommitmentTxPayload>(*tx);
     144       58100 :             if (!opt_qc) {
     145           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-payload-calc-cbtx-quorummerkleroot");
     146             :             }
     147       58100 :             if (opt_qc->commitment.IsNull()) {
     148             :                 // having null commitments is ok but we don't use them here, move to the next tx
     149       58100 :                 continue;
     150             :             }
     151           0 :             const auto& llmq_params_opt = Params().GetLLMQ(opt_qc->commitment.llmqType);
     152           0 :             if (!llmq_params_opt.has_value()) {
     153           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-commitment-type-calc-cbtx-quorummerkleroot");
     154             :             }
     155           0 :             const auto& llmq_params = llmq_params_opt.value();
     156           0 :             const auto qcHash = ::SerializeHash(opt_qc->commitment);
     157           0 :             if (llmq::IsQuorumRotationEnabled(llmq_params, pindexPrev)) {
     158           0 :                 auto& map_indexed_hashes = qcIndexedHashes[opt_qc->commitment.llmqType];
     159           0 :                 map_indexed_hashes[opt_qc->commitment.quorumIndex] = qcHash;
     160           0 :             } else {
     161           0 :                 auto& vec_hashes = qcHashes[llmq_params.type];
     162           0 :                 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           0 :                     vec_hashes.pop_back();
     167           0 :                 }
     168           0 :                 vec_hashes.emplace_back(qcHash);
     169             :             }
     170       58100 :         }
     171          98 :     }
     172             : 
     173      218820 :     for (const auto& [llmqType, map_indexed_hashes] : qcIndexedHashes) {
     174      182350 :         auto& vec_hashes = qcHashes[llmqType];
     175      182350 :         for (const auto& [_, hash] : map_indexed_hashes) {
     176           0 :             vec_hashes.emplace_back(hash);
     177             :         }
     178             :     }
     179             : 
     180       36470 :     std::vector<uint256> vec_hashes_final;
     181       36470 :     vec_hashes_final.reserve(CalcHashCountFromQCHashes(qcHashes));
     182             : 
     183      401170 :     for (const auto& [llmqType, vec_hashes] : qcHashes) {
     184      182350 :         const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     185      182350 :         assert(llmq_params_opt.has_value());
     186      182350 :         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      364700 :         std::copy(vec_hashes.begin(), vec_hashes.end(), std::back_inserter(vec_hashes_final));
     191             :     }
     192       36470 :     std::sort(vec_hashes_final.begin(), vec_hashes_final.end());
     193             : 
     194       36470 :     int64_t nTime3 = GetTimeMicros(); nTimeLoop += nTime3 - nTime2;
     195       36470 :     LogPrint(BCLog::BENCHMARK, "            - Loop: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeLoop * 0.000001);
     196             : 
     197       36470 :     bool mutated = false;
     198       36470 :     merkleRootRet = ComputeMerkleRoot(vec_hashes_final, &mutated);
     199             : 
     200       36470 :     int64_t nTime4 = GetTimeMicros(); nTimeMerkle += nTime4 - nTime3;
     201       36470 :     LogPrint(BCLog::BENCHMARK, "            - ComputeMerkleRoot: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3), nTimeMerkle * 0.000001);
     202             : 
     203       36470 :     if (mutated) {
     204           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "mutated-calc-cbtx-quorummerkleroot");
     205             :     }
     206             : 
     207       36470 :     return true;
     208       36470 : }
     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       23839 : std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex)
     218             : {
     219       23839 :     if (pindex == nullptr) {
     220           0 :         return std::nullopt;
     221             :     }
     222             : 
     223             :     // There's no CL in CbTx before v20 activation
     224       23839 :     if (!DeploymentActiveAt(*pindex, Params().GetConsensus(), Consensus::DEPLOYMENT_V20)) {
     225          57 :         return std::nullopt;
     226             :     }
     227             : 
     228       23782 :     CBlock block;
     229       23782 :     if (!ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
     230           0 :         return std::nullopt;
     231             :     }
     232             : 
     233       23782 :     const CTransactionRef cbTx = block.vtx[0];
     234       23782 :     const auto opt_cbtx = GetTxPayload<CCbTx>(*cbTx);
     235             : 
     236       23782 :     if (!opt_cbtx.has_value()) {
     237           0 :         return std::nullopt;
     238             :     }
     239             : 
     240       23782 :     if (!opt_cbtx->bestCLSignature.IsValid()) {
     241       23782 :         return std::nullopt;
     242             :     }
     243             : 
     244           0 :     return std::make_pair(opt_cbtx->bestCLSignature, opt_cbtx->bestCLHeightDiff);
     245       23839 : }

Generated by: LCOV version 1.16