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/smldiff.h>
6 :
7 : #include <evo/deterministicmns.h>
8 : #include <evo/specialtx.h>
9 : #include <llmq/blockprocessor.h>
10 : #include <llmq/commitment.h>
11 : #include <llmq/quorumsman.h>
12 : #include <util/std23.h>
13 :
14 : #include <chainparams.h>
15 : #include <consensus/merkle.h>
16 : #include <core_io.h>
17 : #include <deploymentstatus.h>
18 : #include <node/blockstorage.h>
19 : #include <serialize.h>
20 : #include <univalue.h>
21 : #include <validation.h>
22 :
23 : using node::ReadBlockFromDisk;
24 :
25 : // Forward declaration
26 : std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
27 :
28 104 : CSimplifiedMNListDiff::CSimplifiedMNListDiff() = default;
29 :
30 148 : CSimplifiedMNListDiff::~CSimplifiedMNListDiff() = default;
31 :
32 0 : bool CSimplifiedMNListDiff::BuildQuorumsDiff(const CBlockIndex* baseBlockIndex, const CBlockIndex* blockIndex,
33 : const llmq::CQuorumBlockProcessor& quorum_block_processor)
34 : {
35 0 : auto baseQuorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(baseBlockIndex);
36 0 : auto quorums = quorum_block_processor.GetMinedAndActiveCommitmentsUntilBlock(blockIndex);
37 :
38 0 : std::set<std::pair<Consensus::LLMQType, uint256>> baseQuorumHashes;
39 0 : std::set<std::pair<Consensus::LLMQType, uint256>> quorumHashes;
40 0 : for (const auto& [llmqType, vecBlockIndex] : baseQuorums) {
41 0 : for (const auto& blockindex : vecBlockIndex) {
42 0 : baseQuorumHashes.emplace(llmqType, blockindex->GetBlockHash());
43 : }
44 : }
45 0 : for (const auto& [llmqType, vecBlockIndex] : quorums) {
46 0 : for (const auto& blockindex : vecBlockIndex) {
47 0 : quorumHashes.emplace(llmqType, blockindex->GetBlockHash());
48 : }
49 : }
50 :
51 0 : for (const auto& p : baseQuorumHashes) {
52 0 : if (!quorumHashes.count(p)) {
53 0 : deletedQuorums.emplace_back((uint8_t)p.first, p.second);
54 0 : }
55 : }
56 0 : for (const auto& p : quorumHashes) {
57 0 : const auto& [llmqType, hash] = p;
58 0 : if (!baseQuorumHashes.count(p)) {
59 0 : auto [qc, minedBlockHash] = quorum_block_processor.GetMinedCommitment(llmqType, hash);
60 0 : if (minedBlockHash == uint256::ZERO) {
61 0 : return false;
62 : }
63 0 : newQuorums.emplace_back(std::move(qc));
64 0 : }
65 : }
66 :
67 0 : return true;
68 0 : }
69 :
70 0 : bool CSimplifiedMNListDiff::BuildQuorumChainlockInfo(const llmq::CQuorumManager& qman, const CBlockIndex* blockIndex)
71 : {
72 : // Group quorums (indexes corresponding to entries of newQuorums) per CBlockIndex containing the expected CL
73 : // signature in CbTx. We want to avoid to load CbTx now, as more than one quorum will target the same block: hence
74 : // we want to load CbTxs once per block (heavy operation).
75 0 : std::multimap<const CBlockIndex*, uint16_t> workBaseBlockIndexMap;
76 :
77 0 : for (const auto [idx, e] : std23::views::enumerate(newQuorums)) {
78 : // We assume that we have on hand, quorums that correspond to the hashes queried.
79 : // If we cannot find them, something must have gone wrong and we should cease trying
80 : // to build any further.
81 0 : auto quorum = qman.GetQuorum(e.llmqType, e.quorumHash);
82 0 : if (!quorum) {
83 0 : LogPrintf("%s: ERROR! Unexpected missing quorum with llmqType=%d, quorumHash=%s\n", __func__,
84 : std23::to_underlying(e.llmqType), e.quorumHash.ToString());
85 0 : return false;
86 : }
87 :
88 : // In case of rotation, all rotated quorums rely on the CL sig expected in the cycleBlock (the block of the
89 : // first DKG) - 8 In case of non-rotation, quorums rely on the CL sig expected in the block of the DKG - 8
90 0 : const CBlockIndex* pWorkBaseBlockIndex = blockIndex->GetAncestor(quorum->m_quorum_base_block_index->nHeight -
91 0 : quorum->qc->quorumIndex - 8);
92 :
93 0 : workBaseBlockIndexMap.insert(std::make_pair(pWorkBaseBlockIndex, idx));
94 0 : }
95 :
96 0 : for (auto it = workBaseBlockIndexMap.begin(); it != workBaseBlockIndexMap.end();) {
97 : // Process each key (CBlockIndex containing the expected CL signature in CbTx) of the std::multimap once
98 0 : const CBlockIndex* pWorkBaseBlockIndex = it->first;
99 0 : const auto cbcl = GetNonNullCoinbaseChainlock(pWorkBaseBlockIndex);
100 0 : CBLSSignature sig;
101 0 : if (cbcl.has_value()) {
102 0 : sig = cbcl.value().first;
103 0 : }
104 : // Get the range of indexes (values) for the current key and merge them into a single std::set
105 0 : const auto [it_begin, it_end] = workBaseBlockIndexMap.equal_range(it->first);
106 0 : std::set<uint16_t> idx_set;
107 0 : std::transform(it_begin, it_end, std::inserter(idx_set, idx_set.end()),
108 0 : [](const auto& pair) { return pair.second; });
109 : // Advance the iterator to the next key
110 0 : it = it_end;
111 :
112 : // Different CBlockIndex can contain the same CL sig in CbTx (both non-null or null during the first blocks after v20 activation)
113 : // Hence, we need to merge the std::set if another std::set already exists for the same sig.
114 0 : if (auto [it_sig, inserted] = quorumsCLSigs.insert({sig, idx_set}); !inserted) {
115 0 : it_sig->second.insert(idx_set.begin(), idx_set.end());
116 0 : }
117 0 : }
118 :
119 0 : return true;
120 0 : }
121 :
122 0 : CSimplifiedMNListDiff BuildSimplifiedDiff(const CDeterministicMNList& from, const CDeterministicMNList& to, bool extended)
123 : {
124 0 : CSimplifiedMNListDiff diffRet;
125 0 : diffRet.baseBlockHash = from.GetBlockHash();
126 0 : diffRet.blockHash = to.GetBlockHash();
127 :
128 0 : to.ForEachMN(/*onlyValid=*/false, [&](const auto& toPtr) {
129 0 : auto fromPtr = from.GetMN(toPtr.proTxHash);
130 0 : if (fromPtr == nullptr) {
131 0 : CSimplifiedMNListEntry sme{toPtr.to_sml_entry()};
132 0 : diffRet.mnList.push_back(std::move(sme));
133 0 : } else {
134 0 : CSimplifiedMNListEntry sme1{toPtr.to_sml_entry()};
135 0 : CSimplifiedMNListEntry sme2(fromPtr->to_sml_entry());
136 0 : if ((sme1 != sme2) || (extended && (sme1.scriptPayout != sme2.scriptPayout ||
137 0 : sme1.scriptOperatorPayout != sme2.scriptOperatorPayout))) {
138 0 : diffRet.mnList.push_back(std::move(sme1));
139 0 : }
140 0 : }
141 0 : });
142 :
143 0 : from.ForEachMN(/*onlyValid=*/false, [&](const auto& fromPtr) {
144 0 : auto toPtr = to.GetMN(fromPtr.proTxHash);
145 0 : if (toPtr == nullptr) {
146 0 : diffRet.deletedMNs.emplace_back(fromPtr.proTxHash);
147 0 : }
148 0 : });
149 :
150 0 : return diffRet;
151 0 : }
152 :
153 0 : bool BuildSimplifiedMNListDiff(CDeterministicMNManager& dmnman, const ChainstateManager& chainman,
154 : const llmq::CQuorumBlockProcessor& qblockman, const llmq::CQuorumManager& qman,
155 : const uint256& baseBlockHash, const uint256& blockHash,
156 : CSimplifiedMNListDiff& mnListDiffRet, std::string& errorRet, bool extended)
157 : {
158 0 : AssertLockHeld(::cs_main);
159 0 : mnListDiffRet = CSimplifiedMNListDiff();
160 :
161 0 : const CBlockIndex* baseBlockIndex = chainman.ActiveChain().Genesis();
162 0 : if (!baseBlockHash.IsNull()) {
163 0 : baseBlockIndex = chainman.m_blockman.LookupBlockIndex(baseBlockHash);
164 0 : if (!baseBlockIndex) {
165 0 : errorRet = strprintf("block %s not found", baseBlockHash.ToString());
166 0 : return false;
167 : }
168 0 : }
169 :
170 0 : const CBlockIndex* blockIndex = chainman.m_blockman.LookupBlockIndex(blockHash);
171 0 : if (!blockIndex) {
172 0 : errorRet = strprintf("block %s not found", blockHash.ToString());
173 0 : return false;
174 : }
175 :
176 0 : if (!chainman.ActiveChain().Contains(baseBlockIndex) || !chainman.ActiveChain().Contains(blockIndex)) {
177 0 : errorRet = strprintf("block %s and %s are not in the same chain", baseBlockHash.ToString(), blockHash.ToString());
178 0 : return false;
179 : }
180 0 : if (baseBlockIndex->nHeight > blockIndex->nHeight) {
181 0 : errorRet = strprintf("base block %s is higher then block %s", baseBlockHash.ToString(), blockHash.ToString());
182 0 : return false;
183 : }
184 :
185 0 : auto baseDmnList = dmnman.GetListForBlock(baseBlockIndex);
186 0 : auto dmnList = dmnman.GetListForBlock(blockIndex);
187 0 : mnListDiffRet = BuildSimplifiedDiff(baseDmnList, dmnList, extended);
188 :
189 : // We need to return the value that was provided by the other peer as it otherwise won't be able to recognize the
190 : // response. This will usually be identical to the block found in baseBlockIndex. The only difference is when a
191 : // null block hash was provided to get the diff from the genesis block.
192 0 : mnListDiffRet.baseBlockHash = baseBlockHash;
193 :
194 0 : if (!mnListDiffRet.BuildQuorumsDiff(baseBlockIndex, blockIndex, qblockman)) {
195 0 : errorRet = strprintf("failed to build quorums diff");
196 0 : return false;
197 : }
198 :
199 0 : if (DeploymentActiveAfter(blockIndex, chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) {
200 0 : if (!mnListDiffRet.BuildQuorumChainlockInfo(qman, blockIndex)) {
201 0 : errorRet = strprintf("failed to build quorum chainlock info");
202 0 : return false;
203 : }
204 0 : }
205 :
206 : // TODO store coinbase TX in CBlockIndex
207 0 : CBlock block;
208 0 : if (!ReadBlockFromDisk(block, blockIndex, chainman.GetConsensus())) {
209 0 : errorRet = strprintf("failed to read block %s from disk", blockHash.ToString());
210 0 : return false;
211 : }
212 :
213 0 : mnListDiffRet.cbTx = CMutableTransaction(*block.vtx[0]);
214 :
215 0 : std::vector<uint256> vHashes;
216 0 : std::vector<bool> vMatch(block.vtx.size(), false);
217 0 : for (const auto& tx : block.vtx) {
218 0 : vHashes.emplace_back(tx->GetHash());
219 : }
220 0 : vMatch[0] = true; // only coinbase matches
221 0 : mnListDiffRet.cbTxMerkleTree = CPartialMerkleTree(vHashes, vMatch);
222 :
223 0 : return true;
224 0 : }
|