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