Line data Source code
1 : // Copyright (c) 2018-2025 The Dash Core developers
2 : // Distributed under the MIT/X11 software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <llmq/dkgsession.h>
6 :
7 : #include <batchedlogger.h>
8 : #include <bls/bls_worker.h>
9 : #include <evo/deterministicmns.h>
10 : #include <llmq/commitment.h>
11 : #include <llmq/debug.h>
12 : #include <llmq/dkgsessionmgr.h>
13 : #include <llmq/options.h>
14 : #include <llmq/utils.h>
15 : #include <masternode/meta.h>
16 : #include <util/helpers.h>
17 : #include <util/std23.h>
18 :
19 : #include <chainparams.h>
20 : #include <deploymentstatus.h>
21 : #include <logging.h>
22 : #include <validation.h>
23 :
24 : #include <cxxtimer.hpp>
25 :
26 : #include <algorithm>
27 : #include <array>
28 : #include <atomic>
29 : #include <memory>
30 : #include <ranges>
31 : #include <type_traits>
32 :
33 : namespace llmq {
34 174514 : CDKGLogger::CDKGLogger(const CDKGSession& _quorumDkg, std::string_view _func, int source_line) :
35 87258 : CBatchedLogger(BCLog::LLMQ_DKG, BCLog::Level::Debug,
36 174516 : strprintf("QuorumDKG(type=%s, qIndex=%d, h=%d, member=%d)", _quorumDkg.params.name, _quorumDkg.quorumIndex,
37 87258 : _quorumDkg.m_quorum_base_block_index->nHeight, _quorumDkg.AreWeMember()),
38 87258 : __FILE__, source_line)
39 174514 : {
40 174514 : }
41 :
42 : static std::array<std::atomic<double>, std23::to_underlying(DKGError::type::_COUNT)> simDkgErrorMap{};
43 :
44 34 : void SetSimulatedDKGErrorRate(DKGError::type type, double rate)
45 : {
46 34 : if (type >= DKGError::type::_COUNT) return;
47 33 : simDkgErrorMap[std23::to_underlying(type)] = rate;
48 34 : }
49 :
50 8576 : double GetSimulatedErrorRate(DKGError::type type)
51 : {
52 8576 : if (type >= DKGError::type::_COUNT) return 0;
53 8574 : return simDkgErrorMap[std23::to_underlying(type)];
54 8576 : }
55 :
56 20211 : bool CDKGSession::ShouldSimulateError(DKGError::type type) const
57 : {
58 20211 : if (params.type != Consensus::LLMQType::LLMQ_TEST) {
59 11651 : return false;
60 : }
61 8560 : double rate = GetSimulatedErrorRate(type);
62 8560 : return GetRandBool(rate);
63 20211 : }
64 :
65 41828 : CDKGMember::CDKGMember(const CDeterministicMNCPtr& _dmn, size_t _idx) :
66 20914 : dmn(_dmn),
67 20914 : idx(_idx),
68 20914 : id(_dmn->proTxHash)
69 20914 : {
70 20914 : }
71 :
72 17953 : uint256 CDKGPrematureCommitment::GetSignHash() const
73 : {
74 17953 : return BuildCommitmentHash(llmqType, quorumHash, validMembers, quorumPublicKey, quorumVvecHash);
75 : }
76 :
77 11043 : CDKGSession::CDKGSession(CBLSWorker& _blsWorker, CDeterministicMNManager& dmnman, CDKGDebugManager& _dkgDebugManager,
78 : CDKGSessionManager& _dkgManager, CQuorumSnapshotManager& qsnapman,
79 : const ChainstateManager& chainman, const CBlockIndex* pQuorumBaseBlockIndex,
80 : const Consensus::LLMQParams& _params) :
81 11043 : blsWorker{_blsWorker},
82 11043 : cache{_blsWorker},
83 11043 : m_dmnman{dmnman},
84 11043 : dkgDebugManager{_dkgDebugManager},
85 11043 : dkgManager{_dkgManager},
86 11043 : m_qsnapman{qsnapman},
87 11043 : m_chainman{chainman},
88 11043 : params{_params},
89 11043 : m_quorum_base_block_index{pQuorumBaseBlockIndex}
90 11043 : {
91 0 : }
92 :
93 11041 : CDKGSession::~CDKGSession() = default;
94 :
95 11057 : bool CDKGSession::Init(const uint256& _myProTxHash, int _quorumIndex)
96 : {
97 11057 : const auto mns = utils::GetAllQuorumMembers(params.type, {m_dmnman, m_qsnapman, m_chainman, m_quorum_base_block_index});
98 11057 : quorumIndex = _quorumIndex;
99 11057 : members.resize(mns.size());
100 11043 : memberIds.resize(members.size());
101 11043 : receivedVvecs.resize(members.size());
102 11043 : receivedSkContributions.resize(members.size());
103 11043 : vecEncryptedContributions.resize(members.size());
104 :
105 31956 : for (const auto i : util::irange(mns.size())) {
106 20912 : members[i] = std::make_unique<CDKGMember>(mns[i], i);
107 20914 : membersMap.emplace(members[i]->dmn->proTxHash, i);
108 20913 : memberIds[i] = members[i]->id;
109 : }
110 :
111 11043 : if (!_myProTxHash.IsNull()) {
112 22785 : for (const auto i : util::irange(members.size())) {
113 15749 : const auto& m = members[i];
114 15749 : if (m->dmn->proTxHash == _myProTxHash) {
115 3661 : myIdx = i;
116 3661 : myProTxHash = _myProTxHash;
117 3661 : myId = m->id;
118 3661 : break;
119 : }
120 : }
121 10697 : }
122 :
123 11042 : CDKGLogger logger(*this, __func__, __LINE__);
124 :
125 11043 : if (LogAcceptDebug(BCLog::LLMQ) && IsQuorumRotationEnabled(params, m_quorum_base_block_index)) {
126 3610 : int cycleQuorumBaseHeight = m_quorum_base_block_index->nHeight - quorumIndex;
127 3610 : const CBlockIndex* pCycleQuorumBaseBlockIndex = m_quorum_base_block_index->GetAncestor(cycleQuorumBaseHeight);
128 3610 : std::stringstream ss;
129 15843 : for (const auto& mn : members) {
130 12233 : ss << mn->dmn->proTxHash.ToString().substr(0, 4) << " | ";
131 : }
132 3610 : logger.Batch("DKGComposition h[%d] i[%d] DKG:[%s]", pCycleQuorumBaseBlockIndex->nHeight, quorumIndex, ss.str());
133 3610 : }
134 :
135 11043 : if (mns.size() < size_t(params.minSize)) {
136 6170 : logger.Batch("not enough members (%d < %d), aborting init", mns.size(), params.minSize);
137 6170 : return false;
138 : }
139 :
140 4873 : if (!myProTxHash.IsNull()) {
141 3037 : dkgDebugManager.InitLocalSessionStatus(params, quorumIndex, m_quorum_base_block_index->GetBlockHash(), m_quorum_base_block_index->nHeight);
142 3037 : relayMembers = utils::GetQuorumRelayMembers(params, {m_dmnman, m_qsnapman, m_chainman, m_quorum_base_block_index}, myProTxHash,
143 : /*onlyOutbound=*/true);
144 3037 : if (LogAcceptDebug(BCLog::LLMQ)) {
145 3037 : std::stringstream ss;
146 8644 : for (const auto& r : relayMembers) {
147 5607 : ss << r.ToString().substr(0, 4) << " | ";
148 : }
149 3037 : logger.Batch("forMember[%s] relayMembers[%s]", myProTxHash.ToString().substr(0, 4), ss.str());
150 3037 : }
151 3037 : }
152 :
153 4873 : if (myProTxHash.IsNull()) {
154 1836 : logger.Batch("initialized as observer. mns=%d", mns.size());
155 1836 : } else {
156 3037 : logger.Batch("initialized as member. mns=%d", mns.size());
157 : }
158 :
159 4873 : return true;
160 11071 : }
161 :
162 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
163 8952 : bool CDKGSession::PreVerifyMessage(const CDKGContribution& qc, bool& retBan) const
164 : {
165 8952 : CDKGLogger logger(*this, __func__, __LINE__);
166 :
167 8952 : retBan = false;
168 :
169 8952 : if (qc.quorumHash != m_quorum_base_block_index->GetBlockHash()) {
170 0 : logger.Batch("contribution for wrong quorum, rejecting");
171 0 : return false;
172 : }
173 :
174 8952 : auto* member = GetMember(qc.proTxHash);
175 8952 : if (member == nullptr) {
176 0 : logger.Batch("contributor not a member of this quorum, rejecting contribution");
177 0 : retBan = true;
178 0 : return false;
179 : }
180 :
181 8952 : if (qc.contributions->blobs.size() != members.size()) {
182 0 : logger.Batch("invalid contributions count");
183 0 : retBan = true;
184 0 : return false;
185 : }
186 8952 : if (qc.vvec->size() != size_t(params.threshold)) {
187 0 : logger.Batch("invalid verification vector length");
188 0 : retBan = true;
189 0 : return false;
190 : }
191 :
192 8952 : if (!CBLSWorker::VerifyVerificationVector(*qc.vvec)) {
193 0 : logger.Batch("invalid verification vector");
194 0 : retBan = true;
195 0 : return false;
196 : }
197 :
198 8952 : if (member->contributions.size() >= 2) {
199 : // don't do any further processing if we got more than 1 valid contributions already
200 : // this is a DoS protection against members sending multiple contributions with valid signatures to us
201 : // we must bail out before any expensive BLS verification happens
202 0 : logger.Batch("dropping contribution from %s as we already got %d contributions", member->dmn->proTxHash.ToString(), member->contributions.size());
203 0 : return false;
204 : }
205 :
206 8952 : return true;
207 8952 : }
208 :
209 8952 : std::optional<CInv> CDKGSession::ReceiveMessage(const CDKGContribution& qc)
210 : {
211 8952 : CDKGLogger logger(*this, __func__, __LINE__);
212 8952 : cxxtimer::Timer t1(true);
213 :
214 17904 : auto state = WITH_LOCK(invCs, return ReceiveMessagePreamble(qc, MsgPhase::Contribution, logger));
215 8952 : if (!state) return std::nullopt;
216 44752 : auto& [member, hash, inv, should_process] = *state;
217 8952 : if (!should_process) return inv;
218 :
219 17904 : receivedVvecs[member->idx] = qc.vvec;
220 :
221 44286 : int receivedCount = std::ranges::count_if(members, [](const auto& m) { return !m->contributions.empty(); });
222 :
223 8952 : logger.Batch("received and relayed contribution. received=%d/%d, time=%d", receivedCount, members.size(), t1.count());
224 :
225 8952 : if (!AreWeMember()) {
226 4 : return inv;
227 : }
228 :
229 8948 : cxxtimer::Timer t2(true);
230 8948 : dkgManager.WriteVerifiedVvecContribution(params.type, m_quorum_base_block_index, qc.proTxHash, qc.vvec);
231 :
232 8948 : bool complain = false;
233 8948 : CBLSSecretKey skContribution;
234 8948 : if (!MaybeDecrypt(*qc.contributions, *myIdx, skContribution, PROTOCOL_VERSION)) {
235 0 : logger.Batch("contribution from %s could not be decrypted", member->dmn->proTxHash.ToString());
236 0 : complain = true;
237 8948 : } else if (member->idx != myIdx && ShouldSimulateError(DKGError::type::COMPLAIN_LIE)) {
238 4 : logger.Batch("lying/complaining for %s", member->dmn->proTxHash.ToString());
239 4 : complain = true;
240 4 : }
241 :
242 8948 : if (complain) {
243 4 : member->weComplain = true;
244 12 : dkgDebugManager.UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) {
245 4 : status.statusBits.weComplain = true;
246 4 : return true;
247 : });
248 4 : return inv;
249 : }
250 :
251 8944 : logger.Batch("decrypted our contribution share. time=%d", t2.count());
252 :
253 17888 : receivedSkContributions[member->idx] = skContribution;
254 17888 : vecEncryptedContributions[member->idx] = qc.contributions;
255 8944 : LOCK(cs_pending);
256 17888 : pendingContributionVerifications.emplace_back(member->idx);
257 8944 : if (pendingContributionVerifications.size() >= 32) {
258 0 : VerifyPendingContributions();
259 0 : }
260 8944 : return inv;
261 8952 : }
262 :
263 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
264 1481 : bool CDKGSession::PreVerifyMessage(const CDKGComplaint& qc, bool& retBan) const
265 : {
266 1481 : CDKGLogger logger(*this, __func__, __LINE__);
267 :
268 1481 : retBan = false;
269 :
270 1481 : if (qc.quorumHash != m_quorum_base_block_index->GetBlockHash()) {
271 0 : logger.Batch("complaint for wrong quorum, rejecting");
272 0 : return false;
273 : }
274 :
275 1481 : auto* member = GetMember(qc.proTxHash);
276 1481 : if (member == nullptr) {
277 0 : logger.Batch("complainer not a member of this quorum, rejecting complaint");
278 0 : retBan = true;
279 0 : return false;
280 : }
281 :
282 1481 : if (qc.badMembers.size() != (size_t)params.size) {
283 0 : logger.Batch("invalid badMembers bitset size");
284 0 : retBan = true;
285 0 : return false;
286 : }
287 :
288 1481 : if (qc.complainForMembers.size() != (size_t)params.size) {
289 0 : logger.Batch("invalid complainForMembers bitset size");
290 0 : retBan = true;
291 0 : return false;
292 : }
293 :
294 1481 : if (member->complaints.size() >= 2) {
295 : // don't do any further processing if we got more than 1 valid complaints already
296 : // this is a DoS protection against members sending multiple complaints with valid signatures to us
297 : // we must bail out before any expensive BLS verification happens
298 0 : logger.Batch("dropping complaint from %s as we already got %d complaints",
299 0 : member->dmn->proTxHash.ToString(), member->complaints.size());
300 0 : return false;
301 : }
302 :
303 1481 : return true;
304 1481 : }
305 :
306 1481 : std::optional<CInv> CDKGSession::ReceiveMessage(const CDKGComplaint& qc)
307 : {
308 1481 : CDKGLogger logger(*this, __func__, __LINE__);
309 :
310 2962 : auto state = WITH_LOCK(invCs, return ReceiveMessagePreamble(qc, MsgPhase::Complaint, logger));
311 1481 : if (!state) return std::nullopt;
312 5088 : auto& [member, hash, inv, should_process] = *state;
313 1481 : if (!should_process) return inv;
314 :
315 1481 : int receivedCount = 0;
316 7450 : for (const auto i : util::irange(members.size())) {
317 5969 : const auto& m = members[i];
318 5969 : if (qc.badMembers[i]) {
319 1785 : logger.Batch("%s voted for %s to be bad", member->dmn->proTxHash.ToString(), m->dmn->proTxHash.ToString());
320 1785 : m->badMemberVotes.emplace(qc.proTxHash);
321 1785 : if (AreWeMember() && i == myIdx) {
322 277 : logger.Batch("%s voted for us to be bad", member->dmn->proTxHash.ToString());
323 277 : }
324 1785 : }
325 5969 : if (qc.complainForMembers[i]) {
326 48 : m->complaintsFromOthers.emplace(qc.proTxHash);
327 48 : m->someoneComplain = true;
328 144 : dkgDebugManager.UpdateLocalMemberStatus(params.type, quorumIndex, m->idx, [&](CDKGDebugMemberStatus& status) {
329 48 : return status.complaintsFromMembers.emplace(member->idx).second;
330 : });
331 48 : if (AreWeMember() && i == myIdx) {
332 16 : logger.Batch("%s complained about us", member->dmn->proTxHash.ToString());
333 16 : }
334 48 : }
335 5969 : if (!m->complaints.empty()) {
336 2716 : receivedCount++;
337 2716 : }
338 : }
339 :
340 1481 : logger.Batch("received and relayed complaint. received=%d", receivedCount);
341 1481 : return inv;
342 1481 : }
343 :
344 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
345 24 : bool CDKGSession::PreVerifyMessage(const CDKGJustification& qj, bool& retBan) const
346 : {
347 24 : CDKGLogger logger(*this, __func__, __LINE__);
348 :
349 24 : retBan = false;
350 :
351 24 : if (qj.quorumHash != m_quorum_base_block_index->GetBlockHash()) {
352 0 : logger.Batch("justification for wrong quorum, rejecting");
353 0 : return false;
354 : }
355 :
356 24 : auto* member = GetMember(qj.proTxHash);
357 24 : if (member == nullptr) {
358 0 : logger.Batch("justifier not a member of this quorum, rejecting justification");
359 0 : retBan = true;
360 0 : return false;
361 : }
362 :
363 24 : if (qj.contributions.empty()) {
364 0 : logger.Batch("justification with no contributions");
365 0 : retBan = true;
366 0 : return false;
367 : }
368 :
369 24 : std::unordered_set<size_t> contributionsSet;
370 132 : for (const auto& [index, skContribution] : qj.contributions) {
371 36 : if (GetMemberAtIndex(index) == nullptr) {
372 0 : logger.Batch("invalid contribution index");
373 0 : retBan = true;
374 0 : return false;
375 : }
376 :
377 36 : if (!contributionsSet.emplace(index).second) {
378 0 : logger.Batch("duplicate contribution index");
379 0 : retBan = true;
380 0 : return false;
381 : }
382 :
383 36 : if (!skContribution.IsValid()) {
384 0 : logger.Batch("invalid contribution");
385 0 : retBan = true;
386 0 : return false;
387 : }
388 : }
389 :
390 24 : if (member->justifications.size() >= 2) {
391 : // don't do any further processing if we got more than 1 valid justification already
392 : // this is a DoS protection against members sending multiple justifications with valid signatures to us
393 : // we must bail out before any expensive BLS verification happens
394 0 : logger.Batch("dropping justification from %s as we already got %d justifications",
395 0 : member->dmn->proTxHash.ToString(), member->justifications.size());
396 0 : return false;
397 : }
398 :
399 24 : return true;
400 24 : }
401 :
402 24 : std::optional<CInv> CDKGSession::ReceiveMessage(const CDKGJustification& qj)
403 : {
404 24 : CDKGLogger logger(*this, __func__, __LINE__);
405 :
406 48 : auto state = WITH_LOCK(invCs, return ReceiveMessagePreamble(qj, MsgPhase::Justification, logger));
407 24 : if (!state) return std::nullopt;
408 224 : auto& [member, hash, inv, should_process] = *state;
409 24 : if (!should_process) return inv;
410 :
411 24 : if (member->bad) {
412 : // we locally determined him to be bad (sent none or more then one contributions)
413 : // don't give him a second chance (but we relay the justification in case other members disagree)
414 0 : return inv;
415 : }
416 :
417 60 : for (const auto& [index, skContribution] : qj.contributions) {
418 36 : const auto* member2 = GetMemberAtIndex(index);
419 36 : assert(member2);
420 :
421 36 : if (member->complaintsFromOthers.count(member2->dmn->proTxHash) == 0) {
422 0 : logger.Batch("got justification from %s for %s even though he didn't complain",
423 0 : member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
424 0 : MarkBadMember(member->idx);
425 0 : }
426 : }
427 24 : if (member->bad) {
428 0 : return inv;
429 : }
430 :
431 24 : cxxtimer::Timer t1(true);
432 :
433 24 : std::list<std::future<bool>> futures;
434 96 : for (const auto& [index, skContribution] : qj.contributions) {
435 36 : const auto* member2 = GetMemberAtIndex(index);
436 36 : assert(member2);
437 :
438 : // watch out to not bail out before these async calls finish (they rely on valid references)
439 108 : futures.emplace_back(blsWorker.AsyncVerifyContributionShare(member2->id, receivedVvecs[member->idx], skContribution));
440 : }
441 24 : auto resultIt = futures.begin();
442 68 : for (const auto& [index, skContribution] : qj.contributions) {
443 36 : const auto* member2 = GetMemberAtIndex(index);
444 36 : assert(member2);
445 :
446 36 : bool result = (resultIt++)->get();
447 36 : if (!result) {
448 12 : logger.Batch(" %s did send an invalid justification for %s", member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
449 12 : MarkBadMember(member->idx);
450 12 : } else {
451 24 : logger.Batch(" %s justified for %s", member->dmn->proTxHash.ToString(), member2->dmn->proTxHash.ToString());
452 24 : if (AreWeMember() && member2->id == myId) {
453 16 : receivedSkContributions[member->idx] = skContribution;
454 8 : member->weComplain = false;
455 :
456 24 : dkgManager.WriteVerifiedSkContribution(params.type, m_quorum_base_block_index, member->dmn->proTxHash, skContribution);
457 8 : }
458 24 : member->complaintsFromOthers.erase(member2->dmn->proTxHash);
459 : }
460 : }
461 :
462 96 : auto receivedCount = std::count_if(members.cbegin(), members.cend(), [](const auto& m){
463 72 : return !m->justifications.empty();
464 : });
465 96 : auto expectedCount = std::count_if(members.cbegin(), members.cend(), [](const auto& m){
466 72 : return m->someoneComplain;
467 : });
468 :
469 24 : logger.Batch("verified justification: received=%d/%d time=%d", receivedCount, expectedCount, t1.count());
470 24 : return inv;
471 24 : }
472 :
473 : // only performs cheap verifications, but not the signature of the message. this is checked with batched verification
474 7505 : bool CDKGSession::PreVerifyMessage(const CDKGPrematureCommitment& qc, bool& retBan) const
475 : {
476 7505 : CDKGLogger logger(*this, __func__, __LINE__);
477 :
478 7505 : retBan = false;
479 :
480 7505 : if (qc.quorumHash != m_quorum_base_block_index->GetBlockHash()) {
481 0 : logger.Batch("commitment for wrong quorum, rejecting");
482 0 : return false;
483 : }
484 :
485 7505 : auto* member = GetMember(qc.proTxHash);
486 7505 : if (member == nullptr) {
487 0 : logger.Batch("committer not a member of this quorum, rejecting premature commitment");
488 0 : retBan = true;
489 0 : return false;
490 : }
491 :
492 7505 : if (qc.validMembers.size() != (size_t)params.size) {
493 0 : logger.Batch("invalid validMembers bitset size");
494 0 : retBan = true;
495 0 : return false;
496 : }
497 :
498 7505 : if (qc.CountValidMembers() < params.minSize) {
499 0 : logger.Batch("invalid validMembers count. validMembersCount=%d", qc.CountValidMembers());
500 0 : retBan = true;
501 0 : return false;
502 : }
503 7505 : if (!qc.sig.IsValid()) {
504 0 : logger.Batch("invalid membersSig");
505 0 : retBan = true;
506 0 : return false;
507 : }
508 7505 : if (!qc.quorumSig.IsValid()) {
509 1 : logger.Batch("invalid quorumSig");
510 1 : retBan = true;
511 1 : return false;
512 : }
513 :
514 8017 : for (const auto i : std::views::iota(members.size(), size_t(params.size))) {
515 : // cppcheck-suppress useStlAlgorithm
516 513 : if (qc.validMembers[i]) {
517 0 : retBan = true;
518 0 : logger.Batch("invalid validMembers bitset. bit %d should not be set", i);
519 0 : return false;
520 : }
521 : }
522 :
523 7504 : if (member->prematureCommitments.size() >= 2) {
524 : // don't do any further processing if we got more than 1 valid commitment already
525 : // this is a DoS protection against members sending multiple commitments with valid signatures to us
526 : // we must bail out before any expensive BLS verification happens
527 0 : logger.Batch("dropping commitment from %s as we already got %d commitments",
528 0 : member->dmn->proTxHash.ToString(), member->prematureCommitments.size());
529 0 : return false;
530 : }
531 :
532 7504 : return true;
533 7505 : }
534 :
535 7503 : std::optional<CInv> CDKGSession::ReceiveMessage(const CDKGPrematureCommitment& qc)
536 : {
537 7503 : CDKGLogger logger(*this, __func__, __LINE__);
538 :
539 7503 : cxxtimer::Timer t1(true);
540 :
541 7503 : logger.Batch("received premature commitment from %s. validMembers=%d", qc.proTxHash.ToString(), qc.CountValidMembers());
542 :
543 7503 : auto* member = GetMember(qc.proTxHash);
544 7503 : const uint256 hash = ::SerializeHash(qc);
545 :
546 : {
547 7503 : LOCK(invCs);
548 :
549 : // keep track of ALL commitments but only relay valid ones (or if we couldn't build the vvec)
550 : // relaying is done further down
551 7503 : prematureCommitments.emplace(hash, qc);
552 7502 : member->prematureCommitments.emplace(hash);
553 7505 : }
554 :
555 7503 : std::vector<uint16_t> memberIndexes;
556 7503 : std::vector<BLSVerificationVectorPtr> vvecs;
557 7503 : std::vector<CBLSSecretKey> skContributions;
558 7503 : BLSVerificationVectorPtr quorumVvec;
559 7503 : if (dkgManager.GetVerifiedContributions(params.type, m_quorum_base_block_index, qc.validMembers, memberIndexes, vvecs, skContributions)) {
560 7347 : quorumVvec = cache.BuildQuorumVerificationVector(::SerializeHash(memberIndexes), vvecs);
561 7347 : }
562 :
563 7503 : if (quorumVvec == nullptr) {
564 156 : logger.Batch("failed to build quorum verification vector. skipping full verification");
565 : // we might be the unlucky one who didn't receive all contributions, but we still have to relay
566 : // the premature commitment as others might be luckier
567 156 : } else {
568 : // we got all information that is needed to verify everything (even though we might not be a member of the quorum)
569 : // if any of this verification fails, we won't relay this message. This ensures that invalid messages are lost
570 : // in the network. Nodes relaying such invalid messages to us are not punished as they might have not known
571 : // all contributions. We only handle up to 2 commitments per member, so a DoS shouldn't be possible
572 :
573 7347 : if ((*quorumVvec)[0] != qc.quorumPublicKey) {
574 0 : logger.Batch("calculated quorum public key does not match");
575 0 : return std::nullopt;
576 : }
577 7347 : uint256 vvecHash = ::SerializeHash(*quorumVvec);
578 7347 : if (qc.quorumVvecHash != vvecHash) {
579 0 : logger.Batch("calculated quorum vvec hash does not match");
580 0 : return std::nullopt;
581 : }
582 :
583 7347 : CBLSPublicKey pubKeyShare = cache.BuildPubKeyShare(::SerializeHash(std::make_pair(memberIndexes, member->id)), quorumVvec, member->id);
584 7347 : if (!pubKeyShare.IsValid()) {
585 0 : logger.Batch("failed to calculate public key share");
586 0 : return std::nullopt;
587 : }
588 :
589 7347 : if (!qc.quorumSig.VerifyInsecure(pubKeyShare, qc.GetSignHash())) {
590 0 : logger.Batch("failed to verify quorumSig");
591 0 : return std::nullopt;
592 : }
593 : }
594 :
595 15006 : WITH_LOCK(invCs, validCommitments.emplace(hash));
596 :
597 7503 : CInv inv(MSG_QUORUM_PREMATURE_COMMITMENT, hash);
598 :
599 15006 : dkgDebugManager.UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [&](CDKGDebugMemberStatus& status) {
600 7503 : status.statusBits.receivedPrematureCommitment = true;
601 7503 : return true;
602 : });
603 :
604 37292 : int receivedCount = std::ranges::count_if(members, [](const auto& m) { return !m->prematureCommitments.empty(); });
605 :
606 7503 : t1.stop();
607 :
608 7503 : logger.Batch("verified premature commitment. received=%d/%d, time=%d", receivedCount, members.size(), t1.count());
609 7503 : return inv;
610 7505 : }
611 :
612 56982 : CDKGMember* CDKGSession::GetMember(const uint256& proTxHash) const
613 : {
614 56982 : auto it = membersMap.find(proTxHash);
615 56982 : if (it == membersMap.end()) {
616 0 : return nullptr;
617 : }
618 56982 : return members[it->second].get();
619 56982 : }
620 :
621 144 : CDKGMember* CDKGSession::GetMemberAtIndex(size_t index) const
622 : {
623 144 : if (index >= members.size()) return nullptr;
624 144 : return members[index].get();
625 144 : }
626 :
627 0 : std::vector<CFinalCommitment> CDKGSession::FinalizeCommitments() { return {}; }
628 :
629 0 : CFinalCommitment CDKGSession::FinalizeSingleCommitment() { return {}; }
630 :
631 13036 : bool CDKGSession::GetContribution(const uint256& hash, CDKGContribution& ret) const
632 : {
633 13036 : LOCK(invCs);
634 13036 : auto it = contributions.find(hash);
635 13036 : if (it == contributions.end()) return false;
636 6518 : ret = it->second;
637 6518 : return true;
638 13036 : }
639 :
640 1627 : bool CDKGSession::GetComplaint(const uint256& hash, CDKGComplaint& ret) const
641 : {
642 1627 : LOCK(invCs);
643 1627 : auto it = complaints.find(hash);
644 1627 : if (it == complaints.end()) return false;
645 1057 : ret = it->second;
646 1057 : return true;
647 1627 : }
648 :
649 16 : bool CDKGSession::GetJustification(const uint256& hash, CDKGJustification& ret) const
650 : {
651 16 : LOCK(invCs);
652 16 : auto it = justifications.find(hash);
653 16 : if (it == justifications.end()) return false;
654 16 : ret = it->second;
655 16 : return true;
656 16 : }
657 :
658 11235 : bool CDKGSession::GetPrematureCommitment(const uint256& hash, CDKGPrematureCommitment& ret) const
659 : {
660 11235 : LOCK(invCs);
661 11235 : auto it = prematureCommitments.find(hash);
662 11235 : if (it == prematureCommitments.end() || !validCommitments.count(hash)) return false;
663 5548 : ret = it->second;
664 5548 : return true;
665 11235 : }
666 :
667 : template <typename MsgType>
668 10461 : std::optional<CDKGSession::ReceiveMessageState> CDKGSession::ReceiveMessagePreamble(const MsgType& msg, MsgPhase phase, CDKGLogger& logger)
669 : {
670 10461 : auto* member = GetMember(msg.proTxHash);
671 10461 : if (member == nullptr) {
672 0 : logger.Batch("message from non-member %s", msg.proTxHash.ToString());
673 0 : return std::nullopt;
674 : }
675 :
676 10461 : GetDataMsg inv_type{0};
677 10461 : std::string msg_name;
678 :
679 : // Select member set, inv type, and name based on phase
680 20917 : auto& member_set = [&]() -> Uint256HashSet& {
681 10456 : switch (phase) {
682 : case MsgPhase::Contribution:
683 8951 : inv_type = MSG_QUORUM_CONTRIB;
684 8951 : msg_name = "contribution";
685 8951 : return member->contributions;
686 : case MsgPhase::Complaint:
687 1481 : inv_type = MSG_QUORUM_COMPLAINT;
688 1481 : msg_name = "complaint";
689 1481 : return member->complaints;
690 : case MsgPhase::Justification:
691 24 : inv_type = MSG_QUORUM_JUSTIFICATION;
692 24 : msg_name = "justification";
693 24 : return member->justifications;
694 : }
695 0 : assert(false);
696 10456 : }();
697 :
698 10457 : logger.Batch("received %s from %s", msg_name, msg.proTxHash.ToString());
699 :
700 10457 : if (member_set.size() >= 2) {
701 : // only relay up to 2 messages, that's enough to let the other members know about his bad behavior
702 0 : return std::nullopt;
703 : }
704 :
705 10457 : const uint256 hash = ::SerializeHash(msg);
706 10457 : member_set.emplace(hash);
707 : if constexpr (std::is_same_v<MsgType, CDKGContribution>) {
708 8952 : contributions.emplace(hash, msg);
709 : } else if constexpr (std::is_same_v<MsgType, CDKGComplaint>) {
710 1481 : complaints.emplace(hash, msg);
711 : } else if constexpr (std::is_same_v<MsgType, CDKGJustification>) {
712 24 : justifications.emplace(hash, msg);
713 : }
714 :
715 20904 : dkgDebugManager.UpdateLocalMemberStatus(params.type, quorumIndex, member->idx, [phase](CDKGDebugMemberStatus& status) {
716 10449 : switch (phase) {
717 8948 : case MsgPhase::Contribution: status.statusBits.receivedContribution = true; break;
718 1477 : case MsgPhase::Complaint: status.statusBits.receivedComplaint = true; break;
719 24 : case MsgPhase::Justification: status.statusBits.receivedJustification = true; break;
720 : }
721 10449 : return true;
722 : });
723 :
724 10457 : bool should_process{true};
725 10457 : if (member_set.size() > 1) {
726 : // don't do any further processing if we got more than 1 justification. we already relayed it,
727 : // so others know about his bad behavior
728 0 : MarkBadMember(member->idx);
729 0 : logger.Batch("%s did send multiple %ss", member->dmn->proTxHash.ToString(), msg_name);
730 0 : should_process = false;
731 0 : }
732 :
733 : // we always relay, even if further verification fails
734 10457 : return ReceiveMessageState{member, hash, CInv{inv_type, hash}, should_process};
735 10465 : }
736 :
737 : template std::optional<CDKGSession::ReceiveMessageState> CDKGSession::ReceiveMessagePreamble<CDKGContribution>(const CDKGContribution&, MsgPhase, CDKGLogger&);
738 : template std::optional<CDKGSession::ReceiveMessageState> CDKGSession::ReceiveMessagePreamble<CDKGComplaint>(const CDKGComplaint&, MsgPhase, CDKGLogger&);
739 : template std::optional<CDKGSession::ReceiveMessageState> CDKGSession::ReceiveMessagePreamble<CDKGJustification>(const CDKGJustification&, MsgPhase, CDKGLogger&);
740 :
741 802 : void CDKGSession::MarkBadMember(size_t idx)
742 : {
743 802 : auto* member = members.at(idx).get();
744 802 : if (member->bad) {
745 6 : return;
746 : }
747 1592 : dkgDebugManager.UpdateLocalMemberStatus(params.type, quorumIndex, idx, [&](CDKGDebugMemberStatus& status) {
748 796 : status.statusBits.bad = true;
749 796 : return true;
750 : });
751 796 : member->bad = true;
752 802 : }
753 : } // namespace llmq
|