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 : }
|