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 8452 : std::string ToString(const QuorumMembers& members)
35 : {
36 8452 : std::stringstream ss;
37 11110 : for (const auto& mn : members) {
38 2658 : ss << mn->proTxHash.ToString().substr(0, 4) << "|";
39 : }
40 8452 : return ss.str();
41 8452 : }
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 4698 : 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 1566 : explicit PreviousQuorumQuarters(size_t s) : quarterHMinusC(s), quarterHMinus2C(s), quarterHMinus3C(s) {}
63 :
64 783 : std::vector<QuorumQuarter*> GetCycles() { return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C}; }
65 328 : std::vector<const QuorumQuarter*> GetCycles() const
66 : {
67 328 : return {&quarterHMinusC, &quarterHMinus2C, &quarterHMinus3C};
68 : }
69 : };
70 :
71 1042 : 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 1042 : uint256 h;
78 1042 : CSHA256 sha256;
79 2084 : sha256.Write(dmn->pdmnState->confirmedHashWithProRegTxHash.begin(),
80 1042 : dmn->pdmnState->confirmedHashWithProRegTxHash.size());
81 1042 : sha256.Write(modifier.begin(), modifier.size());
82 1042 : sha256.Finalize(h.begin());
83 1042 : return UintToArith256(h);
84 : }
85 :
86 1405 : 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 1405 : const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
91 :
92 1405 : if (DeploymentActiveAfter(pWorkBlockIndex, consensus_params, Consensus::DEPLOYMENT_V20)) {
93 : // v20 is active: calculate modifier using the new way.
94 1004 : auto cbcl = GetNonNullCoinbaseChainlock(pWorkBlockIndex);
95 1004 : if (cbcl.has_value()) {
96 : // We have a non-null CL signature: calculate modifier using this CL signature
97 0 : auto& [bestCLSignature, bestCLHeightDiff] = cbcl.value();
98 0 : 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 1004 : return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
102 1004 : }
103 :
104 : // v20 isn't active yet: calculate modifier using the usual way
105 401 : if (llmqParams.useRotation) {
106 401 : return ::SerializeHash(std::make_pair(llmqParams.type, pWorkBlockIndex->GetBlockHash()));
107 : }
108 0 : return ::SerializeHash(std::make_pair(llmqParams.type, pCycleQuorumBaseBlockIndex->GetBlockHash()));
109 1405 : }
110 :
111 164 : std::vector<MasternodeScore> CalculateScoresForQuorum(QuorumMembers&& dmns, const uint256& modifier, const bool onlyEvoNodes)
112 : {
113 164 : std::vector<MasternodeScore> scores;
114 164 : scores.reserve(dmns.size());
115 :
116 262 : for (auto& dmn : dmns) {
117 98 : if (dmn->pdmnState->IsBanned()) continue;
118 98 : 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 0 : continue;
122 : }
123 98 : if (onlyEvoNodes && dmn->nType != MnType::Evo) {
124 0 : continue;
125 : }
126 98 : scores.emplace_back(calculateQuorumScore(dmn, modifier), std::move(dmn));
127 : };
128 164 : return scores;
129 164 : }
130 :
131 786 : std::vector<MasternodeScore> CalculateScoresForQuorum(const CDeterministicMNList& mn_list, const uint256& modifier,
132 : const bool onlyEvoNodes)
133 : {
134 786 : std::vector<MasternodeScore> scores;
135 786 : scores.reserve(mn_list.GetCounts().total());
136 :
137 1730 : mn_list.ForEachMNShared(/*onlyValid=*/true, [&](const auto& dmn) {
138 944 : 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 0 : return;
142 : }
143 944 : if (onlyEvoNodes && dmn->nType != MnType::Evo) {
144 0 : return;
145 : }
146 944 : scores.emplace_back(calculateQuorumScore(dmn, modifier), dmn);
147 944 : });
148 786 : return scores;
149 786 : }
150 :
151 : /**
152 : * Calculate a quorum based on the modifier. The resulting list is deterministically sorted by score
153 : */
154 : template <typename List>
155 950 : QuorumMembers CalculateQuorum(List&& mn_list, const uint256& modifier, size_t maxSize = 0, const bool onlyEvoNodes = false)
156 : {
157 950 : auto scores = CalculateScoresForQuorum(std::forward<List>(mn_list), modifier, onlyEvoNodes);
158 :
159 : // sort is descending order
160 1846 : std::sort(scores.rbegin(), scores.rend(), [](const MasternodeScore& a, const MasternodeScore& b) {
161 896 : 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 896 : return a.m_score < b.m_score;
167 896 : });
168 :
169 : // return top maxSize entries only (if specified)
170 950 : if (maxSize > 0 && scores.size() > maxSize) {
171 0 : scores.resize(maxSize);
172 0 : }
173 :
174 950 : QuorumMembers result;
175 950 : result.reserve(scores.size());
176 1992 : for (auto& [_, node] : scores) {
177 1042 : result.emplace_back(std::move(node));
178 : }
179 950 : return result;
180 950 : }
181 :
182 458 : 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 458 : if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
189 : ASSERT_IF_DEBUG(false);
190 0 : return {};
191 : }
192 :
193 458 : std::vector<CDeterministicMNCPtr> sortedCombinedMns;
194 : {
195 916 : const CBlockIndex* pWorkBlockIndex = pCycleQuorumBaseBlockIndex->GetAncestor(
196 458 : pCycleQuorumBaseBlockIndex->nHeight - llmq::WORK_DIFF_DEPTH);
197 458 : auto mn_list = dmnman.GetListForBlock(pWorkBlockIndex);
198 458 : const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
199 458 : auto sortedAllMns = CalculateQuorum(mn_list, modifier);
200 :
201 458 : std::vector<CDeterministicMNCPtr> usedMNs;
202 458 : size_t i{0};
203 972 : for (const auto& dmn : sortedAllMns) {
204 514 : if (snapshot.activeQuorumMembers[i]) {
205 446 : usedMNs.push_back(dmn);
206 446 : } else {
207 68 : if (!dmn->pdmnState->IsBanned()) {
208 : // the list begins with all the unused MNs
209 68 : sortedCombinedMns.push_back(dmn);
210 68 : }
211 : }
212 514 : i++;
213 : }
214 :
215 : // Now add the already used MNs to the end of the list
216 458 : std::move(usedMNs.begin(), usedMNs.end(), std::back_inserter(sortedCombinedMns));
217 458 : }
218 :
219 458 : if (LogAcceptDebug(BCLog::LLMQ)) {
220 458 : LogPrint(BCLog::LLMQ, "%s h[%d] from[%d] sortedCombinedMns[%s]\n", __func__,
221 : pCycleQuorumBaseBlockIndex->nHeight, nHeight, ToString(sortedCombinedMns));
222 458 : }
223 :
224 458 : size_t numQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
225 458 : size_t quorumSize = static_cast<size_t>(llmqParams.size);
226 458 : auto quarterSize{quorumSize / 4};
227 :
228 458 : std::vector<QuorumMembers> quarterQuorumMembers(numQuorums);
229 :
230 458 : if (sortedCombinedMns.empty()) {
231 0 : return quarterQuorumMembers;
232 : }
233 :
234 458 : switch (snapshot.mnSkipListMode) {
235 : case SnapshotSkipMode::MODE_NO_SKIPPING: {
236 458 : auto itm = sortedCombinedMns.begin();
237 1374 : for (const size_t i : util::irange(numQuorums)) {
238 1832 : while (quarterQuorumMembers[i].size() < quarterSize) {
239 916 : quarterQuorumMembers[i].push_back(*itm);
240 916 : itm++;
241 916 : if (itm == sortedCombinedMns.end()) {
242 900 : itm = sortedCombinedMns.begin();
243 900 : }
244 : }
245 : }
246 458 : return quarterQuorumMembers;
247 : }
248 : case SnapshotSkipMode::MODE_SKIPPING_ENTRIES: // List holds entries to be skipped
249 : {
250 0 : size_t first_entry_index{0};
251 0 : std::vector<int> processesdSkipList;
252 0 : for (const auto& s : snapshot.mnSkipList) {
253 0 : if (first_entry_index == 0) {
254 0 : first_entry_index = s;
255 0 : processesdSkipList.push_back(s);
256 0 : } else {
257 0 : processesdSkipList.push_back(first_entry_index + s);
258 : }
259 : }
260 :
261 0 : int idx = 0;
262 0 : auto itsk = processesdSkipList.begin();
263 0 : for (const size_t i : util::irange(numQuorums)) {
264 0 : while (quarterQuorumMembers[i].size() < quarterSize) {
265 0 : if (itsk != processesdSkipList.end() && idx == *itsk) {
266 0 : itsk++;
267 0 : } else {
268 0 : quarterQuorumMembers[i].push_back(sortedCombinedMns[idx]);
269 : }
270 0 : idx++;
271 0 : if (idx == static_cast<int>(sortedCombinedMns.size())) {
272 0 : idx = 0;
273 0 : }
274 : }
275 : }
276 0 : return quarterQuorumMembers;
277 0 : }
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 458 : }
284 :
285 0 : QuorumMembers ComputeQuorumMembers(Consensus::LLMQType llmqType, const CChainParams& chainparams,
286 : const CDeterministicMNList& mn_list, const CBlockIndex* pQuorumBaseBlockIndex)
287 : {
288 0 : bool EvoOnly = (chainparams.GetConsensus().llmqTypePlatform == llmqType) &&
289 0 : DeploymentActiveAfter(pQuorumBaseBlockIndex, chainparams.GetConsensus(), Consensus::DEPLOYMENT_V19);
290 0 : const auto& llmq_params_opt = chainparams.GetLLMQ(llmqType);
291 0 : assert(llmq_params_opt.has_value());
292 0 : if (llmq_params_opt->useRotation || pQuorumBaseBlockIndex->nHeight % llmq_params_opt->dkgInterval != 0) {
293 : ASSERT_IF_DEBUG(false);
294 0 : return {};
295 : }
296 :
297 0 : const auto modifier = GetHashModifier(llmq_params_opt.value(), chainparams.GetConsensus(), pQuorumBaseBlockIndex);
298 0 : return CalculateQuorum(mn_list, modifier, llmq_params_opt->size, EvoOnly);
299 0 : }
300 :
301 164 : 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 164 : if (!llmqParams.useRotation || pCycleQuorumBaseBlockIndex->nHeight % llmqParams.dkgInterval != 0) {
307 : ASSERT_IF_DEBUG(false);
308 0 : return;
309 : }
310 :
311 164 : const auto allMnsTotal = allMns.GetCounts().total();
312 164 : quorumSnapshot.activeQuorumMembers.resize(allMnsTotal);
313 164 : const auto modifier = GetHashModifier(llmqParams, consensus_params, pCycleQuorumBaseBlockIndex);
314 164 : auto sortedAllMns = CalculateQuorum(allMns, modifier);
315 :
316 164 : LogPrint(BCLog::LLMQ, "BuildQuorumSnapshot h[%d] numMns[%d]\n", pCycleQuorumBaseBlockIndex->nHeight,
317 : allMnsTotal);
318 :
319 164 : std::fill(quorumSnapshot.activeQuorumMembers.begin(), quorumSnapshot.activeQuorumMembers.end(), false);
320 164 : size_t index = {};
321 428 : for (const auto& dmn : sortedAllMns) {
322 264 : if (mnUsedAtH.HasMN(dmn->proTxHash)) {
323 166 : quorumSnapshot.activeQuorumMembers[index] = true;
324 166 : }
325 264 : index++;
326 : }
327 :
328 164 : if (skipList.empty()) {
329 164 : quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_NO_SKIPPING;
330 164 : quorumSnapshot.mnSkipList.clear();
331 164 : } else {
332 0 : quorumSnapshot.mnSkipListMode = SnapshotSkipMode::MODE_SKIPPING_ENTRIES;
333 0 : quorumSnapshot.mnSkipList = std::move(skipList);
334 : }
335 164 : }
336 :
337 783 : std::vector<QuorumMembers> BuildNewQuorumQuarterMembers(const Consensus::LLMQParams& llmqParams,
338 : const llmq::UtilParameters& util_params,
339 : const CDeterministicMNList& allMns,
340 : const PreviousQuorumQuarters& previousQuarters)
341 : {
342 783 : if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
343 : ASSERT_IF_DEBUG(false);
344 0 : return {};
345 : }
346 :
347 783 : size_t nQuorums = static_cast<size_t>(llmqParams.signingActiveQuorumCount);
348 783 : std::vector<QuorumMembers> quarterQuorumMembers{nQuorums};
349 :
350 783 : size_t quorumSize = static_cast<size_t>(llmqParams.size);
351 783 : auto quarterSize{quorumSize / 4};
352 783 : const auto modifier = GetHashModifier(llmqParams, util_params.m_chainman.GetConsensus(), util_params.m_base_index);
353 :
354 783 : if (allMns.GetCounts().enabled() < quarterSize) {
355 619 : return quarterQuorumMembers;
356 : }
357 :
358 164 : auto MnsUsedAtH = CDeterministicMNList();
359 164 : std::vector<CDeterministicMNList> MnsUsedAtHIndexed{nQuorums};
360 :
361 328 : bool skipRemovedMNs = DeploymentActiveAfter(util_params.m_base_index, util_params.m_chainman.GetConsensus(),
362 164 : Consensus::DEPLOYMENT_V19) ||
363 0 : (util_params.m_chainman.GetParams().NetworkIDString() == CBaseChainParams::TESTNET);
364 :
365 492 : for (const size_t idx : util::irange(nQuorums)) {
366 1312 : for (auto* prev_cycle : previousQuarters.GetCycles()) {
367 1900 : for (const auto& mn : prev_cycle->m_members[idx]) {
368 916 : if (skipRemovedMNs && !allMns.HasMN(mn->proTxHash)) {
369 0 : continue;
370 : }
371 916 : if (allMns.IsMNPoSeBanned(mn->proTxHash)) {
372 2 : continue;
373 : }
374 : try {
375 914 : MnsUsedAtH.AddMN(mn);
376 914 : } catch (const std::runtime_error& e) {
377 748 : }
378 : try {
379 914 : MnsUsedAtHIndexed[idx].AddMN(mn);
380 914 : } catch (const std::runtime_error& e) {
381 596 : }
382 : }
383 : }
384 : }
385 :
386 164 : std::vector<CDeterministicMNCPtr> MnsNotUsedAtH;
387 432 : allMns.ForEachMNShared(/*onlyValid=*/false, [&MnsUsedAtH, &MnsNotUsedAtH](const auto& dmn) {
388 268 : if (!MnsUsedAtH.HasMN(dmn->proTxHash)) {
389 102 : if (!dmn->pdmnState->IsBanned()) {
390 98 : MnsNotUsedAtH.push_back(dmn);
391 98 : }
392 102 : }
393 268 : });
394 :
395 164 : auto sortedMnsUsedAtHM = CalculateQuorum(MnsUsedAtH, modifier);
396 164 : auto sortedCombinedMnsList = CalculateQuorum(std::move(MnsNotUsedAtH), modifier);
397 330 : for (auto& m : sortedMnsUsedAtHM) {
398 166 : sortedCombinedMnsList.push_back(std::move(m));
399 : }
400 :
401 164 : if (LogAcceptDebug(BCLog::LLMQ)) {
402 164 : LogPrint(BCLog::LLMQ, "%s h[%d] sortedCombinedMns[%s]\n", __func__, util_params.m_base_index->nHeight,
403 : ToString(sortedCombinedMnsList));
404 164 : }
405 :
406 164 : std::vector<int> skipList;
407 164 : size_t firstSkippedIndex = 0;
408 164 : size_t idx{0};
409 492 : for (const size_t i : util::irange(nQuorums)) {
410 328 : auto usedMNsCount = MnsUsedAtHIndexed[i].GetCounts().total();
411 328 : bool updated{false};
412 328 : size_t initial_loop_idx = idx;
413 352 : while (quarterQuorumMembers[i].size() < quarterSize && (usedMNsCount + quarterQuorumMembers[i].size() < sortedCombinedMnsList.size())) {
414 24 : bool skip{true};
415 24 : if (!MnsUsedAtHIndexed[i].HasMN(sortedCombinedMnsList[idx]->proTxHash)) {
416 : try {
417 : // NOTE: AddMN is the one that can throw exceptions, must be exicuted first
418 24 : MnsUsedAtHIndexed[i].AddMN(sortedCombinedMnsList[idx]);
419 24 : quarterQuorumMembers[i].push_back(sortedCombinedMnsList[idx]);
420 24 : updated = true;
421 24 : skip = false;
422 24 : } catch (const std::runtime_error& e) {
423 0 : }
424 24 : }
425 24 : if (skip) {
426 0 : if (firstSkippedIndex == 0) {
427 0 : firstSkippedIndex = idx;
428 0 : skipList.push_back(idx);
429 0 : } else {
430 0 : skipList.push_back(idx - firstSkippedIndex);
431 : }
432 0 : }
433 24 : if (++idx == sortedCombinedMnsList.size()) {
434 4 : idx = 0;
435 4 : }
436 24 : if (idx == initial_loop_idx) {
437 : // we made full "while" loop
438 4 : 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 4 : updated = false;
444 4 : }
445 : }
446 : }
447 :
448 164 : llmq::CQuorumSnapshot quorumSnapshot{};
449 164 : BuildQuorumSnapshot(llmqParams, util_params.m_chainman.GetConsensus(), allMns, MnsUsedAtH, sortedCombinedMnsList,
450 164 : quorumSnapshot, skipList, util_params.m_base_index);
451 164 : util_params.m_qsnapman.StoreSnapshotForBlock(llmqParams.type, util_params.m_base_index, quorumSnapshot);
452 :
453 164 : return quarterQuorumMembers;
454 2127 : }
455 :
456 783 : std::vector<QuorumMembers> ComputeQuorumMembersByQuarterRotation(const Consensus::LLMQParams& llmqParams,
457 : const llmq::UtilParameters& util_params)
458 : {
459 783 : const int cycleLength = llmqParams.dkgInterval;
460 783 : if (!llmqParams.useRotation || util_params.m_base_index->nHeight % llmqParams.dkgInterval != 0) {
461 : ASSERT_IF_DEBUG(false);
462 0 : return {};
463 : }
464 783 : const auto nQuorums{static_cast<size_t>(llmqParams.signingActiveQuorumCount)};
465 :
466 783 : const CBlockIndex* pWorkBlockIndex = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
467 : llmq::WORK_DIFF_DEPTH);
468 783 : CDeterministicMNList allMns = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
469 783 : 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 783 : PreviousQuorumQuarters previousQuarters(nQuorums);
473 783 : auto prev_cycles{previousQuarters.GetCycles()};
474 1241 : for (size_t idx{0}; idx < prev_cycles.size(); idx++) {
475 2186 : prev_cycles[idx]->m_cycle_index = util_params.m_base_index->GetAncestor(util_params.m_base_index->nHeight -
476 1093 : (cycleLength * (idx + 1)));
477 1093 : if (auto opt_snap = util_params.m_qsnapman.GetSnapshotForBlock(llmqParams.type, prev_cycles[idx]->m_cycle_index);
478 2186 : opt_snap.has_value()) {
479 458 : prev_cycles[idx]->m_snap = opt_snap.value();
480 458 : } else {
481 : // TODO: Check if it is triggered from outside (P2P, block validation) and maybe throw an exception
482 : // assert(false);
483 635 : break;
484 : }
485 458 : prev_cycles[idx]->m_members = GetQuorumQuarterMembersBySnapshot(llmqParams, util_params.m_dmnman,
486 458 : util_params.m_chainman.GetConsensus(),
487 458 : prev_cycles[idx]->m_cycle_index,
488 458 : prev_cycles[idx]->m_snap,
489 458 : util_params.m_base_index->nHeight);
490 458 : }
491 :
492 783 : 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 783 : if (LogAcceptDebug(BCLog::LLMQ)) {
497 2349 : for (const size_t i : util::irange(nQuorums)) {
498 1566 : std::stringstream ss;
499 6264 : for (size_t idx = prev_cycles.size(); idx-- > 0;) {
500 4698 : ss << strprintf(" %dCmns[%s]", idx, ToString(prev_cycles[idx]->m_members[i]));
501 : }
502 1566 : ss << strprintf(" new[%s]", ToString(newQuarterMembers[i]));
503 1566 : LogPrint(BCLog::LLMQ, "QuarterComposition h[%d] i[%d]:%s\n", util_params.m_base_index->nHeight, i, ss.str());
504 1566 : }
505 783 : }
506 :
507 783 : std::vector<QuorumMembers> quorumMembers(nQuorums);
508 2349 : for (const size_t i : util::irange(nQuorums)) {
509 : // Move elements from previous quarters into quorumMembers
510 6264 : for (auto* prev_cycle : prev_cycles | std::views::reverse) {
511 4698 : std::move(prev_cycle->m_members[i].begin(), prev_cycle->m_members[i].end(),
512 4698 : std::back_inserter(quorumMembers[i]));
513 : }
514 1566 : std::move(newQuarterMembers[i].begin(), newQuarterMembers[i].end(), std::back_inserter(quorumMembers[i]));
515 1566 : if (LogAcceptDebug(BCLog::LLMQ)) {
516 1566 : LogPrint(BCLog::LLMQ, "QuorumComposition h[%d] i[%d]: [%s]\n", util_params.m_base_index->nHeight, i,
517 : ToString(quorumMembers[i]));
518 1566 : }
519 : }
520 :
521 783 : return quorumMembers;
522 783 : }
523 : } // anonymous namespace
524 :
525 : namespace llmq {
526 : namespace utils {
527 0 : BlsCheck::BlsCheck() = default;
528 :
529 0 : BlsCheck::BlsCheck(CBLSSignature sig, std::vector<CBLSPublicKey> pubkeys, uint256 msg_hash, std::string id_string) :
530 0 : m_sig(sig),
531 0 : m_pubkeys(pubkeys),
532 0 : m_msg_hash(msg_hash),
533 0 : m_id_string(id_string)
534 0 : {
535 0 : }
536 :
537 0 : BlsCheck::~BlsCheck() = default;
538 :
539 0 : bool BlsCheck::operator()()
540 : {
541 0 : if (m_pubkeys.size() > 1) {
542 0 : if (!m_sig.VerifySecureAggregated(m_pubkeys, m_msg_hash)) {
543 0 : LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
544 0 : return false;
545 : }
546 0 : } else if (m_pubkeys.size() == 1) {
547 0 : if (!m_sig.VerifyInsecure(m_pubkeys.back(), m_msg_hash)) {
548 0 : LogPrint(BCLog::LLMQ, "%s\n", m_id_string);
549 0 : return false;
550 : }
551 0 : } 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 0 : return true;
557 0 : }
558 :
559 0 : void BlsCheck::swap(BlsCheck& obj)
560 : {
561 0 : std::swap(m_sig, obj.m_sig);
562 0 : std::swap(m_pubkeys, obj.m_pubkeys);
563 0 : std::swap(m_msg_hash, obj.m_msg_hash);
564 0 : std::swap(m_id_string, obj.m_id_string);
565 0 : }
566 :
567 792 : QuorumMembers GetAllQuorumMembers(Consensus::LLMQType llmqType, const UtilParameters& util_params, bool reset_cache)
568 : {
569 792 : static RecursiveMutex cs_members;
570 792 : static std::map<Consensus::LLMQType, Uint256LruHashMap<QuorumMembers>> mapQuorumMembers GUARDED_BY(cs_members);
571 792 : static RecursiveMutex cs_indexed_members;
572 792 : static std::map<Consensus::LLMQType, unordered_lru_cache<std::pair<uint256, int>, QuorumMembers, StaticSaltedHasher>> mapIndexedQuorumMembers GUARDED_BY(cs_indexed_members);
573 :
574 792 : if (!util_params.m_chainman.IsQuorumTypeEnabled(llmqType, util_params.m_base_index->pprev)) {
575 0 : return {};
576 : }
577 :
578 792 : std::vector<CDeterministicMNCPtr> quorumMembers;
579 : {
580 792 : LOCK(cs_members);
581 792 : if (mapQuorumMembers.empty()) {
582 5 : InitQuorumsCache(mapQuorumMembers, util_params.m_chainman.GetConsensus());
583 5 : }
584 792 : if (reset_cache) {
585 16 : mapQuorumMembers[llmqType].clear();
586 792 : } else if (mapQuorumMembers[llmqType].get(util_params.m_base_index->GetBlockHash(), quorumMembers)) {
587 9 : return quorumMembers;
588 : }
589 792 : }
590 :
591 783 : const auto& llmq_params_opt = util_params.m_chainman.GetParams().GetLLMQ(llmqType);
592 783 : assert(llmq_params_opt.has_value());
593 783 : const auto& llmq_params = llmq_params_opt.value();
594 :
595 783 : if (IsQuorumRotationEnabled(llmq_params, util_params.m_base_index)) {
596 788 : if (LOCK(cs_indexed_members); mapIndexedQuorumMembers.empty()) {
597 5 : InitQuorumsCache(mapIndexedQuorumMembers, util_params.m_chainman.GetConsensus());
598 5 : }
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 783 : int quorumIndex = util_params.m_base_index->nHeight % llmq_params.dkgInterval;
609 783 : if (quorumIndex >= llmq_params.signingActiveQuorumCount) {
610 0 : return {};
611 : }
612 783 : int cycleQuorumBaseHeight = util_params.m_base_index->nHeight - quorumIndex;
613 783 : 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 783 : if (reset_cache) {
621 16 : LOCK(cs_indexed_members);
622 16 : mapIndexedQuorumMembers[llmqType].clear();
623 1550 : } else if (LOCK(cs_indexed_members); mapIndexedQuorumMembers[llmqType].get(
624 1534 : std::pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), quorumIndex), quorumMembers)) {
625 0 : LOCK(cs_members);
626 0 : mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
627 0 : return quorumMembers;
628 0 : }
629 :
630 783 : auto q = ComputeQuorumMembersByQuarterRotation(llmq_params, util_params.replace_index(pCycleQuorumBaseBlockIndex));
631 783 : quorumMembers = q[quorumIndex];
632 :
633 783 : LOCK(cs_indexed_members);
634 2349 : for (const size_t i : util::irange(q.size())) {
635 1566 : mapIndexedQuorumMembers[llmqType].emplace(std::make_pair(pCycleQuorumBaseBlockIndex->GetBlockHash(), i),
636 1566 : std::move(q[i]));
637 : }
638 783 : } else {
639 0 : const CBlockIndex* pWorkBlockIndex = DeploymentActiveAfter(util_params.m_base_index,
640 0 : util_params.m_chainman.GetConsensus(),
641 : Consensus::DEPLOYMENT_V20)
642 0 : ? util_params.m_base_index->GetAncestor(
643 0 : util_params.m_base_index->nHeight - WORK_DIFF_DEPTH)
644 0 : : util_params.m_base_index.get();
645 0 : CDeterministicMNList mn_list = util_params.m_dmnman.GetListForBlock(pWorkBlockIndex);
646 0 : quorumMembers = ComputeQuorumMembers(llmqType, util_params.m_chainman.GetParams(), mn_list,
647 0 : util_params.m_base_index);
648 0 : }
649 :
650 783 : LOCK(cs_members);
651 783 : mapQuorumMembers[llmqType].insert(util_params.m_base_index->GetBlockHash(), quorumMembers);
652 783 : return quorumMembers;
653 792 : }
654 :
655 15 : 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 15 : uint256 h1;
663 15 : uint256 h2;
664 15 : if (proTxHash1 < proTxHash2) {
665 7 : h1 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash1));
666 7 : h2 = ::SerializeHash(std::make_tuple(proTxHash1, proTxHash2, proTxHash2));
667 7 : } else {
668 8 : h1 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash1));
669 8 : h2 = ::SerializeHash(std::make_tuple(proTxHash2, proTxHash1, proTxHash2));
670 : }
671 15 : if (h1 < h2) {
672 7 : return proTxHash1;
673 : }
674 8 : return proTxHash2;
675 15 : }
676 :
677 0 : Uint256HashSet GetQuorumConnections(const Consensus::LLMQParams& llmqParams, const CSporkManager& sporkman,
678 : const UtilParameters& util_params, const uint256& forMember, bool onlyOutbound)
679 : {
680 0 : if (IsAllMembersConnectedEnabled(llmqParams.type, sporkman)) {
681 0 : auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
682 0 : Uint256HashSet result;
683 :
684 0 : for (const auto& dmn : mns) {
685 0 : if (dmn->proTxHash == forMember) {
686 0 : 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 0 : uint256 deterministicOutbound = DeterministicOutboundConnection(forMember, dmn->proTxHash);
692 0 : if (!onlyOutbound || deterministicOutbound == dmn->proTxHash) {
693 0 : result.emplace(dmn->proTxHash);
694 0 : }
695 : }
696 0 : return result;
697 0 : }
698 0 : return GetQuorumRelayMembers(llmqParams, util_params, forMember, onlyOutbound);
699 0 : }
700 :
701 0 : Uint256HashSet GetQuorumRelayMembers(const Consensus::LLMQParams& llmqParams, const UtilParameters& util_params,
702 : const uint256& forMember, bool onlyOutbound)
703 : {
704 0 : auto mns = GetAllQuorumMembers(llmqParams.type, util_params);
705 0 : Uint256HashSet result;
706 :
707 0 : 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 0 : Uint256HashSet r{};
712 0 : 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 0 : return r;
717 : }
718 0 : int gap = 1;
719 0 : int gap_max = (int)mns.size() - 1;
720 0 : int k = 0;
721 0 : while ((gap_max >>= 1) || k <= 1) {
722 0 : 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 0 : gap <<= 1;
730 0 : k++;
731 0 : const auto& otherDmn = mns[idx];
732 0 : if (otherDmn->proTxHash == proTxHash) {
733 0 : continue;
734 : }
735 0 : r.emplace(otherDmn->proTxHash);
736 : }
737 0 : return r;
738 0 : };
739 :
740 0 : for (const auto i : util::irange(mns.size())) {
741 0 : const auto& dmn = mns[i];
742 0 : if (dmn->proTxHash == forMember) {
743 0 : auto r = calcOutbound(i, dmn->proTxHash);
744 0 : result.insert(r.begin(), r.end());
745 0 : } else if (!onlyOutbound) {
746 0 : auto r = calcOutbound(i, dmn->proTxHash);
747 0 : if (r.count(forMember)) {
748 0 : result.emplace(dmn->proTxHash);
749 0 : }
750 0 : }
751 : }
752 :
753 0 : return result;
754 0 : }
755 :
756 0 : 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 0 : static RecursiveMutex qwatchConnectionSeedCs;
763 0 : if (!qwatchConnectionSeedGenerated) {
764 0 : LOCK(qwatchConnectionSeedCs);
765 0 : qwatchConnectionSeed = GetRandHash();
766 0 : qwatchConnectionSeedGenerated = true;
767 0 : }
768 :
769 0 : std::unordered_set<size_t> result;
770 0 : uint256 rnd = qwatchConnectionSeed;
771 0 : for ([[maybe_unused]] const auto _ : util::irange(connectionCount)) {
772 0 : rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair(llmqType, pQuorumBaseBlockIndex->GetBlockHash())));
773 0 : result.emplace(rnd.GetUint64(0) % memberCount);
774 : }
775 0 : return result;
776 0 : }
777 :
778 : } // namespace utils
779 : } // namespace llmq
|