|
| 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 <active/quorums.h> |
| 6 | + |
| 7 | +#include <bls/bls_ies.h> |
| 8 | +#include <bls/bls_worker.h> |
| 9 | +#include <evo/deterministicmns.h> |
| 10 | +#include <llmq/commitment.h> |
| 11 | +#include <llmq/dkgsessionmgr.h> |
| 12 | +#include <llmq/options.h> |
| 13 | +#include <llmq/quorums.h> |
| 14 | +#include <llmq/utils.h> |
| 15 | +#include <masternode/node.h> |
| 16 | +#include <masternode/sync.h> |
| 17 | + |
| 18 | +#include <chain.h> |
| 19 | +#include <chainparams.h> |
| 20 | +#include <logging.h> |
| 21 | +#include <net.h> |
| 22 | +#include <netmessagemaker.h> |
| 23 | +#include <validation.h> |
| 24 | + |
| 25 | +#include <cxxtimer.hpp> |
| 26 | + |
| 27 | +namespace llmq { |
| 28 | +QuorumParticipant::QuorumParticipant(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman, |
| 29 | + QuorumObserverParent& qman, CQuorumSnapshotManager& qsnapman, |
| 30 | + const CActiveMasternodeManager& mn_activeman, const ChainstateManager& chainman, |
| 31 | + const CMasternodeSync& mn_sync, const CSporkManager& sporkman, |
| 32 | + const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery, bool quorums_watch) : |
| 33 | + QuorumObserver(connman, dmnman, qman, qsnapman, chainman, mn_sync, sporkman, sync_map, quorums_recovery), |
| 34 | + m_bls_worker{bls_worker}, |
| 35 | + m_mn_activeman{mn_activeman}, |
| 36 | + m_quorums_watch{quorums_watch} |
| 37 | +{ |
| 38 | +} |
| 39 | + |
| 40 | +QuorumParticipant::~QuorumParticipant() = default; |
| 41 | + |
| 42 | +void QuorumParticipant::CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, |
| 43 | + gsl::not_null<const CBlockIndex*> pindexNew) const |
| 44 | +{ |
| 45 | + auto lastQuorums = m_qman.ScanQuorums(llmqParams.type, pindexNew, (size_t)llmqParams.keepOldConnections); |
| 46 | + auto deletableQuorums = GetQuorumsToDelete(llmqParams, pindexNew); |
| 47 | + |
| 48 | + const uint256 proTxHash = m_mn_activeman.GetProTxHash(); |
| 49 | + const bool watchOtherISQuorums = llmqParams.type == Params().GetConsensus().llmqTypeDIP0024InstantSend && |
| 50 | + ranges::any_of(lastQuorums, [&proTxHash](const auto& old_quorum){ return old_quorum->IsMember(proTxHash); }); |
| 51 | + |
| 52 | + for (const auto& quorum : lastQuorums) { |
| 53 | + if (utils::EnsureQuorumConnections(llmqParams, m_connman, m_sporkman, {m_dmnman, m_qsnapman, m_chainman, quorum->m_quorum_base_block_index}, |
| 54 | + m_dmnman.GetListAtChainTip(), proTxHash, /*is_masternode=*/true, m_quorums_watch)) { |
| 55 | + if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) { |
| 56 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); |
| 57 | + } |
| 58 | + } else if (watchOtherISQuorums && !quorum->IsMember(proTxHash)) { |
| 59 | + Uint256HashSet connections; |
| 60 | + const auto& cindexes = utils::CalcDeterministicWatchConnections(llmqParams.type, quorum->m_quorum_base_block_index, quorum->members.size(), 1); |
| 61 | + for (auto idx : cindexes) { |
| 62 | + connections.emplace(quorum->members[idx]->proTxHash); |
| 63 | + } |
| 64 | + if (!connections.empty()) { |
| 65 | + if (!m_connman.HasMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash())) { |
| 66 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- llmqType[%d] h[%d] adding mn inter-quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); |
| 67 | + m_connman.SetMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); |
| 68 | + m_connman.SetMasternodeQuorumRelayMembers(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); |
| 69 | + } |
| 70 | + if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) { |
| 71 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- llmqType[%d] h[%d] keeping mn inter-quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, quorum->m_quorum_base_block_index->nHeight, quorum->m_quorum_base_block_index->GetBlockHash().ToString()); |
| 72 | + } |
| 73 | + } |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + for (const auto& quorumHash : deletableQuorums) { |
| 78 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, quorumHash.ToString()); |
| 79 | + m_connman.RemoveMasternodeQuorumNodes(llmqParams.type, quorumHash); |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +bool QuorumParticipant::SetQuorumSecretKeyShare(CQuorum& quorum, Span<CBLSSecretKey> skContributions) const |
| 84 | +{ |
| 85 | + return quorum.SetSecretKeyShare(m_bls_worker.AggregateSecretKeys(skContributions), m_mn_activeman.GetProTxHash()); |
| 86 | +} |
| 87 | + |
| 88 | +size_t QuorumParticipant::GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null<const CBlockIndex*> pIndex) const |
| 89 | +{ |
| 90 | + auto mns = m_dmnman.GetListForBlock(pIndex); |
| 91 | + std::vector<uint256> vecProTxHashes; |
| 92 | + vecProTxHashes.reserve(mns.GetValidMNsCount()); |
| 93 | + mns.ForEachMN(/*onlyValid=*/true, |
| 94 | + [&](const auto& pMasternode) { vecProTxHashes.emplace_back(pMasternode.proTxHash); }); |
| 95 | + std::sort(vecProTxHashes.begin(), vecProTxHashes.end()); |
| 96 | + size_t nIndex{0}; |
| 97 | + { |
| 98 | + auto my_protx_hash = m_mn_activeman.GetProTxHash(); |
| 99 | + for (const auto i : irange::range(vecProTxHashes.size())) { |
| 100 | + // cppcheck-suppress useStlAlgorithm |
| 101 | + if (my_protx_hash == vecProTxHashes[i]) { |
| 102 | + nIndex = i; |
| 103 | + break; |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + return nIndex % quorum.qc->validMembers.size(); |
| 108 | +} |
| 109 | + |
| 110 | +MessageProcessingResult QuorumParticipant::ProcessContribQGETDATA(bool request_limit_exceeded, CDataStream& vStream, |
| 111 | + const CQuorum& quorum, CQuorumDataRequest& request, |
| 112 | + gsl::not_null<const CBlockIndex*> block_index) |
| 113 | +{ |
| 114 | + if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { |
| 115 | + assert(block_index); |
| 116 | + |
| 117 | + int memberIdx = quorum.GetMemberIndex(request.GetProTxHash()); |
| 118 | + if (memberIdx == -1) { |
| 119 | + request.SetError(CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER); |
| 120 | + return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{}; |
| 121 | + } |
| 122 | + |
| 123 | + std::vector<CBLSIESEncryptedObject<CBLSSecretKey>> vecEncrypted; |
| 124 | + if (!m_qman.GetEncryptedContributions(request.GetLLMQType(), block_index, |
| 125 | + quorum.qc->validMembers, request.GetProTxHash(), vecEncrypted)) { |
| 126 | + request.SetError(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING); |
| 127 | + return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{}; |
| 128 | + } |
| 129 | + |
| 130 | + vStream << vecEncrypted; |
| 131 | + } |
| 132 | + |
| 133 | + return {}; |
| 134 | +} |
| 135 | + |
| 136 | +MessageProcessingResult QuorumParticipant::ProcessContribQDATA(CNode& pfrom, CDataStream& vStream, |
| 137 | + CQuorum& quorum, CQuorumDataRequest& request) |
| 138 | +{ |
| 139 | + if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { |
| 140 | + if (WITH_LOCK(quorum.cs_vvec_shShare, return !quorum.HasVerificationVectorInternal() |
| 141 | + || quorum.quorumVvec->size() != size_t(quorum.params.threshold))) { |
| 142 | + // Don't bump score because we asked for it |
| 143 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- %s: No valid quorum verification vector available, from peer=%d\n", __func__, NetMsgType::QDATA, pfrom.GetId()); |
| 144 | + return {}; |
| 145 | + } |
| 146 | + |
| 147 | + int memberIdx = quorum.GetMemberIndex(request.GetProTxHash()); |
| 148 | + if (memberIdx == -1) { |
| 149 | + // Don't bump score because we asked for it |
| 150 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- %s: Not a member of the quorum, from peer=%d\n", __func__, NetMsgType::QDATA, pfrom.GetId()); |
| 151 | + return {}; |
| 152 | + } |
| 153 | + |
| 154 | + std::vector<CBLSIESEncryptedObject<CBLSSecretKey>> vecEncrypted; |
| 155 | + vStream >> vecEncrypted; |
| 156 | + |
| 157 | + std::vector<CBLSSecretKey> vecSecretKeys; |
| 158 | + vecSecretKeys.resize(vecEncrypted.size()); |
| 159 | + for (const auto i : irange::range(vecEncrypted.size())) { |
| 160 | + if (!m_mn_activeman.Decrypt(vecEncrypted[i], memberIdx, vecSecretKeys[i], PROTOCOL_VERSION)) { |
| 161 | + return MisbehavingError{10, "failed to decrypt"}; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + if (!quorum.SetSecretKeyShare(m_bls_worker.AggregateSecretKeys(vecSecretKeys), m_mn_activeman.GetProTxHash())) { |
| 166 | + return MisbehavingError{10, "invalid secret key share received"}; |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + return {}; |
| 171 | +} |
| 172 | + |
| 173 | +bool QuorumParticipant::IsMasternode() const |
| 174 | +{ |
| 175 | + // We are only initialized if masternode mode is enabled |
| 176 | + return true; |
| 177 | +} |
| 178 | + |
| 179 | +bool QuorumParticipant::IsWatching() const |
| 180 | +{ |
| 181 | + // Watch-only mode can co-exist with masternode mode |
| 182 | + return m_quorums_watch; |
| 183 | +} |
| 184 | + |
| 185 | +void QuorumParticipant::StartDataRecoveryThread(gsl::not_null<const CBlockIndex*> pIndex, CQuorumCPtr pQuorum, |
| 186 | + uint16_t nDataMaskIn) const |
| 187 | +{ |
| 188 | + bool expected = false; |
| 189 | + if (!pQuorum->fQuorumDataRecoveryThreadRunning.compare_exchange_strong(expected, true)) { |
| 190 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- Already running\n", __func__); |
| 191 | + return; |
| 192 | + } |
| 193 | + |
| 194 | + workerPool.push([pQuorum = std::move(pQuorum), pIndex, nDataMaskIn, this](int threadId) mutable { |
| 195 | + const size_t size_offset = GetQuorumRecoveryStartOffset(*pQuorum, pIndex); |
| 196 | + DataRecoveryThread(pIndex, std::move(pQuorum), nDataMaskIn, m_mn_activeman.GetProTxHash(), size_offset); |
| 197 | + }); |
| 198 | +} |
| 199 | + |
| 200 | +void QuorumParticipant::TriggerQuorumDataRecoveryThreads(gsl::not_null<const CBlockIndex*> block_index) const |
| 201 | +{ |
| 202 | + if (!m_quorums_recovery) { |
| 203 | + return; |
| 204 | + } |
| 205 | + |
| 206 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- Process block %s\n", __func__, block_index->GetBlockHash().ToString()); |
| 207 | + |
| 208 | + const uint256 proTxHash = m_mn_activeman.GetProTxHash(); |
| 209 | + |
| 210 | + for (const auto& params : Params().GetConsensus().llmqs) { |
| 211 | + auto vecQuorums = m_qman.ScanQuorums(params.type, block_index, params.keepOldConnections); |
| 212 | + const bool fWeAreQuorumTypeMember = ranges::any_of(vecQuorums, [&proTxHash](const auto& pQuorum) { return pQuorum->IsValidMember(proTxHash); }); |
| 213 | + |
| 214 | + for (auto& pQuorum : vecQuorums) { |
| 215 | + if (pQuorum->IsValidMember(proTxHash)) { |
| 216 | + uint16_t nDataMask{0}; |
| 217 | + if (!pQuorum->HasVerificationVector()) { |
| 218 | + nDataMask |= CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR; |
| 219 | + } |
| 220 | + if (!pQuorum->GetSkShare().IsValid()) { |
| 221 | + nDataMask |= CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS; |
| 222 | + } |
| 223 | + if (nDataMask != 0) { |
| 224 | + StartDataRecoveryThread(block_index, std::move(pQuorum), nDataMask); |
| 225 | + } else { |
| 226 | + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- No data needed from (%d, %s) at height %d\n", __func__, |
| 227 | + ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), block_index->nHeight); |
| 228 | + } |
| 229 | + } else { |
| 230 | + TryStartVvecSyncThread(block_index, std::move(pQuorum), fWeAreQuorumTypeMember); |
| 231 | + } |
| 232 | + } |
| 233 | + } |
| 234 | +} |
| 235 | +} // namespace llmq |
0 commit comments