Line data Source code
1 : // Copyright (c) 2018-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 <llmq/utils.h>
6 :
7 : #include <bls/bls.h>
8 : #include <evo/deterministicmns.h>
9 : #include <llmq/options.h>
10 : #include <llmq/snapshot.h>
11 : #include <llmq/types.h>
12 : #include <masternode/meta.h>
13 : #include <util/helpers.h>
14 : #include <util/std23.h>
15 :
16 : #include <chainparams.h>
17 : #include <deploymentstatus.h>
18 : #include <random.h>
19 : #include <util/time.h>
20 : #include <validation.h>
21 :
22 : #include <atomic>
23 : #include <map>
24 : #include <optional>
25 :
26 : /**
27 : * Forward declarations
28 : */
29 : std::optional<std::pair<CBLSSignature, uint32_t>> GetNonNullCoinbaseChainlock(const CBlockIndex* pindex);
30 :
31 : namespace {
32 : using QuorumMembers = std::vector<CDeterministicMNCPtr>;
33 :
34 91933 : std::string ToString(const QuorumMembers& members)
35 : {
36 91933 : std::stringstream ss;
37 241153 : for (const auto& mn : members) {
38 149220 : ss << mn->proTxHash.ToString().substr(0, 4) << "|";
39 : }
40 91933 : return ss.str();
41 91933 : }
42 :
43 : struct MasternodeScore {
44 : arith_uint256 m_score;
45 : CDeterministicMNCPtr m_node;
46 : };
47 :
48 : struct QuorumQuarter : public llmq::CycleBase {
49 : std::vector<QuorumMembers> m_members;
50 :
51 : public:
52 45450 : explicit QuorumQuarter(size_t size) : m_members(size) {}
53 : };
54 :
55 : // QuorumMembers per quorumIndex at heights H-Cycle, H-2Cycles, H-3Cycles
56 : struct PreviousQuorumQuarters {
57 : QuorumQuarter quarterHMinusC;
58 : QuorumQuarter quarterHMinus2C;
59 : QuorumQuarter quarterHMinus3C;
60 :
61 : public:
62 15150 : explicit PreviousQuorumQuarters(size_t s) : quarterHMinusC(s), quarterHMinus2C(s), quarterHMinus3C(s) {}
63 :
64 7575 : std::vector<QuorumQuarter*> GetCycles() { return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C}; }
65 9718 : std::vector<const QuorumQuarter*> GetCycles() const
66 : {
67 9718 : return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C};
68 : }
69 : };
70 :
71 143162 : arith_uint256 calculateQuorumScore(const CDeterministicMNCPtr& dmn, const uint256& modifier)
72 : {
73 : // calculate sha256(sha256(proTxHash, confirmedHash), modifier) per MN
74 : // Please note that this is not a double-sha256 but a single-sha256
75 : // The first part is already precalculated (confirmedHashWithProRegTxHash)
76 : // TODO When https://github.com/bitcoin/bitcoin/pull/13191 gets backported, implement something that is similar but for single-sha256
77 143162 : uint256 h;
78 143162 : CSHA256 sha256;
79 286324 : sha256.Write(dmn->pdmnState->confirmedHashWithProRegTxHash.begin(),
80 143162 : dmn->pdmnState->confirmedHashWithProRegTxHash.size());
81 143162 : sha256.Write(modifier.begin(), modifier.size());
82 143162 : sha256.Finalize(h.begin());
83 143162 : return UintToArith256(h);
84 : }
85 :
86 29567 : uint256 GetHashModifier(const Consensus::LLMQParams& llmqParams, const Consensus::Params& consensus_params,
87 : gsl::not_null<const CBlockIndex*> pCycleQuorumBaseBlockIndex)
88 : {
89 : ASSERT_IF_DEBUG(pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval == 0);
90 29567 : const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
91 :
92 29567 : if (DeploymentActiveAfter(pWorkBlockIndex, consensus_params, Consensus::DEPLOYMENT_V20)) {
93 : // v20 is active: calculate modifier using the new way.
94 22300 : auto cbcl = GetNonNullCoinbaseChainlock(pWorkBlockIndex);
95 22300 : if (cbcl.has_value()) {
96 : // We have a non-null CL signature: calculate modifier using this CL signature
97 9136 : auto& [bestCLSignature, bestCLHeightDiff] = cbcl.value();
98 18260 : return ::SerializeHash(std::make_tuple(llmqParams.type, pWorkBlockIndex->nHeight, bestCLSignature));
99 : }
100 : // No non-null CL signature found in coinbase: calculate modifier using block hash only
101 13164 : return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
102 22294 : }
103 :
104 : // v20 isn't active yet: calculate modifier using the usual way
105 7267 : if (llmqParams.useRotation) {
106 6329 : return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
107 : }
108 938 : return ::SerializeHash(std::make_pair(llmqParams.type, pCycleQuorumBaseBlockIndex->GetBlockHash()));
109 29573 : }
110 :
111 4859 : std::vector<MasternodeScore> CalculateScoresForQuorum(QuorumMembers&& dmns, const uint256& modifier, const bool onlyEvoNodes)
112 : {
113 4859 : std::vector<MasternodeScore> scores;
114 4859 : scores.reserve(dmns.size());
115 :
116 17096 : for (auto& dmn : dmns) {
117 12237 : if (dmn->pdmnState->IsBanned()) continue;
118 12237 : if (dmn->pdmnState->confirmedHash.IsNull()) {
119 : // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a
120 : // future quorums
121 486 : continue;
122 : }
123 11751 : if (onlyEvoNodes && dmn->nType != MnType::Evo) {
124 0 : continue;
125 : }
126 11751 : scores.emplace_back(calculateQuorumScore(dmn, modifier), std::move(dmn));
127 : };
128 4859 : return scores;
129 4859 : }
130 :
131 26847 : std::vector<MasternodeScore> CalculateScoresForQuorum(const CDeterministicMNList& mn_list, const uint256& modifier,
132 : const bool onlyEvoNodes)
133 : {
134 26847 : std::vector<MasternodeScore> scores;
135 26847 : scores.reserve(mn_list.GetCounts().total());
136 :
137 172945 : mn_list.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
138 146100 : if (dmn->pdmnState->confirmedHash.IsNull()) {
139 : // we only take confirmed MNs into account to avoid hash grinding on the ProRegTxHash to sneak MNs into a
140 : // future quorums
141 2206 : return;
142 : }
143 143894 : if (onlyEvoNodes && dmn->nType != MnType::Evo) {
144 12483 : return;
145 : }
146 131411 : scores.emplace_back(calculateQuorumScore(dmn, modifier), dmn);
147 146100 : });
148 26845 : return scores;
149 26851 : }
150 :
151 : /**
152 : * Calculate a quorum based on the modifier. The resulting list is deterministically sorted by score
153 : */
154 : template <typename List>
155 31704 : QuorumMembers CalculateQuorum(List&& mn_list, const uint256& modifier, size_t maxSize = 0, const bool onlyEvoNodes = false)
156 : {
157 31704 : auto scores = CalculateScoresForQuorum(std::forward<List>(mn_list), modifier, onlyEvoNodes);
158 :
159 : // sort is descending order
160 347160 : std::sort(scores.rbegin(), scores.rend(), [](const MasternodeScore& a, const MasternodeScore& b) {
161 315456 : if (a.m_score == b.m_score) {
162 : // this should actually never happen, but we should stay compatible with how the non-deterministic MNs did the sorting
163 : // TODO - add assert ?
164 0 : return a.m_node->collateralOutpoint < b.m_node->collateralOutpoint;
165 : }
166 315456 : return a.m_score < b.m_score;
167 315456 : });
168 :
169 : // return top maxSize entries only (if specified)
170 31704 : if (maxSize > 0 && scores.size() > maxSize) {
171 2708 : scores.resize(maxSize);
172 2708 : }
173 :
174 31704 : QuorumMembers result;
175 31704 : result.reserve(scores.size());
176 164145 : for (auto& [_, node] : scores) {
177 132441 : result.emplace_back(std::move(node));
178 : }
179 31704 : return result;
180 31704 : }
181 :
182 11324 : std::vector<QuorumMembers> GetQuorumQuarterMembersBySnapshot(const Consensus::LLMQParams& llmqParams,
183 : CDeterministicMNManager& dmnman,
184 : const Consensus::Params& consensus_params,
185 : const CBlockIndex* pCycleQuorumBaseBlockIndex,
186 : const llmq::CQuorumSnapshot& snapshot, int nHeight)
187 : {
188 11324 : if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
189 : ASSERT_IF_DEBUG(false);
190 0 : return {};
191 : }
192 :
193 11324 : std::vector<CDeterministicMNCPtr> sortedCombinedMns;
194 : {
195 22648 : const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(
196 11324 : pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
197 11324 : auto mn_list = dmnman.GetListForBlock(pWorkBlockIndex);
198 11324 : const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
199 11324 : auto sortedAllMns = CalculateQuorum(mn_list, modifier);
200 :
201 11324 : std::vector<CDeterministicMNCPtr> usedMNs;
202 11324 : size_t i{0};
203 73402 : for (const auto& dmn : sortedAllMns) {
204 62078 : if (snapshot.activeQuorumMembers[i]) {
205 33999 : usedMNs.push_back(dmn);
206 33999 : } else {
207 28079 : if (!dmn->pdmnState->IsBanned()) {
208 : // the list begins with all the unused MNs
209 28079 : sortedCombinedMns.push_back(dmn);
210 28079 : }
211 : }
212 62078 : i++;
213 : }
214 :
215 : // Now add the already used MNs to the end of the list
216 11324 : std::move(usedMNs.begin(), usedMNs.end(), std::back_inserter(sortedCombinedMns));
217 11324 : }
218 :
219 11324 : if (LogAcceptDebug(BCLog::LLMQ)) {
220 11324 : LogPrint(BCLog::LLMQ, "%s h[%d] from[%d] sortedCombinedMns[%s]\n", __func__,
221 : pCycleQuorumBaseBlockIndex->nHeight, nHeight, ToString(sortedCombinedMns));
222 11324 : }
223 :
224 11324 : size_t numQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
225 11324 : size_t quorumSize = static_cast<size_t>(llmqParams.size);
226 11324 : auto quarterSize{quorumSize / 4};
227 :
228 11324 : std::vector<QuorumMembers> quarterQuorumMembers(numQuorums);
229 :
230 11324 : if (sortedCombinedMns.empty()) {
231 478 : return quarterQuorumMembers;
232 : }
233 :
234 10846 : switch (snapshot.mnSkipListMode) {
235 : case SnapshotSkipMode::MODE_NO_SKIPPING: {
236 8607 : auto itm = sortedCombinedMns.begin();
237 25821 : for (const size_t i : util::irange(numQuorums)) {
238 34428 : while (quarterQuorumMembers[i].size() < quarterSize) {
239 17214 : quarterQuorumMembers[i].push_back(*itm);
240 17214 : itm++;
241 17214 : if (itm == sortedCombinedMns.end()) {
242 2615 : itm = sortedCombinedMns.begin();
243 2615 : }
244 : }
245 : }
246 8607 : return quarterQuorumMembers;
247 : }
248 : case SnapshotSkipMode::MODE_SKIPPING_ENTRIES: // List holds entries to be skipped
249 : {
250 2239 : size_t first_entry_index{0};
251 2239 : std::vector<int> processesdSkipList;
252 6778 : for (const auto& s : snapshot.mnSkipList) {
253 4539 : if (first_entry_index == 0) {
254 3208 : first_entry_index = s;
255 3208 : processesdSkipList.push_back(s);
256 3208 : } else {
257 1331 : processesdSkipList.push_back(first_entry_index + s);
258 : }
259 : }
260 :
261 2239 : int idx = 0;
262 2239 : auto itsk = processesdSkipList.begin();
263 6717 : for (const size_t i : util::irange(numQuorums)) {
264 13495 : while (quarterQuorumMembers[i].size() < quarterSize) {
265 9017 : if (itsk != processesdSkipList.end() && idx == *itsk) {
266 4539 : itsk++;
267 4539 : } else {
268 4478 : quarterQuorumMembers[i].push_back(sortedCombinedMns[idx]);
269 : }
270 9017 : idx++;
271 9017 : if (idx == static_cast<int>(sortedCombinedMns.size())) {
272 1026 : idx = 0;
273 1026 : }
274 : }
275 : }
276 2239 : return quarterQuorumMembers;
277 2239 : }
278 : case SnapshotSkipMode::MODE_NO_SKIPPING_ENTRIES: // List holds entries to be kept
279 0 : case SnapshotSkipMode::MODE_ALL_SKIPPED: // Every node was skipped. Returning empty quarterQuorumMembers
280 : default:
281 0 : return quarterQuorumMembers;
282 : }
283 11324 : }
284 :
285 5803 : QuorumMembers ComputeQuorumMembers(Consensus::LLMQType llmqType, const CChainParams& chainparams,
286 : const CDeterministicMNList& mn_list, const CBlockIndex* pQuorumBaseBlockIndex)
287 : {
288 7773 : bool EvoOnly = (chainparams.GetConsensus().llmqTypePlatform == llmqType) &&
289 1970 : DeploymentActiveAfter(pQuorumBaseBlockIndex, chainparams.GetConsensus(), Consensus::DEPLOYMENT_V19);
290 5803 : const auto& llmq_params_opt = chainparams.GetLLMQ(llmqType);
291 5803 : assert(llmq_params_opt.has_value());
292 5803 : if (llmq_params_opt->useRotation || pQuorumBaseBlockIndex->nHeight % llmq_params_opt->dkgInterval != 0) {
293 : ASSERT_IF_DEBUG(false);
294 0 : return {};
295 : }
296 :
297 5803 : const auto modifier = GetHashModifier(llmq_params_opt.value(), chainparams.GetConsensus(), pQuorumBaseBlockIndex);
298 5803 : return CalculateQuorum(mn_list, modifier, llmq_params_opt->size, EvoOnly);
299 5803 : }
300 :
301 4859 : void BuildQuorumSnapshot(const Consensus::LLMQParams& llmqParams, const Consensus::Params& consensus_params,
302 : const CDeterministicMNList& allMns, const CDeterministicMNList& mnUsedAtH,
303 : std::vector<CDeterministicMNCPtr>& sortedCombinedMns, llmq::CQuorumSnapshot& quorumSnapshot,
304 : std::vector<int>& skipList, const CBlockIndex* pCycleQuorumBaseBlockIndex)
305 : {
306 4859 : if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
307 : ASSERT_IF_DEBUG(false);
308 0 : return;
309 : }
310 :
311 4859 : const auto allMnsTotal = allMns.GetCounts().total();
312 4859 : quorumSnapshot.activeQuorumMembers.resize(allMnsTotal);
313 4859 : const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
314 4859 : auto sortedAllMns = CalculateQuorum(allMns, modifier);
315 :
316 4859 : LogPrint(BCLog::LLMQ, "BuildQuorumSnapshot h[%d] numMns[%d]\n", pCycleQuorumBaseBlockIndex->nHeight,
317 : allMnsTotal);
318 :
319 4859 : std::fill(quorumSnapshot.activeQuorumMembers.begin(), quorumSnapshot.activeQuorumMembers.end(), false);
320 4859 : size_t index = {};
321 32819 : for (const auto& dmn : sortedAllMns) {
322 27960 : if (mnUsedAtH.HasMN(dmn->proTxHash)) {
323 16209 : quorumSnapshot.activeQuorumMembers[index] = true;
324 16209 : }
325 27960 : index++;
326 : }
327 :
328 4859 : if (skipList.empty()) {
329 3596 : quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_NO_SKIPPING;
330 3596 : quorumSnapshot.mnSkipList.clear();
331 3596 : } else {
332 1263 : quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
333 1263 : quorumSnapshot.mnSkipList = std::move(skipList);
334 : }
335 4859 : }
336 :
337 7575 : std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams,
338 : const llmq::UtilParameters& util_params,
339 : const CDeterministicMNList& allMns,
340 : const PreviousQuorumQuarters& previousQuarters)
341 : {
342 7575 : if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
343 : ASSERT_IF_DEBUG(false);
344 0 : return {};
345 : }
346 :
347 7575 : size_t nQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
348 7575 : std::vector<QuorumMembers> quarterQuorumMembers{nQuorums};
349 :
350 7575 : size_t quorumSize = static_cast<size_t>(llmqParams.size);
351 7575 : auto quarterSize{quorumSize / 4};
352 7575 : const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index);
353 :
354 7575 : if (allMns.GetCounts().enabled() < quarterSize) {
355 2716 : return quarterQuorumMembers;
356 : }
357 :
358 4859 : auto MnsUsedAtH = CDeterministicMNList();
359 4859 : std::vector<CDeterministicMNList> MnsUsedAtHIndexed{nQuorums};
360 :
361 9718 : bool skipRemovedMNs = DeploymentActiveAfter(util_params.m_base_index, util_params.m_chainman.GetConsensus(),
362 4931 : Consensus::DEPLOYMENT_V19) ||
363 72 : (util_params.m_chainman.GetParams().NetworkIDString() == CBaseChainParams::TESTNET);
364 :
365 14577 : for (const size_t idx : util::irange(nQuorums)) {
366 38872 : for (auto* prev_cycle : previousQuarters.GetCycles()) {
367 50846 : for (const auto& mn : prev_cycle->m_members[idx]) {
368 21692 : if (skipRemovedMNs && !allMns.HasMN(mn->proTxHash)) {
369 0 : continue;
370 : }
371 21692 : if (allMns.IsMNPoSeBanned(mn->proTxHash)) {
372 240 : continue;
373 : }
374 : try {
375 21452 : MnsUsedAtH.AddMN(mn);
376 21452 : } catch (const std::runtime_error& e) {
377 5243 : }
378 : try {
379 21452 : MnsUsedAtHIndexed[idx].AddMN(mn);
380 21452 : } catch (const std::runtime_error& e) {
381 1865 : }
382 : }
383 : }
384 : }
385 :
386 4859 : std::vector<CDeterministicMNCPtr> MnsNotUsedAtH;
387 34196 : allMns.ForEachMNShared(/*onlyValid=*/false, [&MnsUsedAtH, &MnsNotUsedAtH](const auto& dmn) {
388 29337 : if (!MnsUsedAtH.HasMN(dmn->proTxHash)) {
389 13128 : if (!dmn->pdmnState->IsBanned()) {
390 12237 : MnsNotUsedAtH.push_back(dmn);
391 12237 : }
392 13128 : }
393 29337 : });
394 :
395 4859 : auto sortedMnsUsedAtHM = CalculateQuorum(MnsUsedAtH, modifier);
396 4859 : auto sortedCombinedMnsList = CalculateQuorum(std::move(MnsNotUsedAtH), modifier);
397 21068 : for (auto& m : sortedMnsUsedAtHM) {
398 16209 : sortedCombinedMnsList.push_back(std::move(m));
399 : }
400 :
401 4859 : if (LogAcceptDebug(BCLog::LLMQ)) {
402 4859 : LogPrint(BCLog::LLMQ, "%s h[%d] sortedCombinedMns[%s]\n", __func__, util_params.m_base_index->nHeight,
403 : ToString(sortedCombinedMnsList));
404 4859 : }
405 :
406 4859 : std::vector<int> skipList;
407 4859 : size_t firstSkippedIndex = 0;
408 4859 : size_t idx{0};
409 14577 : for (const size_t i : util::irange(nQuorums)) {
410 9718 : auto usedMNsCount = MnsUsedAtHIndexed[i].GetCounts().total();
411 9718 : bool updated{false};
412 9718 : size_t initial_loop_idx = idx;
413 20340 : while (quarterQuorumMembers[i].size() < quarterSize && (usedMNsCount + quarterQuorumMembers[i].size() < sortedCombinedMnsList.size())) {
414 10622 : bool skip{true};
415 10622 : if (!MnsUsedAtHIndexed[i].HasMN(sortedCombinedMnsList[idx]->proTxHash)) {
416 : try {
417 : // NOTE: AddMN is the one that can throw exceptions, must be exicuted first
418 7899 : MnsUsedAtHIndexed[i].AddMN(sortedCombinedMnsList[idx]);
419 7899 : quarterQuorumMembers[i].push_back(sortedCombinedMnsList[idx]);
420 7899 : updated = true;
421 7899 : skip = false;
422 7899 : } catch (const std::runtime_error& e) {
423 0 : }
424 7899 : }
425 10622 : if (skip) {
426 2723 : if (firstSkippedIndex == 0) {
427 1868 : firstSkippedIndex = idx;
428 1868 : skipList.push_back(idx);
429 1868 : } else {
430 855 : skipList.push_back(idx - firstSkippedIndex);
431 : }
432 2723 : }
433 10622 : if (++idx == sortedCombinedMnsList.size()) {
434 795 : idx = 0;
435 795 : }
436 10622 : if (idx == initial_loop_idx) {
437 : // we made full "while" loop
438 241 : if (!updated) {
439 : // there are not enough MNs, there is nothing we can do here
440 0 : return std::vector<QuorumMembers>(nQuorums);
441 : }
442 : // reset and try again
443 241 : updated = false;
444 241 : }
445 : }
446 : }
447 :
448 4859 : llmq::CQuorumSnapshot quorumSnapshot{};
449 4859 : BuildQuorumSnapshot(llmqParams, util_params.m_chainman.GetConsensus(), allMns, MnsUsedAtH, sortedCombinedMnsList,
450 4859 : quorumSnapshot, skipList, util_params.m_base_index);
451 4859 : util_params.m_qsnapman.StoreSnapshotForBlock(llmqParams.type, util_params.m_base_index, quorumSnapshot);
452 :
453 4859 : return quarterQuorumMembers;
454 14683 : }
455 :
456 7575 : std::vector<QuorumMembers> ComputeQuorumMembersByQuarterRotation(const Consensus::LLMQParams& llmqParams,
457 : const llmq::UtilParameters& util_params)
458 : {
459 7575 : const int cycleLength = llmqParams.dkgInterval;
460 7575 : if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
461 : ASSERT_IF_DEBUG(false);
462 0 : return {};
463 : }
464 7575 : const auto nQuorums{static_cast<size_t>(llmqParams.signingActiveQuorumCount)};
465 :
466 7575 : const CBlockIndex* pWorkBlockIndex = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
467 : llmq::WORK_DIFF_DEPTH);
468 7575 : CDeterministicMNList allMns = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
469 7575 : LogPrint(BCLog::LLMQ, "ComputeQuorumMembersByQuarterRotation llmqType[%d] nHeight[%d] allMns[%d]\n",
470 : std23::to_underlying(llmqParams.type), util_params.m_base_index->nHeight, allMns.GetCounts().enabled());
471 :
472 7575 : PreviousQuorumQuarters previousQuarters(nQuorums);
473 7575 : auto prev_cycles{previousQuarters.GetCycles()};
474 18899 : for (size_t idx{0}; idx < prev_cycles.size(); idx++) {
475 31390 : prev_cycles[idx]->m_cycle_index = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
476 15695 : (cycleLength * (idx + 1)));
477 15695 : if (auto opt_snap = util_params.m_qsnapman.GetSnapshotForBlock(llmqParams.type, prev_cycles[idx]->m_cycle_index);
478 31390 : opt_snap.has_value()) {
479 11324 : prev_cycles[idx]->m_snap = opt_snap.value();
480 11324 : } else {
481 : // TODO: Check if it is triggered from outside (P2P, block validation) and maybe throw an exception
482 : // assert(false);
483 4371 : break;
484 : }
485 11324 : prev_cycles[idx]->m_members = GetQuorumQuarterMembersBySnapshot(llmqParams, util_params.m_dmnman,
486 11324 : util_params.m_chainman.GetConsensus(),
487 11324 : prev_cycles[idx]->m_cycle_index,
488 11324 : prev_cycles[idx]->m_snap,
489 11324 : util_params.m_base_index->nHeight);
490 11324 : }
491 :
492 7575 : auto newQuarterMembers = BuildNewQuorumQuarterMembers(llmqParams, util_params, allMns, previousQuarters);
493 : // TODO: Check if it is triggered from outside (P2P, block validation) and maybe throw an exception
494 : // assert (!newQuarterMembers.empty());
495 :
496 7575 : if (LogAcceptDebug(BCLog::LLMQ)) {
497 22725 : for (const size_t i : util::irange(nQuorums)) {
498 15150 : std::stringstream ss;
499 60600 : for (size_t idx = prev_cycles.size(); idx-- > 0;) {
500 45450 : ss << strprintf(" %dCmns[%s]", idx, ToString(prev_cycles[idx]->m_members[i]));
501 : }
502 15150 : ss << strprintf(" new[%s]", ToString(newQuarterMembers[i]));
503 15150 : LogPrint(BCLog::LLMQ, "QuarterComposition h[%d] i[%d]:%s\n", util_params.m_base_index->nHeight, i, ss.str());
504 15150 : }
505 7575 : }
506 :
507 7575 : std::vector<QuorumMembers> quorumMembers(nQuorums);
508 22725 : for (const size_t i : util::irange(nQuorums)) {
509 : // Move elements from previous quarters into quorumMembers
510 60600 : for (auto* prev_cycle : prev_cycles | std::views::reverse) {
511 45450 : std::move(prev_cycle->m_members[i].begin(), prev_cycle->m_members[i].end(),
512 45450 : std::back_inserter(quorumMembers[i]));
513 : }
514 15150 : std::move(newQuarterMembers[i].begin(), newQuarterMembers[i].end(), std::back_inserter(quorumMembers[i]));
515 15150 : if (LogAcceptDebug(BCLog::LLMQ)) {
516 15150 : LogPrint(BCLog::LLMQ, "QuorumComposition h[%d] i[%d]: [%s]\n", util_params.m_base_index->nHeight, i,
517 : ToString(quorumMembers[i]));
518 15150 : }
519 : }
520 :
521 7575 : return quorumMembers;
522 7575 : }
523 : } // anonymous namespace
524 :
525 : namespace llmq {
526 : namespace utils {
527 38904 : BlsCheck::BlsCheck() = default;
528 :
529 19452 : BlsCheck::BlsCheck(CBLSSignature sig, std::vector<CBLSPublicKey> pubkeys, uint256 msg_hash, std::string id_string) :
530 9726 : m_sig(sig),
531 9726 : m_pubkeys(pubkeys),
532 9726 : m_msg_hash(msg_hash),
533 9726 : m_id_string(id_string)
534 9726 : {
535 19452 : }
536 :
537 60610 : BlsCheck::~BlsCheck() = default;
538 :
539 9725 : bool BlsCheck::operator()()
540 : {
541 9725 : if (m_pubkeys.size() > 1) {
542 4805 : if (!m_sig.VerifySecureAggregated(m_pubkeys, m_msg_hash)) {
543 2 : LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
544 2 : return false;
545 : }
546 9723 : } else if (m_pubkeys.size() == 1) {
547 4920 : if (!m_sig.VerifyInsecure(m_pubkeys.back(), m_msg_hash)) {
548 2 : LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
549 2 : return false;
550 : }
551 4918 : } else {
552 : // we should not get there ever
553 0 : LogPrint(BCLog::LLMQ, "%s - no public keys are provided\n", m_id_string);
554 0 : return false;
555 : }
556 9721 : return true;
557 9725 : }
558 :
559 19452 : void BlsCheck::swap(BlsCheck& obj)
560 : {
561 19452 : std::swap(m_sig, obj.m_sig);
562 19452 : std::swap(m_pubkeys, obj.m_pubkeys);
563 19452 : std::swap(m_msg_hash, obj.m_msg_hash);
564 19452 : std::swap(m_id_string, obj.m_id_string);
565 19452 : }
566 :
567 661951 : QuorumMembers GetAllQuorumMembers(Consensus::LLMQType llmqType, const UtilParameters& util_params, bool reset_cache)
568 : {
569 661951 : static RecursiveMutex cs_members;
570 661951 : static std::map<Consensus::LLMQType, Uint256LruHashMap<QuorumMembers>> mapQuorumMembers GUARDED_BY(cs_members);
571 661951 : static RecursiveMutex cs_indexed_members;
572 661951 : static std::map<Consensus::LLMQType, unordered_lru_cache<std::pair<uint256, int>, QuorumMembers, StaticSaltedHasher>> mapIndexedQuorumMembers GUARDED_BY(cs_indexed_members);
573 :
574 661951 : if (!util_params.m_chainman.IsQuorumTypeEnabled(llmqType, util_params.m_base_index->pprev)) {
575 3355 : return {};
576 : }
577 :
578 658596 : std::vector<CDeterministicMNCPtr> quorumMembers;
579 : {
580 658596 : LOCK(cs_members);
581 658594 : if (mapQuorumMembers.empty()) {
582 1012 : InitQuorumsCache(mapQuorumMembers, util_params.m_chainman.GetConsensus());
583 1012 : }
584 658594 : if (reset_cache) {
585 696 : mapQuorumMembers[llmqType].clear();
586 658594 : } else if (mapQuorumMembers[llmqType].get(util_params.m_base_index->GetBlockHash(), quorumMembers)) {
587 642974 : return quorumMembers;
588 : }
589 658594 : }
590 :
591 15620 : const auto& llmq_params_opt = util_params.m_chainman.GetParams().GetLLMQ(llmqType);
592 15620 : assert(llmq_params_opt.has_value());
593 15620 : const auto& llmq_params = llmq_params_opt.value();
594 :
595 15620 : if (IsQuorumRotationEnabled(llmq_params, util_params.m_base_index)) {
596 10786 : if (LOCK(cs_indexed_members); mapIndexedQuorumMembers.empty()) {
597 969 : InitQuorumsCache(mapIndexedQuorumMembers, util_params.m_chainman.GetConsensus());
598 969 : }
599 : /*
600 : * Quorums created with rotation are now created in a different way. All signingActiveQuorumCount are created
601 : * during the period of dkgInterval. But they are not created exactly in the same block, they are spread
602 : * overtime: one quorum in each block until all signingActiveQuorumCount are created. The new concept of
603 : * quorumIndex is introduced in order to identify them. In every dkgInterval blocks (also called
604 : * CycleQuorumBaseBlock), the spread quorum creation starts like this: For quorumIndex = 0 :
605 : * signingActiveQuorumCount Quorum Q with quorumIndex is created at height CycleQuorumBaseBlock + quorumIndex
606 : */
607 :
608 9817 : int quorumIndex = util_params.m_base_index->nHeight % llmq_params.dkgInterval;
609 9817 : if (quorumIndex >= llmq_params.signingActiveQuorumCount) {
610 0 : return {};
611 : }
612 9817 : int cycleQuorumBaseHeight = util_params.m_base_index->nHeight - quorumIndex;
613 9817 : const CBlockIndex* pCycleQuorumBaseBlockIndex = util_params.m_base_index->GetAncestor(cycleQuorumBaseHeight);
614 :
615 : /*
616 : * Since mapQuorumMembers stores Quorum members per block hash, and we don't know yet the block hashes of blocks
617 : * for all quorumIndexes (since these blocks are not created yet) We store them in a second cache
618 : * mapIndexedQuorumMembers which stores them by {CycleQuorumBaseBlockHash, quorumIndex}
619 : */
620 9817 : if (reset_cache) {
621 696 : LOCK(cs_indexed_members);
622 696 : mapIndexedQuorumMembers[llmqType].clear();
623 18938 : } else if (LOCK(cs_indexed_members); mapIndexedQuorumMembers[llmqType].get(
624 18242 : std::pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), quorumIndex), quorumMembers)) {
625 2242 : LOCK(cs_members);
626 2242 : mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
627 2242 : return quorumMembers;
628 2242 : }
629 :
630 7575 : auto q = ComputeQuorumMembersByQuarterRotation(llmq_params, util_params.replace_index(pCycleQuorumBaseBlockIndex));
631 7575 : quorumMembers = q[quorumIndex];
632 :
633 7575 : LOCK(cs_indexed_members);
634 22725 : for (const size_t i : util::irange(q.size())) {
635 15150 : mapIndexedQuorumMembers[llmqType].emplace(std::make_pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), i),
636 15150 : std::move(q[i]));
637 : }
638 7575 : } else {
639 16471 : const CBlockIndex* pWorkBlockIndex = DeploymentActiveAfter(util_params.m_base_index,
640 5803 : util_params.m_chainman.GetConsensus(),
641 : Consensus::DEPLOYMENT_V20)
642 9730 : ? util_params.m_base_index->GetAncestor(
643 4865 : util_params.m_base_index->nHeight - WORK_DIFF_DEPTH)
644 938 : : util_params.m_base_index.get();
645 5803 : CDeterministicMNList mn_list = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
646 11607 : quorumMembers = ComputeQuorumMembers(llmqType, util_params.m_chainman.GetParams(), mn_list,
647 5803 : util_params.m_base_index);
648 5804 : }
649 :
650 13377 : LOCK(cs_members);
651 13378 : mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
652 13378 : return quorumMembers;
653 661965 : }
654 :
655 300797 : uint256 DeterministicOutboundConnection(const uint256& proTxHash1, const uint256& proTxHash2)
656 : {
657 : // We need to deterministically select who is going to initiate the connection. The naive way would be to simply
658 : // return the min(proTxHash1, proTxHash2), but this would create a bias towards MNs with a numerically low
659 : // hash. To fix this, we return the proTxHash that has the lowest value of:
660 : // hash(min(proTxHash1, proTxHash2), max(proTxHash1, proTxHash2), proTxHashX)
661 : // where proTxHashX is the proTxHash to compare
662 300797 : uint256 h1;
663 300797 : uint256 h2;
664 300797 : if (proTxHash1 < proTxHash2) {
665 154235 : h1 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash1));
666 154235 : h2 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash2));
667 154235 : } else {
668 146562 : h1 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash1));
669 146562 : h2 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash2));
670 : }
671 300797 : if (h1 < h2) {
672 151564 : return proTxHash1;
673 : }
674 149233 : return proTxHash2;
675 300797 : }
676 :
677 265741 : Uint256HashSet GetQuorumConnections(const Consensus::LLMQParams& llmqParams, const CSporkManager& sporkman,
678 : const UtilParameters& util_params, const uint256& forMember, bool onlyOutbound)
679 : {
680 265741 : if (IsAllMembersConnectedEnabled(llmqParams.type, sporkman)) {
681 101872 : auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
682 101872 : Uint256HashSet result;
683 :
684 477274 : for (const auto& dmn : mns) {
685 375402 : if (dmn->proTxHash == forMember) {
686 74867 : continue;
687 : }
688 : // Determine which of the two MNs (forMember vs dmn) should initiate the outbound connection and which
689 : // one should wait for the inbound connection. We do this in a deterministic way, so that even when we
690 : // end up with both connecting to each other, we know which one to disconnect
691 300535 : uint256 deterministicOutbound = DeterministicOutboundConnection(forMember, dmn->proTxHash);
692 300535 : if (!onlyOutbound || deterministicOutbound == dmn->proTxHash) {
693 180378 : result.emplace(dmn->proTxHash);
694 180378 : }
695 : }
696 101872 : return result;
697 101872 : }
698 163869 : return GetQuorumRelayMembers(llmqParams, util_params, forMember, onlyOutbound);
699 265741 : }
700 :
701 257095 : Uint256HashSet GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, const UtilParameters& util_params,
702 : const uint256& forMember, bool onlyOutbound)
703 : {
704 257095 : auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
705 257095 : Uint256HashSet result;
706 :
707 555314 : auto calcOutbound = [&](size_t i, const uint256& proTxHash) {
708 : // Relay to nodes at indexes (i+2^k)%n, where
709 : // k: 0..max(1, floor(log2(n-1))-1)
710 : // n: size of the quorum/ring
711 298219 : Uint256HashSet r{};
712 298219 : if (mns.size() == 1) {
713 : // No outbound connections are needed when there is one MN only.
714 : // Also note that trying to calculate results via the algorithm below
715 : // would result in an endless loop.
716 8891 : return r;
717 : }
718 289328 : int gap = 1;
719 289328 : int gap_max = (int)mns.size() - 1;
720 289328 : int k = 0;
721 867984 : while ((gap_max >>= 1) || k <= 1) {
722 578656 : size_t idx = (i + gap) % mns.size();
723 : // It doesn't matter if this node is going to be added to the resulting set or not,
724 : // we should always bump the gap and the k (step count) regardless.
725 : // Refusing to bump the gap results in an incomplete set in the best case scenario
726 : // (idx won't ever change again once we hit `==`). Not bumping k guarantees an endless
727 : // loop when the first or the second node we check is the one that should be skipped
728 : // (k <= 1 forever).
729 578656 : gap <<= 1;
730 578656 : k++;
731 578656 : const auto& otherDmn = mns[idx];
732 578656 : if (otherDmn->proTxHash == proTxHash) {
733 85887 : continue;
734 : }
735 492768 : r.emplace(otherDmn->proTxHash);
736 : }
737 289328 : return r;
738 298221 : };
739 :
740 981843 : for (const auto i : util::irange(mns.size())) {
741 724747 : const auto& dmn = mns[i];
742 724747 : if (dmn->proTxHash == forMember) {
743 219919 : auto r = calcOutbound(i, dmn->proTxHash);
744 219920 : result.insert(r.begin(), r.end());
745 724748 : } else if (!onlyOutbound) {
746 78299 : auto r = calcOutbound(i, dmn->proTxHash);
747 78299 : if (r.count(forMember)) {
748 31468 : result.emplace(dmn->proTxHash);
749 31468 : }
750 78299 : }
751 : }
752 :
753 257093 : return result;
754 257097 : }
755 :
756 18372 : std::unordered_set<size_t> CalcDeterministicWatchConnections(Consensus::LLMQType llmqType,
757 : gsl::not_null<const CBlockIndex*> pQuorumBaseBlockIndex,
758 : size_t memberCount, size_t connectionCount)
759 : {
760 : static uint256 qwatchConnectionSeed;
761 : static std::atomic<bool> qwatchConnectionSeedGenerated{false};
762 18372 : static RecursiveMutex qwatchConnectionSeedCs;
763 18372 : if (!qwatchConnectionSeedGenerated) {
764 217 : LOCK(qwatchConnectionSeedCs);
765 217 : qwatchConnectionSeed = GetRandHash();
766 217 : qwatchConnectionSeedGenerated = true;
767 217 : }
768 :
769 18372 : std::unordered_set<size_t> result;
770 18372 : uint256 rnd = qwatchConnectionSeed;
771 36744 : for ([[maybe_unused]] const auto _ : util::irange(connectionCount)) {
772 18372 : rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash())));
773 18372 : result.emplace(rnd.GetUint64(0) % memberCount);
774 : }
775 18372 : return result;
776 18372 : }
777 :
778 : } // namespace utils
779 : } // namespace llmq
|