LCOV - code coverage report
Current view: top level - src/llmq - dkgsession.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 369 469 78.7 %
Date: 2026-06-25 07:23:43 Functions: 47 52 90.4 %

          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

Generated by: LCOV version 1.16