diff --git a/src/Makefile.am b/src/Makefile.am index 8c6db62bfb0c..a7a0819ce088 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -151,6 +151,7 @@ endif .PHONY: FORCE check-symbols check-security # dash core # BITCOIN_CORE_H = \ + active/quorums.h \ addrdb.h \ addressindex.h \ spentindex.h \ @@ -278,6 +279,7 @@ BITCOIN_CORE_H = \ llmq/options.h \ llmq/params.h \ llmq/quorums.h \ + llmq/quorumsman.h \ llmq/signhash.h \ llmq/signing.h \ llmq/net_signing.h \ @@ -286,6 +288,7 @@ BITCOIN_CORE_H = \ llmq/types.h \ llmq/utils.h \ llmq/observer/context.h \ + llmq/observer/quorums.h \ logging.h \ logging/timer.h \ mapport.h \ @@ -479,6 +482,7 @@ libbitcoin_util_a-clientversion.$(OBJEXT): obj/build.h libbitcoin_node_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS) $(MINIUPNPC_CPPFLAGS) $(NATPMP_CPPFLAGS) $(EVENT_CFLAGS) $(EVENT_PTHREADS_CFLAGS) libbitcoin_node_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) libbitcoin_node_a_SOURCES = \ + active/quorums.cpp \ addrdb.cpp \ addressindex.cpp \ addrman.cpp \ @@ -549,12 +553,14 @@ libbitcoin_node_a_SOURCES = \ llmq/net_signing.cpp \ llmq/options.cpp \ llmq/quorums.cpp \ + llmq/quorumsman.cpp \ llmq/signhash.cpp \ llmq/signing.cpp \ llmq/signing_shares.cpp \ llmq/snapshot.cpp \ llmq/utils.cpp \ llmq/observer/context.cpp \ + llmq/observer/quorums.cpp \ mapport.cpp \ masternode/active/context.cpp \ masternode/active/notificationinterface.cpp \ diff --git a/src/active/quorums.cpp b/src/active/quorums.cpp new file mode 100644 index 000000000000..cae1280e60c3 --- /dev/null +++ b/src/active/quorums.cpp @@ -0,0 +1,235 @@ +// Copyright (c) 2018-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace llmq { +QuorumParticipant::QuorumParticipant(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman, + QuorumObserverParent& qman, CQuorumSnapshotManager& qsnapman, + const CActiveMasternodeManager& mn_activeman, const ChainstateManager& chainman, + const CMasternodeSync& mn_sync, const CSporkManager& sporkman, + const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery, bool quorums_watch) : + QuorumObserver(connman, dmnman, qman, qsnapman, chainman, mn_sync, sporkman, sync_map, quorums_recovery), + m_bls_worker{bls_worker}, + m_mn_activeman{mn_activeman}, + m_quorums_watch{quorums_watch} +{ +} + +QuorumParticipant::~QuorumParticipant() = default; + +void QuorumParticipant::CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, + gsl::not_null pindexNew) const +{ + auto lastQuorums = m_qman.ScanQuorums(llmqParams.type, pindexNew, (size_t)llmqParams.keepOldConnections); + auto deletableQuorums = GetQuorumsToDelete(llmqParams, pindexNew); + + const uint256 proTxHash = m_mn_activeman.GetProTxHash(); + const bool watchOtherISQuorums = llmqParams.type == Params().GetConsensus().llmqTypeDIP0024InstantSend && + ranges::any_of(lastQuorums, [&proTxHash](const auto& old_quorum){ return old_quorum->IsMember(proTxHash); }); + + for (const auto& quorum : lastQuorums) { + if (utils::EnsureQuorumConnections(llmqParams, m_connman, m_sporkman, {m_dmnman, m_qsnapman, m_chainman, quorum->m_quorum_base_block_index}, + m_dmnman.GetListAtChainTip(), proTxHash, /*is_masternode=*/true, m_quorums_watch)) { + if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) { + 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()); + } + } else if (watchOtherISQuorums && !quorum->IsMember(proTxHash)) { + Uint256HashSet connections; + const auto& cindexes = utils::CalcDeterministicWatchConnections(llmqParams.type, quorum->m_quorum_base_block_index, quorum->members.size(), 1); + for (auto idx : cindexes) { + connections.emplace(quorum->members[idx]->proTxHash); + } + if (!connections.empty()) { + if (!m_connman.HasMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash())) { + 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()); + m_connman.SetMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); + m_connman.SetMasternodeQuorumRelayMembers(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); + } + if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) { + 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()); + } + } + } + } + + for (const auto& quorumHash : deletableQuorums) { + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, quorumHash.ToString()); + m_connman.RemoveMasternodeQuorumNodes(llmqParams.type, quorumHash); + } +} + +bool QuorumParticipant::SetQuorumSecretKeyShare(CQuorum& quorum, Span skContributions) const +{ + return quorum.SetSecretKeyShare(m_bls_worker.AggregateSecretKeys(skContributions), m_mn_activeman.GetProTxHash()); +} + +size_t QuorumParticipant::GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null pIndex) const +{ + auto mns = m_dmnman.GetListForBlock(pIndex); + std::vector vecProTxHashes; + vecProTxHashes.reserve(mns.GetValidMNsCount()); + mns.ForEachMN(/*onlyValid=*/true, + [&](const auto& pMasternode) { vecProTxHashes.emplace_back(pMasternode.proTxHash); }); + std::sort(vecProTxHashes.begin(), vecProTxHashes.end()); + size_t nIndex{0}; + { + auto my_protx_hash = m_mn_activeman.GetProTxHash(); + for (const auto i : irange::range(vecProTxHashes.size())) { + // cppcheck-suppress useStlAlgorithm + if (my_protx_hash == vecProTxHashes[i]) { + nIndex = i; + break; + } + } + } + return nIndex % quorum.qc->validMembers.size(); +} + +MessageProcessingResult QuorumParticipant::ProcessContribQGETDATA(bool request_limit_exceeded, CDataStream& vStream, + const CQuorum& quorum, CQuorumDataRequest& request, + gsl::not_null block_index) +{ + if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { + assert(block_index); + + int memberIdx = quorum.GetMemberIndex(request.GetProTxHash()); + if (memberIdx == -1) { + request.SetError(CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER); + return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{}; + } + + std::vector> vecEncrypted; + if (!m_qman.GetEncryptedContributions(request.GetLLMQType(), block_index, + quorum.qc->validMembers, request.GetProTxHash(), vecEncrypted)) { + request.SetError(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING); + return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{}; + } + + vStream << vecEncrypted; + } + + return {}; +} + +MessageProcessingResult QuorumParticipant::ProcessContribQDATA(CNode& pfrom, CDataStream& vStream, + CQuorum& quorum, CQuorumDataRequest& request) +{ + if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { + if (WITH_LOCK(quorum.cs_vvec_shShare, return !quorum.HasVerificationVectorInternal() + || quorum.quorumVvec->size() != size_t(quorum.params.threshold))) { + // Don't bump score because we asked for it + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- %s: No valid quorum verification vector available, from peer=%d\n", __func__, NetMsgType::QDATA, pfrom.GetId()); + return {}; + } + + int memberIdx = quorum.GetMemberIndex(request.GetProTxHash()); + if (memberIdx == -1) { + // Don't bump score because we asked for it + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- %s: Not a member of the quorum, from peer=%d\n", __func__, NetMsgType::QDATA, pfrom.GetId()); + return {}; + } + + std::vector> vecEncrypted; + vStream >> vecEncrypted; + + std::vector vecSecretKeys; + vecSecretKeys.resize(vecEncrypted.size()); + for (const auto i : irange::range(vecEncrypted.size())) { + if (!m_mn_activeman.Decrypt(vecEncrypted[i], memberIdx, vecSecretKeys[i], PROTOCOL_VERSION)) { + return MisbehavingError{10, "failed to decrypt"}; + } + } + + if (!quorum.SetSecretKeyShare(m_bls_worker.AggregateSecretKeys(vecSecretKeys), m_mn_activeman.GetProTxHash())) { + return MisbehavingError{10, "invalid secret key share received"}; + } + } + + return {}; +} + +bool QuorumParticipant::IsMasternode() const +{ + // We are only initialized if masternode mode is enabled + return true; +} + +bool QuorumParticipant::IsWatching() const +{ + // Watch-only mode can co-exist with masternode mode + return m_quorums_watch; +} + +void QuorumParticipant::StartDataRecoveryThread(gsl::not_null pIndex, CQuorumCPtr pQuorum, + uint16_t nDataMaskIn) const +{ + bool expected = false; + if (!pQuorum->fQuorumDataRecoveryThreadRunning.compare_exchange_strong(expected, true)) { + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- Already running\n", __func__); + return; + } + + workerPool.push([pQuorum = std::move(pQuorum), pIndex, nDataMaskIn, this](int threadId) mutable { + const size_t size_offset = GetQuorumRecoveryStartOffset(*pQuorum, pIndex); + DataRecoveryThread(pIndex, std::move(pQuorum), nDataMaskIn, m_mn_activeman.GetProTxHash(), size_offset); + }); +} + +void QuorumParticipant::TriggerQuorumDataRecoveryThreads(gsl::not_null block_index) const +{ + if (!m_quorums_recovery) { + return; + } + + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- Process block %s\n", __func__, block_index->GetBlockHash().ToString()); + + const uint256 proTxHash = m_mn_activeman.GetProTxHash(); + + for (const auto& params : Params().GetConsensus().llmqs) { + auto vecQuorums = m_qman.ScanQuorums(params.type, block_index, params.keepOldConnections); + const bool fWeAreQuorumTypeMember = ranges::any_of(vecQuorums, [&proTxHash](const auto& pQuorum) { return pQuorum->IsValidMember(proTxHash); }); + + for (auto& pQuorum : vecQuorums) { + if (pQuorum->IsValidMember(proTxHash)) { + uint16_t nDataMask{0}; + if (!pQuorum->HasVerificationVector()) { + nDataMask |= CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR; + } + if (!pQuorum->GetSkShare().IsValid()) { + nDataMask |= CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS; + } + if (nDataMask != 0) { + StartDataRecoveryThread(block_index, std::move(pQuorum), nDataMask); + } else { + LogPrint(BCLog::LLMQ, "QuorumParticipant::%s -- No data needed from (%d, %s) at height %d\n", __func__, + ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), block_index->nHeight); + } + } else { + TryStartVvecSyncThread(block_index, std::move(pQuorum), fWeAreQuorumTypeMember); + } + } + } +} +} // namespace llmq diff --git a/src/active/quorums.h b/src/active/quorums.h new file mode 100644 index 000000000000..458b733683bd --- /dev/null +++ b/src/active/quorums.h @@ -0,0 +1,83 @@ +// Copyright (c) 2018-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_ACTIVE_QUORUMS_H +#define BITCOIN_ACTIVE_QUORUMS_H + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +class CActiveMasternodeManager; +class CBlockIndex; +class CBLSWorker; +class CConnman; +class CDeterministicMNManager; +class CDKGSessionManager; +class CNode; +class CSporkManager; +struct MessageProcessingResult; +namespace llmq { +class CQuorum; +class CQuorumDataRequest; +class CQuorumSnapshotManager; +enum class QvvecSyncMode : int8_t; +} // namespace llmq + +namespace llmq { +class QuorumParticipant final : public QuorumObserver +{ +private: + CBLSWorker& m_bls_worker; + const CActiveMasternodeManager& m_mn_activeman; + const bool m_quorums_watch{false}; + +public: + QuorumParticipant() = delete; + QuorumParticipant(const QuorumParticipant&) = delete; + QuorumParticipant& operator=(const QuorumParticipant&) = delete; + explicit QuorumParticipant(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman, + QuorumObserverParent& qman, CQuorumSnapshotManager& qsnapman, + const CActiveMasternodeManager& mn_activeman, const ChainstateManager& chainman, + const CMasternodeSync& mn_sync, const CSporkManager& sporkman, + const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery, bool quorums_watch); + ~QuorumParticipant(); + +public: + // QuorumObserver + bool IsMasternode() const override; + bool IsWatching() const override; + bool SetQuorumSecretKeyShare(CQuorum& quorum, Span skContributions) const override; + [[nodiscard]] MessageProcessingResult ProcessContribQGETDATA(bool request_limit_exceeded, CDataStream& vStream, + const CQuorum& quorum, CQuorumDataRequest& request, + gsl::not_null block_index) override; + [[nodiscard]] MessageProcessingResult ProcessContribQDATA(CNode& pfrom, CDataStream& vStream, CQuorum& quorum, + CQuorumDataRequest& request) override; + +protected: + // QuorumObserver + void CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, + gsl::not_null pindexNew) const override; + void TriggerQuorumDataRecoveryThreads(gsl::not_null block_index) const override; + +private: + /// Returns the start offset for the masternode with the given proTxHash. This offset is applied when picking data + /// recovery members of a quorum's memberlist and is calculated based on a list of all member of all active quorums + /// for the given llmqType in a way that each member should receive the same number of request if all active + /// llmqType members requests data from one llmqType quorum. + size_t GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null pIndex) const; + + void StartDataRecoveryThread(gsl::not_null pIndex, CQuorumCPtr pQuorum, uint16_t nDataMaskIn) const; +}; +} // namespace llmq + +#endif // BITCOIN_ACTIVE_QUORUMS_H diff --git a/src/chainlock/chainlock.cpp b/src/chainlock/chainlock.cpp index da2e6ee05166..bbfb3c9f83af 100644 --- a/src/chainlock/chainlock.cpp +++ b/src/chainlock/chainlock.cpp @@ -17,7 +17,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/chainlock/chainlock.h b/src/chainlock/chainlock.h index 14c9aa9a3f55..6b36954b8a96 100644 --- a/src/chainlock/chainlock.h +++ b/src/chainlock/chainlock.h @@ -38,7 +38,7 @@ namespace llmq { class CInstantSendManager; class CQuorumManager; class CSigningManager; -enum class VerifyRecSigStatus; +enum class VerifyRecSigStatus : uint8_t; class CChainLocksHandler final : public chainlock::ChainLockSignerParent { diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index ff5e1cf9d4e9..d3de81ca2031 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include CDSNotificationInterface::CDSNotificationInterface(CConnman& connman, @@ -78,7 +78,6 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con m_llmq_ctx->isman->UpdatedBlockTip(pindexNew); m_llmq_ctx->clhandler->UpdatedBlockTip(*m_llmq_ctx->isman); - m_llmq_ctx->qman->UpdatedBlockTip(pindexNew, m_connman, fInitialDownload); if (m_govman.IsValid()) { m_govman.UpdatedBlockTip(pindexNew); diff --git a/src/evo/assetlocktx.cpp b/src/evo/assetlocktx.cpp index 9624a21151cb..a26ff6f4a124 100644 --- a/src/evo/assetlocktx.cpp +++ b/src/evo/assetlocktx.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include diff --git a/src/evo/cbtx.cpp b/src/evo/cbtx.cpp index b89f743834c9..47144774d187 100644 --- a/src/evo/cbtx.cpp +++ b/src/evo/cbtx.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 574659bfff69..8f4fa535c4f1 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include diff --git a/src/evo/smldiff.cpp b/src/evo/smldiff.cpp index 7ac41e93bf5c..823b24af1572 100644 --- a/src/evo/smldiff.cpp +++ b/src/evo/smldiff.cpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 8d3c317dc815..3cdb9e7e79d8 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, diff --git a/src/init.cpp b/src/init.cpp index 9f4b238a6718..364af98cb4d5 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -292,6 +292,7 @@ void PrepareShutdown(NodeContext& node) StopRPC(); StopHTTPServer(); + if (node.observer_ctx) node.observer_ctx->Stop(); if (node.active_ctx) node.active_ctx->Stop(); if (node.peerman) node.peerman->StopHandlers(); if (node.llmq_ctx) node.llmq_ctx->Stop(); @@ -1958,9 +1959,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) fReindex = args.GetBoolArg("-reindex", false); bool fReindexChainState = args.GetBoolArg("-reindex-chainstate", false); - const bool quorums_recovery = args.GetBoolArg("-llmq-data-recovery", llmq::DEFAULT_ENABLE_QUORUM_DATA_RECOVERY); - const bool quorums_watch = args.GetBoolArg("-watchquorums", llmq::DEFAULT_WATCH_QUORUMS); - // cache size calculations CacheSizes cache_sizes = CalculateCacheSizes(args, g_enabled_filter_types.size()); @@ -2028,7 +2026,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) args.GetBoolArg("-spentindex", DEFAULT_SPENTINDEX), args.GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX), chainparams.GetConsensus(), - llmq::GetEnabledQuorumVvecSyncEntries(args), fReindexChainState, cache_sizes.block_tree_db, cache_sizes.coins_db, @@ -2036,8 +2033,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) /*block_tree_db_in_memory=*/false, /*coins_db_in_memory=*/false, /*dash_dbs_in_memory=*/false, - quorums_recovery, - quorums_watch, /*bls_threads=*/[&args]() -> int8_t { int8_t threads = args.GetIntArg("-parbls", llmq::DEFAULT_BLSCHECK_THREADS); if (threads <= 0) { @@ -2211,27 +2206,31 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!g_active_notification_interface); assert(!node.observer_ctx); - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate())); - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman)); - + const bool quorums_recovery = args.GetBoolArg("-llmq-data-recovery", llmq::DEFAULT_ENABLE_QUORUM_DATA_RECOVERY); + const bool quorums_watch = args.GetBoolArg("-watchquorums", llmq::DEFAULT_WATCH_QUORUMS); + const llmq::QvvecSyncModeMap sync_map{llmq::GetEnabledQuorumVvecSyncEntries(args)}; const util::DbWrapperParams dash_db_params{.path = args.GetDataDirNet(), .memory = false, .wipe = (fReindex || fReindexChainState)}; if (node.mn_activeman) { auto cj_server = std::make_unique(node.peerman.get(), chainman, *node.connman, *node.dmnman, *node.dstxman, *node.mn_metaman, *node.mempool, *node.mn_activeman, *node.mn_sync, *node.llmq_ctx->isman); node.active_ctx = std::make_unique(*cj_server, *node.connman, *node.dmnman, *node.govman, chainman, *node.mn_metaman, *node.mnhf_manager, *node.sporkman, *node.mempool, *node.llmq_ctx, *node.peerman, - *node.mn_activeman, *node.mn_sync, dash_db_params, quorums_watch); + *node.mn_activeman, *node.mn_sync, sync_map, dash_db_params, quorums_recovery, quorums_watch); node.peerman->AddExtraHandler(std::move(cj_server)); g_active_notification_interface = std::make_unique(*node.active_ctx, *node.mn_activeman); RegisterValidationInterface(g_active_notification_interface.get()); } else if (quorums_watch) { - node.observer_ctx = std::make_unique(*node.llmq_ctx->bls_worker, *node.dmnman, *node.mn_metaman, *node.llmq_ctx->quorum_block_processor, - *node.llmq_ctx->qman, *node.llmq_ctx->qsnapman, chainman, *node.sporkman, dash_db_params); + node.observer_ctx = std::make_unique(*node.llmq_ctx->bls_worker, *node.connman, *node.dmnman, *node.mn_metaman, *node.mn_sync, + *node.llmq_ctx->quorum_block_processor, *node.llmq_ctx->qman, *node.llmq_ctx->qsnapman, + chainman, *node.sporkman, sync_map, dash_db_params, quorums_recovery); RegisterValidationInterface(node.observer_ctx.get()); } // ********************************************************* Step 7d: Setup other Dash services + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate())); + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman)); + assert(!node.cj_walletman); if (!node.active_ctx) { // Can return nullptr if built without wallet support, must check before use @@ -2339,6 +2338,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) node.llmq_ctx->Start(); node.peerman->StartHandlers(); if (node.active_ctx) node.active_ctx->Start(*node.connman, *node.peerman); + if (node.observer_ctx) node.observer_ctx->Start(); node.scheduler->scheduleEvery(std::bind(&CNetFulfilledRequestManager::DoMaintenance, std::ref(*node.netfulfilledman)), std::chrono::minutes{1}); node.scheduler->scheduleEvery(std::bind(&CMasternodeUtils::DoMaintenance, std::ref(*node.connman), std::ref(*node.dmnman), std::ref(*node.mn_sync), node.cj_walletman.get()), std::chrono::minutes{1}); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index e9d51321466a..3b1a05e6425d 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index b8c22c814b90..783b5757ca22 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include #include diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 999ee67ddf5e..b2f3f5288a09 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -8,23 +8,20 @@ #include #include #include -#include +#include #include #include #include LLMQContext::LLMQContext(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CEvoDB& evo_db, - CSporkManager& sporkman, CTxMemPool& mempool, - const CActiveMasternodeManager* const mn_activeman, const CMasternodeSync& mn_sync, - const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, - bool quorums_recovery, bool quorums_watch, int8_t bls_threads, int64_t max_recsigs_age) : + CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params, int8_t bls_threads, int64_t max_recsigs_age) : bls_worker{std::make_shared()}, qsnapman{std::make_unique(evo_db)}, quorum_block_processor{std::make_unique(chainman.ActiveChainstate(), dmnman, evo_db, *qsnapman, bls_threads)}, qman{std::make_unique(*bls_worker, dmnman, evo_db, *quorum_block_processor, *qsnapman, - mn_activeman, chainman, mn_sync, sporkman, sync_map, db_params, - quorums_recovery, quorums_watch)}, + chainman, db_params)}, sigman{std::make_unique(*qman, db_params, max_recsigs_age)}, clhandler{std::make_unique(chainman.ActiveChainstate(), *qman, sporkman, mempool, mn_sync)}, isman{std::make_unique(*clhandler, chainman.ActiveChainstate(), *sigman, sporkman, @@ -41,12 +38,10 @@ LLMQContext::~LLMQContext() void LLMQContext::Start() { - qman->Start(); clhandler->Start(*isman); } void LLMQContext::Stop() { clhandler->Stop(); - qman->Stop(); } diff --git a/src/llmq/context.h b/src/llmq/context.h index 4547a1062be9..a31f1071f5d1 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -37,10 +37,8 @@ struct LLMQContext { LLMQContext(const LLMQContext&) = delete; LLMQContext& operator=(const LLMQContext&) = delete; explicit LLMQContext(ChainstateManager& chainman, CDeterministicMNManager& dmnman, CEvoDB& evo_db, - CSporkManager& sporkman, CTxMemPool& mempool, - const CActiveMasternodeManager* const mn_activeman, const CMasternodeSync& mn_sync, - const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, - bool quorums_recovery, bool quorums_watch, int8_t bls_threads, int64_t max_recsigs_age); + CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params, int8_t bls_threads, int64_t max_recsigs_age); ~LLMQContext(); void Start(); diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index afec0ae3ee94..394f325c4297 100644 --- a/src/llmq/ehf_signals.cpp +++ b/src/llmq/ehf_signals.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #include namespace llmq { diff --git a/src/llmq/observer/context.cpp b/src/llmq/observer/context.cpp index 241f764dabbc..bc111c068a3d 100644 --- a/src/llmq/observer/context.cpp +++ b/src/llmq/observer/context.cpp @@ -6,25 +6,40 @@ #include #include -#include +#include +#include namespace llmq { -ObserverContext::ObserverContext(CBLSWorker& bls_worker, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, +ObserverContext::ObserverContext(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman, + CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumManager& qman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, - const CSporkManager& sporkman, const util::DbWrapperParams& db_params) : + const CSporkManager& sporkman, const llmq::QvvecSyncModeMap& sync_map, + const util::DbWrapperParams& db_params, bool quorums_recovery) : m_qman{qman}, dkgdbgman{std::make_unique()}, qdkgsman{std::make_unique(bls_worker, dmnman, *dkgdbgman, mn_metaman, qblockman, qsnapman, /*mn_activeman=*/nullptr, chainman, sporkman, db_params, - /*quorums_watch=*/true)} + /*quorums_watch=*/true)}, + qman_handler{std::make_unique(connman, dmnman, qman, qsnapman, chainman, mn_sync, sporkman, + sync_map, quorums_recovery)} { - m_qman.ConnectManager(qdkgsman.get()); + m_qman.ConnectManagers(qman_handler.get(), qdkgsman.get()); } ObserverContext::~ObserverContext() { - m_qman.DisconnectManager(); + m_qman.DisconnectManagers(); +} + +void ObserverContext::Start() +{ + qman_handler->Start(); +} + +void ObserverContext::Stop() +{ + qman_handler->Stop(); } void ObserverContext::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) @@ -33,5 +48,6 @@ void ObserverContext::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlock return; qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload); + qman_handler->UpdatedBlockTip(pindexNew, fInitialDownload); } } // namespace llmq diff --git a/src/llmq/observer/context.h b/src/llmq/observer/context.h index ab7e6ec98e6a..97ff248bbf97 100644 --- a/src/llmq/observer/context.h +++ b/src/llmq/observer/context.h @@ -5,15 +5,19 @@ #ifndef BITCOIN_LLMQ_OBSERVER_CONTEXT_H #define BITCOIN_LLMQ_OBSERVER_CONTEXT_H +#include + #include #include class CBLSWorker; class CBlockIndex; +class CConnman; class CDeterministicMNManager; class ChainstateManager; class CMasternodeMetaMan; +class CMasternodeSync; class CSporkManager; namespace llmq { class CDKGDebugManager; @@ -21,6 +25,7 @@ class CDKGSessionManager; class CQuorumBlockProcessor; class CQuorumManager; class CQuorumSnapshotManager; +class QuorumObserver; } // namespace llmq namespace util { struct DbWrapperParams; @@ -35,12 +40,16 @@ struct ObserverContext final : public CValidationInterface { ObserverContext() = delete; ObserverContext(const ObserverContext&) = delete; ObserverContext& operator=(const ObserverContext&) = delete; - ObserverContext(CBLSWorker& bls_worker, CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, - llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumManager& qman, - llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, - const CSporkManager& sporkman, const util::DbWrapperParams& db_params); + ObserverContext(CBLSWorker& bls_worker, CConnman& connman, CDeterministicMNManager& dmnman, + CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, llmq::CQuorumBlockProcessor& qblockman, + llmq::CQuorumManager& qman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, + const CSporkManager& sporkman, const llmq::QvvecSyncModeMap& sync_map, + const util::DbWrapperParams& db_params, bool quorums_recovery); ~ObserverContext(); + void Start(); + void Stop(); + protected: // CValidationInterface void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; @@ -48,6 +57,9 @@ struct ObserverContext final : public CValidationInterface { public: const std::unique_ptr dkgdbgman; const std::unique_ptr qdkgsman; + +private: + const std::unique_ptr qman_handler; }; } // namespace llmq diff --git a/src/llmq/observer/quorums.cpp b/src/llmq/observer/quorums.cpp new file mode 100644 index 000000000000..cd37b14f86d9 --- /dev/null +++ b/src/llmq/observer/quorums.cpp @@ -0,0 +1,373 @@ +// Copyright (c) 2018-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace llmq { +QuorumObserver::QuorumObserver(CConnman& connman, CDeterministicMNManager& dmnman, QuorumObserverParent& qman, + CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, + const CMasternodeSync& mn_sync, const CSporkManager& sporkman, + const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery) : + m_connman{connman}, + m_dmnman{dmnman}, + m_qman{qman}, + m_qsnapman{qsnapman}, + m_chainman{chainman}, + m_mn_sync{mn_sync}, + m_sporkman{sporkman}, + m_quorums_recovery{quorums_recovery}, + m_sync_map{sync_map} +{ + quorumThreadInterrupt.reset(); +} + +QuorumObserver::~QuorumObserver() +{ + Stop(); +} + +void QuorumObserver::Start() +{ + int workerCount = std::thread::hardware_concurrency() / 2; + workerCount = std::clamp(workerCount, 1, 4); + workerPool.resize(workerCount); + RenameThreadPool(workerPool, "q-mngr"); +} + +void QuorumObserver::Stop() +{ + quorumThreadInterrupt(); + workerPool.clear_queue(); + workerPool.stop(true); +} + +void QuorumObserver::UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload) const +{ + if (!pindexNew) return; + if (!m_mn_sync.IsBlockchainSynced()) return; + + for (const auto& params : Params().GetConsensus().llmqs) { + CheckQuorumConnections(params, pindexNew); + } + + // Cleanup expired data requests + m_qman.CleanupExpiredDataRequests(); + + TriggerQuorumDataRecoveryThreads(pindexNew); + StartCleanupOldQuorumDataThread(pindexNew); +} + +Uint256HashSet QuorumObserver::GetQuorumsToDelete(const Consensus::LLMQParams& llmqParams, + gsl::not_null pindexNew) const +{ + auto connmanQuorumsToDelete = m_connman.GetMasternodeQuorums(llmqParams.type); + + // don't remove connections for the currently in-progress DKG round + if (IsQuorumRotationEnabled(llmqParams, pindexNew)) { + int cycleIndexTipHeight = pindexNew->nHeight % llmqParams.dkgInterval; + int cycleQuorumBaseHeight = pindexNew->nHeight - cycleIndexTipHeight; + std::stringstream ss; + for (const auto quorumIndex : irange::range(llmqParams.signingActiveQuorumCount)) { + if (quorumIndex <= cycleIndexTipHeight) { + int curDkgHeight = cycleQuorumBaseHeight + quorumIndex; + auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); + ss << curDkgHeight << ":" << curDkgBlock.ToString() << " | "; + connmanQuorumsToDelete.erase(curDkgBlock); + } + } + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- llmqType[%d] h[%d] keeping mn quorum connections for rotated quorums: [%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, ss.str()); + } else { + int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % llmqParams.dkgInterval); + auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); + connmanQuorumsToDelete.erase(curDkgBlock); + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, curDkgHeight, curDkgBlock.ToString()); + } + + return connmanQuorumsToDelete; +} + +void QuorumObserver::CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, + gsl::not_null pindexNew) const +{ + auto lastQuorums = m_qman.ScanQuorums(llmqParams.type, pindexNew, (size_t)llmqParams.keepOldConnections); + auto deletableQuorums = GetQuorumsToDelete(llmqParams, pindexNew); + + for (const auto& quorum : lastQuorums) { + if (utils::EnsureQuorumConnections(llmqParams, m_connman, m_sporkman, {m_dmnman, m_qsnapman, m_chainman, quorum->m_quorum_base_block_index}, + m_dmnman.GetListAtChainTip(), /*myProTxHash=*/uint256{}, /*is_masternode=*/false, /*quorums_watch=*/true)) { + if (deletableQuorums.erase(quorum->qc->quorumHash) > 0) { + LogPrint(BCLog::LLMQ, "QuorumObserver::%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()); + } + } + } + + for (const auto& quorumHash : deletableQuorums) { + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, quorumHash.ToString()); + m_connman.RemoveMasternodeQuorumNodes(llmqParams.type, quorumHash); + } +} + +bool QuorumObserver::SetQuorumSecretKeyShare(CQuorum& quorum, Span skContributions) const +{ + // Watch-only nodes cannot work with secret keys + return false; +} + +MessageProcessingResult QuorumObserver::ProcessContribQGETDATA(bool request_limit_exceeded, CDataStream& vStream, + const CQuorum& quorum, CQuorumDataRequest& request, + gsl::not_null block_index) +{ + // Watch-only nodes cannot provide encrypted contributions + if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { + request.SetError(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING); + return request_limit_exceeded ? MisbehavingError{25, "request limit exceeded"} : MessageProcessingResult{}; + } + return {}; +} + +MessageProcessingResult QuorumObserver::ProcessContribQDATA(CNode& pfrom, CDataStream& vStream, + CQuorum& quorum, CQuorumDataRequest& request) +{ + // Watch-only nodes ignore encrypted contributions + return {}; +} + +bool QuorumObserver::IsMasternode() const +{ + // Watch-only nodes are not masternodes + return false; +} + +bool QuorumObserver::IsWatching() const +{ + // We are only initialized if watch-only mode is enabled + return true; +} + +void QuorumObserver::DataRecoveryThread(gsl::not_null block_index, CQuorumCPtr pQuorum, + uint16_t data_mask, const uint256& protx_hash, size_t start_offset) const +{ + size_t nTries{0}; + uint16_t nDataMask{data_mask}; + int64_t nTimeLastSuccess{0}; + uint256* pCurrentMemberHash{nullptr}; + std::vector vecMemberHashes; + const int64_t nRequestTimeout{10}; + + auto printLog = [&](const std::string& strMessage) { + const std::string strMember{pCurrentMemberHash == nullptr ? "nullptr" : pCurrentMemberHash->ToString()}; + LogPrint(BCLog::LLMQ, "QuorumObserver::DataRecoveryThread -- %s - for llmqType %d, quorumHash %s, nDataMask (%d/%d), pCurrentMemberHash %s, nTries %d\n", + strMessage, ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), nDataMask, data_mask, strMember, nTries); + }; + printLog("Start"); + + while (!m_mn_sync.IsBlockchainSynced() && !quorumThreadInterrupt) { + quorumThreadInterrupt.sleep_for(std::chrono::seconds(nRequestTimeout)); + } + + if (quorumThreadInterrupt) { + printLog("Aborted"); + return; + } + + vecMemberHashes.reserve(pQuorum->qc->validMembers.size()); + for (auto& member : pQuorum->members) { + if (pQuorum->IsValidMember(member->proTxHash) && member->proTxHash != protx_hash) { + vecMemberHashes.push_back(member->proTxHash); + } + } + std::sort(vecMemberHashes.begin(), vecMemberHashes.end()); + + printLog("Try to request"); + + while (nDataMask > 0 && !quorumThreadInterrupt) { + if (nDataMask & llmq::CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR && + pQuorum->HasVerificationVector()) { + nDataMask &= ~llmq::CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR; + printLog("Received quorumVvec"); + } + + if (nDataMask & llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS && pQuorum->GetSkShare().IsValid()) { + nDataMask &= ~llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS; + printLog("Received skShare"); + } + + if (nDataMask == 0) { + printLog("Success"); + break; + } + + if ((GetTime().count() - nTimeLastSuccess) > nRequestTimeout) { + if (nTries >= vecMemberHashes.size()) { + printLog("All tried but failed"); + break; + } + // Access the member list of the quorum with the calculated offset applied to balance the load equally + pCurrentMemberHash = &vecMemberHashes[(start_offset + nTries++) % vecMemberHashes.size()]; + if (m_qman.IsDataRequestPending(*pCurrentMemberHash, /*we_requested=*/true, pQuorum->qc->quorumHash, + pQuorum->qc->llmqType)) { + printLog("Already asked"); + continue; + } + // Sleep a bit depending on the start offset to balance out multiple requests to same masternode + quorumThreadInterrupt.sleep_for(std::chrono::milliseconds(start_offset * 100)); + nTimeLastSuccess = GetTime().count(); + m_connman.AddPendingMasternode(*pCurrentMemberHash); + printLog("Connect"); + } + + m_connman.ForEachNode([&](CNode* pNode) { + auto verifiedProRegTxHash = pNode->GetVerifiedProRegTxHash(); + if (pCurrentMemberHash == nullptr || verifiedProRegTxHash != *pCurrentMemberHash) { + return; + } + + if (m_qman.RequestQuorumData(pNode, m_connman, *pQuorum, nDataMask, protx_hash)) { + nTimeLastSuccess = GetTime().count(); + printLog("Requested"); + } else { + const auto status = m_qman.GetDataRequestStatus(*pCurrentMemberHash, /*we_requested=*/true, + pQuorum->qc->quorumHash, pQuorum->qc->llmqType); + switch (status) { + case DataRequestStatus::NotFound: + printLog("Failed"); + pNode->fDisconnect = true; + pCurrentMemberHash = nullptr; + return; + case DataRequestStatus::Processed: + printLog("Processed"); + pNode->fDisconnect = true; + pCurrentMemberHash = nullptr; + return; + case DataRequestStatus::Pending: + printLog("Waiting"); + return; + } + } + }); + quorumThreadInterrupt.sleep_for(std::chrono::seconds(1)); + } + pQuorum->fQuorumDataRecoveryThreadRunning = false; + printLog("Done"); +} + +void QuorumObserver::StartVvecSyncThread(gsl::not_null block_index, CQuorumCPtr pQuorum) const +{ + bool expected = false; + if (!pQuorum->fQuorumDataRecoveryThreadRunning.compare_exchange_strong(expected, true)) { + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- Already running\n", __func__); + return; + } + + workerPool.push([pQuorum = std::move(pQuorum), block_index, this](int threadId) mutable { + DataRecoveryThread(block_index, std::move(pQuorum), CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR, + /*protx_hash=*/uint256(), /*start_offset=*/0); + }); +} + +void QuorumObserver::TriggerQuorumDataRecoveryThreads(gsl::not_null block_index) const +{ + if (!m_quorums_recovery) { + return; + } + + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- Process block %s\n", __func__, block_index->GetBlockHash().ToString()); + for (const auto& params : Params().GetConsensus().llmqs) { + auto vecQuorums = m_qman.ScanQuorums(params.type, block_index, params.keepOldConnections); + for (auto& pQuorum : vecQuorums) { + TryStartVvecSyncThread(block_index, std::move(pQuorum), /*fWeAreQuorumTypeMember=*/false); + } + } +} + +void QuorumObserver::TryStartVvecSyncThread(gsl::not_null block_index, CQuorumCPtr pQuorum, + bool fWeAreQuorumTypeMember) const +{ + if (pQuorum->fQuorumDataRecoveryThreadRunning) return; + + const bool fSyncForTypeEnabled = m_sync_map.count(pQuorum->qc->llmqType) > 0; + const QvvecSyncMode syncMode = fSyncForTypeEnabled ? m_sync_map.at(pQuorum->qc->llmqType) : QvvecSyncMode::Invalid; + const bool fSyncCurrent = syncMode == QvvecSyncMode::Always || + (syncMode == QvvecSyncMode::OnlyIfTypeMember && fWeAreQuorumTypeMember); + + if ((fSyncForTypeEnabled && fSyncCurrent) && !pQuorum->HasVerificationVector()) { + StartVvecSyncThread(block_index, std::move(pQuorum)); + } else { + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- No data needed from (%d, %s) at height %d\n", __func__, + ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), block_index->nHeight); + } +} + +void QuorumObserver::StartCleanupOldQuorumDataThread(gsl::not_null pIndex) const +{ + // Note: this function is CPU heavy and we don't want it to be running during DKGs. + // The largest dkgMiningWindowStart for a related quorum type is 42 (LLMQ_60_75). + // At the same time most quorums use dkgInterval = 24 so the next DKG for them + // (after block 576 + 42) will start at block 576 + 24 * 2. That's only a 6 blocks + // window and it's better to have more room so we pick next cycle. + // dkgMiningWindowStart for small quorums is 10 i.e. a safe block to start + // these calculations is at height 576 + 24 * 2 + 10 = 576 + 58. + if (pIndex->nHeight % 576 != 58) { + return; + } + + cxxtimer::Timer t(/*start=*/ true); + LogPrint(BCLog::LLMQ, "QuorumObserver::%s -- start\n", __func__); + + // do not block the caller thread + workerPool.push([pIndex, t, this](int threadId) { + std::set dbKeysToSkip; + + if (LOCK(cs_cleanup); cleanupQuorumsCache.empty()) { + utils::InitQuorumsCache(cleanupQuorumsCache, m_chainman.GetConsensus(), /*limit_by_connections=*/false); + } + for (const auto& params : Params().GetConsensus().llmqs) { + if (quorumThreadInterrupt) { + break; + } + LOCK(cs_cleanup); + auto& cache = cleanupQuorumsCache[params.type]; + const CBlockIndex* pindex_loop{pIndex}; + std::set quorum_keys; + while (pindex_loop != nullptr && pIndex->nHeight - pindex_loop->nHeight < params.max_store_depth()) { + uint256 quorum_key; + if (cache.get(pindex_loop->GetBlockHash(), quorum_key)) { + quorum_keys.insert(quorum_key); + if (quorum_keys.size() >= static_cast(params.keepOldKeys)) break; // extra safety belt + } + pindex_loop = pindex_loop->pprev; + } + for (const auto& pQuorum : m_qman.ScanQuorums(params.type, pIndex, params.keepOldKeys - quorum_keys.size())) { + const uint256 quorum_key = MakeQuorumKey(*pQuorum); + quorum_keys.insert(quorum_key); + cache.insert(pQuorum->m_quorum_base_block_index->GetBlockHash(), quorum_key); + } + dbKeysToSkip.merge(quorum_keys); + } + + if (!quorumThreadInterrupt) { + m_qman.CleanupOldQuorumData(dbKeysToSkip); + } + + LogPrint(BCLog::LLMQ, "QuorumObserver::StartCleanupOldQuorumDataThread -- done. time=%d\n", t.count()); + }); +} +} // namespace llmq diff --git a/src/llmq/observer/quorums.h b/src/llmq/observer/quorums.h new file mode 100644 index 000000000000..020dc62e7fa5 --- /dev/null +++ b/src/llmq/observer/quorums.h @@ -0,0 +1,129 @@ +// Copyright (c) 2018-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_LLMQ_OBSERVER_QUORUMS_H +#define BITCOIN_LLMQ_OBSERVER_QUORUMS_H + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +class CConnman; +class CDataStream; +class CDeterministicMNManager; +class CMasternodeSync; +class CNode; +class CSporkManager; +struct MessageProcessingResult; +namespace llmq { +class CQuorumDataRequest; +class CQuorumSnapshotManager; +} // namespace llmq + +namespace llmq { +enum class DataRequestStatus : uint8_t { + NotFound, + Pending, + Processed, +}; + +class QuorumObserverParent +{ +public: + virtual ~QuorumObserverParent() = default; + virtual bool GetEncryptedContributions(Consensus::LLMQType llmq_type, const CBlockIndex* block_index, + const std::vector& valid_members, const uint256& protx_hash, + std::vector>& vec_enc) const = 0; + virtual bool IsDataRequestPending(const uint256& proRegTx, bool we_requested, const uint256& quorumHash, + Consensus::LLMQType llmqType) const = 0; + virtual bool RequestQuorumData(CNode* pfrom, CConnman& connman, const CQuorum& quorum, uint16_t nDataMask, + const uint256& proTxHash) const = 0; + virtual DataRequestStatus GetDataRequestStatus(const uint256& proRegTx, bool we_requested, + const uint256& quorumHash, Consensus::LLMQType llmqType) const = 0; + virtual std::vector ScanQuorums(Consensus::LLMQType llmqType, + gsl::not_null pindexStart, + size_t nCountRequested) const = 0; + virtual void CleanupExpiredDataRequests() const = 0; + virtual void CleanupOldQuorumData(const std::set& dbKeysToSkip) const = 0; +}; + +class QuorumObserver +{ +protected: + CConnman& m_connman; + CDeterministicMNManager& m_dmnman; + QuorumObserverParent& m_qman; + CQuorumSnapshotManager& m_qsnapman; + const ChainstateManager& m_chainman; + const CMasternodeSync& m_mn_sync; + const CSporkManager& m_sporkman; + const bool m_quorums_recovery{false}; + const llmq::QvvecSyncModeMap m_sync_map; + +protected: + mutable Mutex cs_cleanup; + mutable std::map> cleanupQuorumsCache GUARDED_BY(cs_cleanup); + + mutable ctpl::thread_pool workerPool; + mutable CThreadInterrupt quorumThreadInterrupt; + +public: + QuorumObserver() = delete; + QuorumObserver(const QuorumObserver&) = delete; + QuorumObserver& operator=(const QuorumObserver&) = delete; + explicit QuorumObserver(CConnman& connman, CDeterministicMNManager& dmnman, QuorumObserverParent& qman, + CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, + const CMasternodeSync& mn_sync, const CSporkManager& sporkman, + const llmq::QvvecSyncModeMap& sync_map, bool quorums_recovery); + virtual ~QuorumObserver(); + + void Start(); + void Stop(); + + void UpdatedBlockTip(const CBlockIndex* pindexNew, bool fInitialDownload) const; + +public: + virtual bool SetQuorumSecretKeyShare(CQuorum& quorum, Span skContributions) const; + [[nodiscard]] virtual MessageProcessingResult ProcessContribQGETDATA( + bool request_limit_exceeded, CDataStream& vStream, const CQuorum& quorum, CQuorumDataRequest& request, + gsl::not_null block_index); + [[nodiscard]] virtual MessageProcessingResult ProcessContribQDATA( + CNode& pfrom, CDataStream& vStream, CQuorum& quorum, CQuorumDataRequest& request); + virtual bool IsMasternode() const; + virtual bool IsWatching() const; + +protected: + virtual void CheckQuorumConnections(const Consensus::LLMQParams& llmqParams, + gsl::not_null pindexNew) const; + virtual void TriggerQuorumDataRecoveryThreads(gsl::not_null pIndex) const; + +protected: + Uint256HashSet GetQuorumsToDelete(const Consensus::LLMQParams& llmqParams, + gsl::not_null pindexNew) const; + + void DataRecoveryThread(gsl::not_null block_index, CQuorumCPtr quorum, uint16_t data_mask, + const uint256& protx_hash, size_t start_offset) const; + void StartVvecSyncThread(gsl::not_null block_index, CQuorumCPtr pQuorum) const; + void TryStartVvecSyncThread(gsl::not_null block_index, CQuorumCPtr pQuorum, + bool fWeAreQuorumTypeMember) const; + + void StartCleanupOldQuorumDataThread(gsl::not_null pIndex) const; +}; +} // namespace llmq + +#endif // BITCOIN_LLMQ_OBSERVER_QUORUMS_H diff --git a/src/llmq/options.h b/src/llmq/options.h index 41347d8f3239..7c644be5f7ef 100644 --- a/src/llmq/options.h +++ b/src/llmq/options.h @@ -21,7 +21,7 @@ enum class LLMQType : uint8_t; } // namespace Consensus namespace llmq { -enum class QvvecSyncMode { +enum class QvvecSyncMode : int8_t { Invalid = -1, Always = 0, OnlyIfTypeMember = 1, diff --git a/src/llmq/quorums.cpp b/src/llmq/quorums.cpp index e8f49362c26f..51108903cd70 100644 --- a/src/llmq/quorums.cpp +++ b/src/llmq/quorums.cpp @@ -1,40 +1,21 @@ // Copyright (c) 2018-2025 The Dash Core developers -// Distributed under the MIT/X11 software license, see the accompanying +// Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -#include +#include -namespace llmq -{ +#include -static const std::string DB_QUORUM_SK_SHARE = "q_Qsk"; -static const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec"; +namespace llmq { +const std::string DB_QUORUM_SK_SHARE = "q_Qsk"; +const std::string DB_QUORUM_QUORUM_VVEC = "q_Qqvvec"; -static uint256 MakeQuorumKey(const CQuorum& q) +uint256 MakeQuorumKey(const CQuorum& q) { CHashWriter hw(SER_NETWORK, 0); hw << q.params.type; @@ -45,6 +26,53 @@ static uint256 MakeQuorumKey(const CQuorum& q) return hw.GetHash(); } +void DataCleanupHelper(CDBWrapper& db, const std::set& skip_list, bool compact) +{ + const auto prefixes = {DB_QUORUM_QUORUM_VVEC, DB_QUORUM_SK_SHARE}; + + CDBBatch batch(db); + std::unique_ptr pcursor(db.NewIterator()); + + for (const auto& prefix : prefixes) { + auto start = std::make_tuple(prefix, uint256()); + pcursor->Seek(start); + + int count{0}; + while (pcursor->Valid()) { + decltype(start) k; + + if (!pcursor->GetKey(k) || std::get<0>(k) != prefix) { + break; + } + + pcursor->Next(); + + if (skip_list.find(std::get<1>(k)) != skip_list.end()) continue; + + ++count; + batch.Erase(k); + + if (batch.SizeEstimate() >= (1 << 24)) { + db.WriteBatch(batch); + batch.Clear(); + } + } + + db.WriteBatch(batch); + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s removed %d\n", __func__, prefix, count); + } + + pcursor.reset(); + + if (compact) { + // Avoid using this on regular cleanups, use on db migrations only + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- compact start\n", __func__); + db.CompactFull(); + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- compact end\n", __func__); + } +} + std::string CQuorumDataRequest::GetErrorString() const { switch (nError) { @@ -94,9 +122,9 @@ bool CQuorum::SetVerificationVector(const std::vector& quorumVecI return true; } -bool CQuorum::SetSecretKeyShare(const CBLSSecretKey& secretKeyShare, const CActiveMasternodeManager& mn_activeman) +bool CQuorum::SetSecretKeyShare(const CBLSSecretKey& secretKeyShare, const uint256& protx_hash) { - if (!secretKeyShare.IsValid() || (secretKeyShare.GetPublicKey() != GetPubKeyShare(GetMemberIndex(mn_activeman.GetProTxHash())))) { + if (protx_hash.IsNull() || !secretKeyShare.IsValid() || (secretKeyShare.GetPublicKey() != GetPubKeyShare(GetMemberIndex(protx_hash)))) { return false; } LOCK(cs_vvec_shShare); @@ -202,1084 +230,4 @@ bool CQuorum::ReadContributions(const CDBWrapper& db) return true; } - -CQuorumManager::CQuorumManager(CBLSWorker& _blsWorker, CDeterministicMNManager& dmnman, CEvoDB& _evoDb, - CQuorumBlockProcessor& _quorumBlockProcessor, CQuorumSnapshotManager& qsnapman, - const CActiveMasternodeManager* const mn_activeman, const ChainstateManager& chainman, - const CMasternodeSync& mn_sync, const CSporkManager& sporkman, - const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, - bool quorums_recovery, bool quorums_watch) : - blsWorker{_blsWorker}, - m_dmnman{dmnman}, - quorumBlockProcessor{_quorumBlockProcessor}, - m_qsnapman{qsnapman}, - m_mn_activeman{mn_activeman}, - m_chainman{chainman}, - m_mn_sync{mn_sync}, - m_sporkman{sporkman}, - m_sync_map{sync_map}, - m_quorums_recovery{quorums_recovery}, - m_quorums_watch{quorums_watch}, - db{util::MakeDbWrapper({db_params.path / "llmq" / "quorumdb", db_params.memory, db_params.wipe, /*cache_size=*/1 << 20})} -{ - utils::InitQuorumsCache(mapQuorumsCache, m_chainman.GetConsensus(), /*limit_by_connections=*/false); - quorumThreadInterrupt.reset(); - MigrateOldQuorumDB(_evoDb); -} - -CQuorumManager::~CQuorumManager() -{ - Stop(); -} - -void CQuorumManager::Start() -{ - int workerCount = std::thread::hardware_concurrency() / 2; - workerCount = std::clamp(workerCount, 1, 4); - workerPool.resize(workerCount); - RenameThreadPool(workerPool, "q-mngr"); -} - -void CQuorumManager::Stop() -{ - quorumThreadInterrupt(); - workerPool.clear_queue(); - workerPool.stop(true); -} - -void CQuorumManager::TriggerQuorumDataRecoveryThreads(CConnman& connman, gsl::not_null pIndex) const -{ - if ((m_mn_activeman == nullptr && !m_quorums_watch) || !m_quorums_recovery) { - return; - } - - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Process block %s\n", __func__, pIndex->GetBlockHash().ToString()); - - for (const auto& params : Params().GetConsensus().llmqs) { - auto vecQuorums = ScanQuorums(params.type, pIndex, params.keepOldConnections); - - // First check if we are member of any quorum of this type - const uint256 proTxHash = m_mn_activeman != nullptr ? m_mn_activeman->GetProTxHash() : uint256(); - - bool fWeAreQuorumTypeMember = ranges::any_of(vecQuorums, [&proTxHash](const auto& pQuorum) { - return pQuorum->IsValidMember(proTxHash); - }); - - for (auto& pQuorum : vecQuorums) { - // If there is already a thread running for this specific quorum skip it - if (pQuorum->fQuorumDataRecoveryThreadRunning) { - continue; - } - - uint16_t nDataMask{0}; - const bool fWeAreQuorumMember = pQuorum->IsValidMember(proTxHash); - const bool fSyncForTypeEnabled = m_sync_map.count(pQuorum->qc->llmqType) > 0; - const QvvecSyncMode syncMode = fSyncForTypeEnabled ? m_sync_map.at(pQuorum->qc->llmqType) - : QvvecSyncMode::Invalid; - const bool fSyncCurrent = syncMode == QvvecSyncMode::Always || (syncMode == QvvecSyncMode::OnlyIfTypeMember && fWeAreQuorumTypeMember); - - if ((fWeAreQuorumMember || (fSyncForTypeEnabled && fSyncCurrent)) && !pQuorum->HasVerificationVector()) { - nDataMask |= llmq::CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR; - } - - if (fWeAreQuorumMember && !pQuorum->GetSkShare().IsValid()) { - nDataMask |= llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS; - } - - if (nDataMask == 0) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- No data needed from (%d, %s) at height %d\n", - __func__, ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), pIndex->nHeight); - continue; - } - - // Finally start the thread which triggers the requests for this quorum - StartQuorumDataRecoveryThread(connman, std::move(pQuorum), pIndex, nDataMask); - } - } -} - -void CQuorumManager::UpdatedBlockTip(const CBlockIndex* pindexNew, CConnman& connman, bool fInitialDownload) const -{ - if (!pindexNew) return; - if (!m_mn_sync.IsBlockchainSynced()) return; - - for (const auto& params : Params().GetConsensus().llmqs) { - CheckQuorumConnections(connman, params, pindexNew); - } - - if (m_mn_activeman != nullptr || m_quorums_watch) { - // Cleanup expired data requests - LOCK(cs_data_requests); - auto it = mapQuorumDataRequests.begin(); - while (it != mapQuorumDataRequests.end()) { - if (it->second.IsExpired(/*add_bias=*/true)) { - it = mapQuorumDataRequests.erase(it); - } else { - ++it; - } - } - } - - TriggerQuorumDataRecoveryThreads(connman, pindexNew); - StartCleanupOldQuorumDataThread(pindexNew); -} - -void CQuorumManager::CheckQuorumConnections(CConnman& connman, const Consensus::LLMQParams& llmqParams, - gsl::not_null pindexNew) const -{ - if (m_mn_activeman == nullptr && !m_quorums_watch) return; - - auto lastQuorums = ScanQuorums(llmqParams.type, pindexNew, (size_t)llmqParams.keepOldConnections); - - auto connmanQuorumsToDelete = connman.GetMasternodeQuorums(llmqParams.type); - - // don't remove connections for the currently in-progress DKG round - if (IsQuorumRotationEnabled(llmqParams, pindexNew)) { - int cycleIndexTipHeight = pindexNew->nHeight % llmqParams.dkgInterval; - int cycleQuorumBaseHeight = pindexNew->nHeight - cycleIndexTipHeight; - std::stringstream ss; - for (const auto quorumIndex : irange::range(llmqParams.signingActiveQuorumCount)) { - if (quorumIndex <= cycleIndexTipHeight) { - int curDkgHeight = cycleQuorumBaseHeight + quorumIndex; - auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); - ss << curDkgHeight << ":" << curDkgBlock.ToString() << " | "; - connmanQuorumsToDelete.erase(curDkgBlock); - } - } - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for rotated quorums: [%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, ss.str()); - } else { - int curDkgHeight = pindexNew->nHeight - (pindexNew->nHeight % llmqParams.dkgInterval); - auto curDkgBlock = pindexNew->GetAncestor(curDkgHeight)->GetBlockHash(); - connmanQuorumsToDelete.erase(curDkgBlock); - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] h[%d] keeping mn quorum connections for quorum: [%d:%s]\n", __func__, ToUnderlying(llmqParams.type), pindexNew->nHeight, curDkgHeight, curDkgBlock.ToString()); - } - - const uint256 myProTxHash = m_mn_activeman != nullptr ? m_mn_activeman->GetProTxHash() : uint256(); - - bool isISType = llmqParams.type == Params().GetConsensus().llmqTypeDIP0024InstantSend; - - bool watchOtherISQuorums = isISType && !myProTxHash.IsNull() && - ranges::any_of(lastQuorums, [&myProTxHash](const auto& old_quorum){ - return old_quorum->IsMember(myProTxHash); - }); - - for (const auto& quorum : lastQuorums) { - if (utils::EnsureQuorumConnections(llmqParams, connman, m_sporkman, {m_dmnman, m_qsnapman, m_chainman, quorum->m_quorum_base_block_index}, - m_dmnman.GetListAtChainTip(), myProTxHash, /*is_masternode=*/m_mn_activeman != nullptr, m_quorums_watch)) { - if (connmanQuorumsToDelete.erase(quorum->qc->quorumHash) > 0) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%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()); - } - } else if (watchOtherISQuorums && !quorum->IsMember(myProTxHash)) { - Uint256HashSet connections; - const auto& cindexes = utils::CalcDeterministicWatchConnections(llmqParams.type, quorum->m_quorum_base_block_index, quorum->members.size(), 1); - for (auto idx : cindexes) { - connections.emplace(quorum->members[idx]->proTxHash); - } - if (!connections.empty()) { - if (!connman.HasMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash())) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%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()); - connman.SetMasternodeQuorumNodes(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); - connman.SetMasternodeQuorumRelayMembers(llmqParams.type, quorum->m_quorum_base_block_index->GetBlockHash(), connections); - } - if (connmanQuorumsToDelete.erase(quorum->qc->quorumHash) > 0) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%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()); - } - } - } - } - for (const auto& quorumHash : connmanQuorumsToDelete) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- removing masternodes quorum connections for quorum %s:\n", __func__, quorumHash.ToString()); - connman.RemoveMasternodeQuorumNodes(llmqParams.type, quorumHash); - } -} - -CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, bool populate_cache) const -{ - const uint256& quorumHash{pQuorumBaseBlockIndex->GetBlockHash()}; - - auto [qc, minedBlockHash] = quorumBlockProcessor.GetMinedCommitment(llmqType, quorumHash); - if (minedBlockHash == uint256::ZERO) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- No mined commitment for llmqType[%d] nHeight[%d] quorumHash[%s]\n", __func__, ToUnderlying(llmqType), pQuorumBaseBlockIndex->nHeight, pQuorumBaseBlockIndex->GetBlockHash().ToString()); - return nullptr; - } - assert(qc.quorumHash == pQuorumBaseBlockIndex->GetBlockHash()); - - const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt.has_value()); - auto quorum = std::make_shared(llmq_params_opt.value(), blsWorker); - auto members = utils::GetAllQuorumMembers(qc.llmqType, {m_dmnman, m_qsnapman, m_chainman, pQuorumBaseBlockIndex}); - - quorum->Init(std::make_unique(std::move(qc)), pQuorumBaseBlockIndex, minedBlockHash, members); - - if (populate_cache && llmq_params_opt->size == 1) { - WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum)); - - return quorum; - } - - bool hasValidVvec = false; - if (WITH_LOCK(cs_db, return quorum->ReadContributions(*db))) { - hasValidVvec = true; - } else { - if (BuildQuorumContributions(quorum->qc, quorum)) { - WITH_LOCK(cs_db, quorum->WriteContributions(*db)); - hasValidVvec = true; - } else { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] quorumIndex[%d] quorum.ReadContributions and BuildQuorumContributions for quorumHash[%s] failed\n", __func__, ToUnderlying(llmqType), quorum->qc->quorumIndex, quorum->qc->quorumHash.ToString()); - } - } - - if (hasValidVvec && populate_cache) { - // pre-populate caches in the background - // recovering public key shares is quite expensive and would result in serious lags for the first few signing - // sessions if the shares would be calculated on-demand - StartCachePopulatorThread(quorum); - } - - WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum)); - - return quorum; -} - -bool CQuorumManager::BuildQuorumContributions(const CFinalCommitmentPtr& fqc, const std::shared_ptr& quorum) const -{ - std::vector memberIndexes; - std::vector vvecs; - std::vector skContributions; - if (!m_qdkgsman || - !m_qdkgsman->GetVerifiedContributions((Consensus::LLMQType)fqc->llmqType, quorum->m_quorum_base_block_index, - fqc->validMembers, memberIndexes, vvecs, skContributions)) { - return false; - } - - cxxtimer::Timer t2(true); - quorum->SetVerificationVector(blsWorker.BuildQuorumVerificationVector(vvecs)); - if (!quorum->HasVerificationVector()) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build quorumVvec\n", __func__); - // without the quorum vvec, there can't be a skShare, so we fail here. Failure is not fatal here, as it still - // allows to use the quorum as a non-member (verification through the quorum pub key) - return false; - } - if (!quorum->SetSecretKeyShare(blsWorker.AggregateSecretKeys(skContributions), *m_mn_activeman)) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build skShare\n", __func__); - // We don't bail out here as this is not a fatal error and still allows us to recover public key shares (as we - // have a valid quorum vvec at this point) - } - t2.stop(); - - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- built quorum vvec and skShare. time=%d\n", __func__, t2.count()); - - return true; -} - -bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const CQuorumBlockProcessor& quorum_block_processor, const uint256& quorumHash) -{ - return quorum_block_processor.HasMinedCommitment(llmqType, quorumHash); -} - -bool CQuorumManager::RequestQuorumData(CNode* pfrom, CConnman& connman, const CQuorum& quorum, uint16_t nDataMask, - const uint256& proTxHash) const -{ - if (pfrom == nullptr) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid pfrom: nullptr\n", __func__); - return false; - } - if (pfrom->GetVerifiedProRegTxHash().IsNull()) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- pfrom is not a verified masternode\n", __func__); - return false; - } - const Consensus::LLMQType llmqType = quorum.qc->llmqType; - if (!Params().GetLLMQ(llmqType).has_value()) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid llmqType: %d\n", __func__, ToUnderlying(llmqType)); - return false; - } - const CBlockIndex* pindex{quorum.m_quorum_base_block_index}; - if (pindex == nullptr) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid m_quorum_base_block_index : nullptr\n", __func__); - return false; - } - - LOCK(cs_data_requests); - const CQuorumDataRequestKey key(pfrom->GetVerifiedProRegTxHash(), true, pindex->GetBlockHash(), llmqType); - const CQuorumDataRequest request(llmqType, pindex->GetBlockHash(), nDataMask, proTxHash); - auto [old_pair, inserted] = mapQuorumDataRequests.emplace(key, request); - if (!inserted) { - if (old_pair->second.IsExpired(/*add_bias=*/true)) { - old_pair->second = request; - } else { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Already requested\n", __func__); - return false; - } - } - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- sending QGETDATA quorumHash[%s] llmqType[%d] proRegTx[%s]\n", __func__, key.quorumHash.ToString(), - ToUnderlying(key.llmqType), key.proRegTx.ToString()); - - CNetMsgMaker msgMaker(pfrom->GetCommonVersion()); - connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::QGETDATA, request)); - - return true; -} - -std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const -{ - const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.ActiveTip()); - return ScanQuorums(llmqType, pindex, nCountRequested); -} - -std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, - gsl::not_null pindexStart, - size_t nCountRequested) const -{ - if (nCountRequested == 0 || !m_chainman.IsQuorumTypeEnabled(llmqType, pindexStart)) { - return {}; - } - - gsl::not_null pindexStore{pindexStart}; - const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt.has_value()); - - // Quorum sets can only change during the mining phase of DKG. - // Find the closest known block index. - const int quorumCycleStartHeight = pindexStart->nHeight - (pindexStart->nHeight % llmq_params_opt->dkgInterval); - const int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmq_params_opt->dkgMiningWindowStart; - const int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmq_params_opt->dkgMiningWindowEnd; - - if (pindexStart->nHeight < quorumCycleMiningStartHeight) { - // too early for this cycle, use the previous one - // bail out if it's below genesis block - if (quorumCycleMiningEndHeight < llmq_params_opt->dkgInterval) return {}; - pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight - llmq_params_opt->dkgInterval); - } else if (pindexStart->nHeight > quorumCycleMiningEndHeight) { - // we are past the mining phase of this cycle, use it - pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight); - } - // everything else is inside the mining phase of this cycle, no pindexStore adjustment needed - - gsl::not_null pIndexScanCommitments{pindexStore}; - size_t nScanCommitments{nCountRequested}; - std::vector vecResultQuorums; - - { - LOCK(cs_scan_quorums); - if (scanQuorumsCache.empty()) { - for (const auto& llmq : Params().GetConsensus().llmqs) { - // NOTE: We store it for each block hash in the DKG mining phase here - // and not for a single quorum hash per quorum like we do for other caches. - // And we only do this for max_cycles() of the most recent quorums - // because signing by old quorums requires the exact quorum hash to be specified - // and quorum scanning isn't needed there. - scanQuorumsCache.try_emplace(llmq.type, llmq.max_cycles(llmq.keepOldConnections) * (llmq.dkgMiningWindowEnd - llmq.dkgMiningWindowStart)); - } - } - auto& cache = scanQuorumsCache[llmqType]; - bool fCacheExists = cache.get(pindexStore->GetBlockHash(), vecResultQuorums); - if (fCacheExists) { - // We have exactly what requested so just return it - if (vecResultQuorums.size() == nCountRequested) { - return vecResultQuorums; - } - // If we have more cached than requested return only a subvector - if (vecResultQuorums.size() > nCountRequested) { - return {vecResultQuorums.begin(), vecResultQuorums.begin() + nCountRequested}; - } - // If we have cached quorums but not enough, subtract what we have from the count and the set correct index where to start - // scanning for the rests - if (!vecResultQuorums.empty()) { - nScanCommitments -= vecResultQuorums.size(); - // bail out if it's below genesis block - if (vecResultQuorums.back()->m_quorum_base_block_index->pprev == nullptr) return {}; - pIndexScanCommitments = vecResultQuorums.back()->m_quorum_base_block_index->pprev; - } - } else { - // If there is nothing in cache request at least keepOldConnections because this gets cached then later - nScanCommitments = std::max(nCountRequested, static_cast(llmq_params_opt->keepOldConnections)); - } - } - - // Get the block indexes of the mined commitments to build the required quorums from - std::vector pQuorumBaseBlockIndexes{ llmq_params_opt->useRotation ? - quorumBlockProcessor.GetMinedCommitmentsIndexedUntilBlock(llmqType, pIndexScanCommitments, nScanCommitments) : - quorumBlockProcessor.GetMinedCommitmentsUntilBlock(llmqType, pIndexScanCommitments, nScanCommitments) - }; - vecResultQuorums.reserve(vecResultQuorums.size() + pQuorumBaseBlockIndexes.size()); - - for (auto& pQuorumBaseBlockIndex : pQuorumBaseBlockIndexes) { - assert(pQuorumBaseBlockIndex); - // populate cache for keepOldConnections most recent quorums only - bool populate_cache = vecResultQuorums.size() < static_cast(llmq_params_opt->keepOldConnections); - - // We assume that every quorum asked for is available to us on hand, if this - // fails then we can assume that something has gone wrong and we should stop - // trying to process any further and return a blank. - auto quorum = GetQuorum(llmqType, pQuorumBaseBlockIndex, populate_cache); - if (!quorum) { - LogPrintf("%s: ERROR! Unexpected missing quorum with llmqType=%d, blockHash=%s, populate_cache=%s\n", - __func__, ToUnderlying(llmqType), pQuorumBaseBlockIndex->GetBlockHash().ToString(), - populate_cache ? "true" : "false"); - return {}; - } - vecResultQuorums.emplace_back(quorum); - } - - const size_t nCountResult{vecResultQuorums.size()}; - if (nCountResult > 0) { - LOCK(cs_scan_quorums); - // Don't cache more than keepOldConnections elements - // because signing by old quorums requires the exact quorum hash - // to be specified and quorum scanning isn't needed there. - auto& cache = scanQuorumsCache[llmqType]; - const size_t nCacheEndIndex = std::min(nCountResult, static_cast(llmq_params_opt->keepOldConnections)); - cache.emplace(pindexStore->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex}); - } - // Don't return more than nCountRequested elements - const size_t nResultEndIndex = std::min(nCountResult, nCountRequested); - return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex}; -} - -CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const -{ - const CBlockIndex* pQuorumBaseBlockIndex = [&]() { - // Lock contention may still be high here; consider using a shared lock - // We cannot hold cs_quorumBaseBlockIndexCache the whole time as that creates lock-order inversion with cs_main; - // We cannot acquire cs_main if we have cs_quorumBaseBlockIndexCache held - const CBlockIndex* pindex; - if (!WITH_LOCK(cs_quorumBaseBlockIndexCache, return quorumBaseBlockIndexCache.get(quorumHash, pindex))) { - pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(quorumHash)); - if (pindex) { - LOCK(cs_quorumBaseBlockIndexCache); - quorumBaseBlockIndexCache.insert(quorumHash, pindex); - } - } - return pindex; - }(); - if (!pQuorumBaseBlockIndex) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found\n", __func__, quorumHash.ToString()); - return nullptr; - } - return GetQuorum(llmqType, pQuorumBaseBlockIndex); -} - -CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, bool populate_cache) const -{ - auto quorumHash = pQuorumBaseBlockIndex->GetBlockHash(); - - // we must check this before we look into the cache. Reorgs might have happened which would mean we might have - // cached quorums which are not in the active chain anymore - if (!HasQuorum(llmqType, quorumBlockProcessor, quorumHash)) { - return nullptr; - } - - CQuorumPtr pQuorum; - if (LOCK(cs_map_quorums); mapQuorumsCache[llmqType].get(quorumHash, pQuorum)) { - return pQuorum; - } - - return BuildQuorumFromCommitment(llmqType, pQuorumBaseBlockIndex, populate_cache); -} - -size_t CQuorumManager::GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null pIndex) const -{ - assert(m_mn_activeman); - - auto mns = m_dmnman.GetListForBlock(pIndex); - std::vector vecProTxHashes; - vecProTxHashes.reserve(mns.GetValidMNsCount()); - mns.ForEachMN(/*onlyValid=*/true, - [&](const auto& pMasternode) { vecProTxHashes.emplace_back(pMasternode.proTxHash); }); - std::sort(vecProTxHashes.begin(), vecProTxHashes.end()); - size_t nIndex{0}; - { - auto my_protx_hash = m_mn_activeman->GetProTxHash(); - for (const auto i : irange::range(vecProTxHashes.size())) { - // cppcheck-suppress useStlAlgorithm - if (my_protx_hash == vecProTxHashes[i]) { - nIndex = i; - break; - } - } - } - return nIndex % quorum.qc->validMembers.size(); -} - -MessageProcessingResult CQuorumManager::ProcessMessage(CNode& pfrom, CConnman& connman, std::string_view msg_type, CDataStream& vRecv) -{ - if (msg_type == NetMsgType::QGETDATA) { - if (m_mn_activeman == nullptr || (pfrom.GetVerifiedProRegTxHash().IsNull() && !pfrom.qwatch)) { - return MisbehavingError{10, "not a verified masternode or a qwatch connection"}; - } - - CQuorumDataRequest request; - vRecv >> request; - - auto sendQDATA = [&](CQuorumDataRequest::Errors nError, - bool request_limit_exceeded, - const CDataStream& body = CDataStream(SER_NETWORK, PROTOCOL_VERSION)) -> MessageProcessingResult { - MessageProcessingResult ret{}; - switch (nError) { - case (CQuorumDataRequest::Errors::NONE): - case (CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID): - case (CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND): - case (CQuorumDataRequest::Errors::QUORUM_NOT_FOUND): - case (CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER): - case (CQuorumDataRequest::Errors::UNDEFINED): - if (request_limit_exceeded) ret = MisbehavingError{25, "request limit exceeded"}; - break; - case (CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING): - case (CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING): - // Do not punish limit exceed if we don't have the requested data - break; - } - request.SetError(nError); - CDataStream ssResponse{SER_NETWORK, pfrom.GetCommonVersion()}; - ssResponse << request << body; - connman.PushMessage(&pfrom, CNetMsgMaker(pfrom.GetCommonVersion()).Make(NetMsgType::QDATA, ssResponse)); - return ret; - }; - - bool request_limit_exceeded(false); - { - LOCK2(::cs_main, cs_data_requests); - const CQuorumDataRequestKey key(pfrom.GetVerifiedProRegTxHash(), false, request.GetQuorumHash(), request.GetLLMQType()); - auto it = mapQuorumDataRequests.find(key); - if (it == mapQuorumDataRequests.end()) { - it = mapQuorumDataRequests.emplace(key, request).first; - } else if (it->second.IsExpired(/*add_bias=*/false)) { - it->second = request; - } else { - request_limit_exceeded = true; - } - } - - if (!Params().GetLLMQ(request.GetLLMQType()).has_value()) { - return sendQDATA(CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID, request_limit_exceeded); - } - - const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex( - request.GetQuorumHash())); - if (pQuorumBaseBlockIndex == nullptr) { - return sendQDATA(CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND, request_limit_exceeded); - } - - const auto pQuorum = GetQuorum(request.GetLLMQType(), pQuorumBaseBlockIndex); - if (pQuorum == nullptr) { - return sendQDATA(CQuorumDataRequest::Errors::QUORUM_NOT_FOUND, request_limit_exceeded); - } - - CDataStream ssResponseData(SER_NETWORK, pfrom.GetCommonVersion()); - - // Check if request wants QUORUM_VERIFICATION_VECTOR data - if (request.GetDataMask() & CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR) { - if (!pQuorum->HasVerificationVector()) { - return sendQDATA(CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING, request_limit_exceeded); - } - - WITH_LOCK(pQuorum->cs_vvec_shShare, ssResponseData << *pQuorum->quorumVvec); - } - - // Check if request wants ENCRYPTED_CONTRIBUTIONS data - if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { - - int memberIdx = pQuorum->GetMemberIndex(request.GetProTxHash()); - if (memberIdx == -1) { - return sendQDATA(CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER, request_limit_exceeded); - } - - std::vector> vecEncrypted; - if (!m_qdkgsman || - !m_qdkgsman->GetEncryptedContributions(request.GetLLMQType(), pQuorumBaseBlockIndex, - pQuorum->qc->validMembers, request.GetProTxHash(), vecEncrypted)) { - return sendQDATA(CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING, request_limit_exceeded); - } - - ssResponseData << vecEncrypted; - } - - return sendQDATA(CQuorumDataRequest::Errors::NONE, request_limit_exceeded, ssResponseData); - } - - if (msg_type == NetMsgType::QDATA) { - if ((m_mn_activeman == nullptr && !m_quorums_watch) || pfrom.GetVerifiedProRegTxHash().IsNull()) { - return MisbehavingError{10, "not a verified masternode and -watchquorums is not enabled"}; - } - - CQuorumDataRequest request; - vRecv >> request; - - { - LOCK2(::cs_main, cs_data_requests); - const CQuorumDataRequestKey key(pfrom.GetVerifiedProRegTxHash(), true, request.GetQuorumHash(), request.GetLLMQType()); - auto it = mapQuorumDataRequests.find(key); - if (it == mapQuorumDataRequests.end()) { - return MisbehavingError{10, "not requested"}; - } - if (it->second.IsProcessed()) { - return MisbehavingError(10, "already received"); - } - if (request != it->second) { - return MisbehavingError(10, "not like requested"); - } - it->second.SetProcessed(); - } - - if (request.GetError() != CQuorumDataRequest::Errors::NONE) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s: %s, from peer=%d\n", __func__, msg_type, strprintf("Error %d (%s)", request.GetError(), request.GetErrorString()), pfrom.GetId()); - return {}; - } - - CQuorumPtr pQuorum; - { - if (LOCK(cs_map_quorums); !mapQuorumsCache[request.GetLLMQType()].get(request.GetQuorumHash(), pQuorum)) { - // Don't bump score because we asked for it - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s: Quorum not found, from peer=%d\n", __func__, msg_type, pfrom.GetId()); - return {}; - } - } - - // Check if request has QUORUM_VERIFICATION_VECTOR data - if (request.GetDataMask() & CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR) { - - std::vector verificationVector; - vRecv >> verificationVector; - - if (pQuorum->SetVerificationVector(verificationVector)) { - StartCachePopulatorThread(pQuorum); - } else { - return MisbehavingError{10, "invalid quorum verification vector"}; - } - } - - // Check if request has ENCRYPTED_CONTRIBUTIONS data - if (request.GetDataMask() & CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS) { - assert(m_mn_activeman); - - if (WITH_LOCK(pQuorum->cs_vvec_shShare, return pQuorum->quorumVvec->size() != size_t(pQuorum->params.threshold))) { - // Don't bump score because we asked for it - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s: No valid quorum verification vector available, from peer=%d\n", __func__, msg_type, pfrom.GetId()); - return {}; - } - - int memberIdx = pQuorum->GetMemberIndex(request.GetProTxHash()); - if (memberIdx == -1) { - // Don't bump score because we asked for it - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s: Not a member of the quorum, from peer=%d\n", __func__, msg_type, pfrom.GetId()); - return {}; - } - - std::vector> vecEncrypted; - vRecv >> vecEncrypted; - - std::vector vecSecretKeys; - vecSecretKeys.resize(vecEncrypted.size()); - for (const auto i : irange::range(vecEncrypted.size())) { - if (!m_mn_activeman->Decrypt(vecEncrypted[i], memberIdx, vecSecretKeys[i], PROTOCOL_VERSION)) { - return MisbehavingError{10, "failed to decrypt"}; - } - } - - CBLSSecretKey secretKeyShare = blsWorker.AggregateSecretKeys(vecSecretKeys); - if (!pQuorum->SetSecretKeyShare(secretKeyShare, *m_mn_activeman)) { - return MisbehavingError{10, "invalid secret key share received"}; - } - } - WITH_LOCK(cs_db, pQuorum->WriteContributions(*db)); - return {}; - } - return {}; -} - -void CQuorumManager::StartCachePopulatorThread(CQuorumCPtr pQuorum) const -{ - if (!pQuorum->HasVerificationVector()) { - return; - } - - cxxtimer::Timer t(true); - LogPrint(BCLog::LLMQ, "CQuorumManager::StartCachePopulatorThread -- type=%d height=%d hash=%s start\n", - ToUnderlying(pQuorum->params.type), - pQuorum->m_quorum_base_block_index->nHeight, - pQuorum->m_quorum_base_block_index->GetBlockHash().ToString()); - - // when then later some other thread tries to get keys, it will be much faster - workerPool.push([pQuorum = std::move(pQuorum), t, this](int threadId) { - for (const auto i : irange::range(pQuorum->members.size())) { - if (quorumThreadInterrupt) { - break; - } - if (pQuorum->qc->validMembers[i]) { - pQuorum->GetPubKeyShare(i); - } - } - LogPrint(BCLog::LLMQ, "CQuorumManager::StartCachePopulatorThread -- type=%d height=%d hash=%s done. time=%d\n", - ToUnderlying(pQuorum->params.type), - pQuorum->m_quorum_base_block_index->nHeight, - pQuorum->m_quorum_base_block_index->GetBlockHash().ToString(), - t.count()); - }); -} - -void CQuorumManager::StartQuorumDataRecoveryThread(CConnman& connman, CQuorumCPtr pQuorum, - gsl::not_null pIndex, uint16_t nDataMaskIn) const -{ - assert(m_mn_activeman); - - bool expected = false; - if (!pQuorum->fQuorumDataRecoveryThreadRunning.compare_exchange_strong(expected, true)) { - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Already running\n", __func__); - return; - } - - workerPool.push([&connman, pQuorum = std::move(pQuorum), pIndex, nDataMaskIn, this](int threadId) { - size_t nTries{0}; - uint16_t nDataMask{nDataMaskIn}; - int64_t nTimeLastSuccess{0}; - uint256* pCurrentMemberHash{nullptr}; - std::vector vecMemberHashes; - const size_t nMyStartOffset{GetQuorumRecoveryStartOffset(*pQuorum, pIndex)}; - const int64_t nRequestTimeout{10}; - - auto printLog = [&](const std::string& strMessage) { - const std::string strMember{pCurrentMemberHash == nullptr ? "nullptr" : pCurrentMemberHash->ToString()}; - LogPrint(BCLog::LLMQ, "CQuorumManager::StartQuorumDataRecoveryThread -- %s - for llmqType %d, quorumHash %s, nDataMask (%d/%d), pCurrentMemberHash %s, nTries %d\n", - strMessage, ToUnderlying(pQuorum->qc->llmqType), pQuorum->qc->quorumHash.ToString(), nDataMask, nDataMaskIn, strMember, nTries); - }; - printLog("Start"); - - while (!m_mn_sync.IsBlockchainSynced() && !quorumThreadInterrupt) { - quorumThreadInterrupt.sleep_for(std::chrono::seconds(nRequestTimeout)); - } - - if (quorumThreadInterrupt) { - printLog("Aborted"); - return; - } - - vecMemberHashes.reserve(pQuorum->qc->validMembers.size()); - for (auto& member : pQuorum->members) { - if (pQuorum->IsValidMember(member->proTxHash) && member->proTxHash != m_mn_activeman->GetProTxHash()) { - vecMemberHashes.push_back(member->proTxHash); - } - } - std::sort(vecMemberHashes.begin(), vecMemberHashes.end()); - - printLog("Try to request"); - - while (nDataMask > 0 && !quorumThreadInterrupt) { - if (nDataMask & llmq::CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR && - pQuorum->HasVerificationVector()) { - nDataMask &= ~llmq::CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR; - printLog("Received quorumVvec"); - } - - if (nDataMask & llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS && pQuorum->GetSkShare().IsValid()) { - nDataMask &= ~llmq::CQuorumDataRequest::ENCRYPTED_CONTRIBUTIONS; - printLog("Received skShare"); - } - - if (nDataMask == 0) { - printLog("Success"); - break; - } - - if ((GetTime().count() - nTimeLastSuccess) > nRequestTimeout) { - if (nTries >= vecMemberHashes.size()) { - printLog("All tried but failed"); - break; - } - // Access the member list of the quorum with the calculated offset applied to balance the load equally - pCurrentMemberHash = &vecMemberHashes[(nMyStartOffset + nTries++) % vecMemberHashes.size()]; - { - LOCK(cs_data_requests); - const CQuorumDataRequestKey key(*pCurrentMemberHash, true, pQuorum->qc->quorumHash, pQuorum->qc->llmqType); - auto it = mapQuorumDataRequests.find(key); - if (it != mapQuorumDataRequests.end() && !it->second.IsExpired(/*add_bias=*/true)) { - printLog("Already asked"); - continue; - } - } - // Sleep a bit depending on the start offset to balance out multiple requests to same masternode - quorumThreadInterrupt.sleep_for(std::chrono::milliseconds(nMyStartOffset * 100)); - nTimeLastSuccess = GetTime().count(); - connman.AddPendingMasternode(*pCurrentMemberHash); - printLog("Connect"); - } - - auto proTxHash = m_mn_activeman->GetProTxHash(); - connman.ForEachNode([&](CNode* pNode) { - auto verifiedProRegTxHash = pNode->GetVerifiedProRegTxHash(); - if (pCurrentMemberHash == nullptr || verifiedProRegTxHash != *pCurrentMemberHash) { - return; - } - - if (RequestQuorumData(pNode, connman, *pQuorum, nDataMask, proTxHash)) { - nTimeLastSuccess = GetTime().count(); - printLog("Requested"); - } else { - LOCK(cs_data_requests); - const CQuorumDataRequestKey key(*pCurrentMemberHash, true, pQuorum->qc->quorumHash, pQuorum->qc->llmqType); - auto it = mapQuorumDataRequests.find(key); - if (it == mapQuorumDataRequests.end()) { - printLog("Failed"); - pNode->fDisconnect = true; - pCurrentMemberHash = nullptr; - return; - } else if (it->second.IsProcessed()) { - printLog("Processed"); - pNode->fDisconnect = true; - pCurrentMemberHash = nullptr; - return; - } else { - printLog("Waiting"); - return; - } - } - }); - quorumThreadInterrupt.sleep_for(std::chrono::seconds(1)); - } - pQuorum->fQuorumDataRecoveryThreadRunning = false; - printLog("Done"); - }); -} - -static void DataCleanupHelper(CDBWrapper& db, std::set skip_list, bool compact = false) -{ - const auto prefixes = {DB_QUORUM_QUORUM_VVEC, DB_QUORUM_SK_SHARE}; - - CDBBatch batch(db); - std::unique_ptr pcursor(db.NewIterator()); - - for (const auto& prefix : prefixes) { - auto start = std::make_tuple(prefix, uint256()); - pcursor->Seek(start); - - int count{0}; - while (pcursor->Valid()) { - decltype(start) k; - - if (!pcursor->GetKey(k) || std::get<0>(k) != prefix) { - break; - } - - pcursor->Next(); - - if (skip_list.find(std::get<1>(k)) != skip_list.end()) continue; - - ++count; - batch.Erase(k); - - if (batch.SizeEstimate() >= (1 << 24)) { - db.WriteBatch(batch); - batch.Clear(); - } - } - - db.WriteBatch(batch); - - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s removed %d\n", __func__, prefix, count); - } - - pcursor.reset(); - - if (compact) { - // Avoid using this on regular cleanups, use on db migrations only - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- compact start\n", __func__); - db.CompactFull(); - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- compact end\n", __func__); - } -} - -void CQuorumManager::StartCleanupOldQuorumDataThread(gsl::not_null pIndex) const -{ - // Note: this function is CPU heavy and we don't want it to be running during DKGs. - // The largest dkgMiningWindowStart for a related quorum type is 42 (LLMQ_60_75). - // At the same time most quorums use dkgInterval = 24 so the next DKG for them - // (after block 576 + 42) will start at block 576 + 24 * 2. That's only a 6 blocks - // window and it's better to have more room so we pick next cycle. - // dkgMiningWindowStart for small quorums is 10 i.e. a safe block to start - // these calculations is at height 576 + 24 * 2 + 10 = 576 + 58. - if ((m_mn_activeman == nullptr && !m_quorums_watch) || (pIndex->nHeight % 576 != 58)) { - return; - } - - cxxtimer::Timer t(/*start=*/ true); - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- start\n", __func__); - - // do not block the caller thread - workerPool.push([pIndex, t, this](int threadId) { - std::set dbKeysToSkip; - - if (LOCK(cs_cleanup); cleanupQuorumsCache.empty()) { - utils::InitQuorumsCache(cleanupQuorumsCache, m_chainman.GetConsensus(), /*limit_by_connections=*/false); - } - for (const auto& params : Params().GetConsensus().llmqs) { - if (quorumThreadInterrupt) { - break; - } - LOCK(cs_cleanup); - auto& cache = cleanupQuorumsCache[params.type]; - const CBlockIndex* pindex_loop{pIndex}; - std::set quorum_keys; - while (pindex_loop != nullptr && pIndex->nHeight - pindex_loop->nHeight < params.max_store_depth()) { - uint256 quorum_key; - if (cache.get(pindex_loop->GetBlockHash(), quorum_key)) { - quorum_keys.insert(quorum_key); - if (quorum_keys.size() >= static_cast(params.keepOldKeys)) break; // extra safety belt - } - pindex_loop = pindex_loop->pprev; - } - for (const auto& pQuorum : ScanQuorums(params.type, pIndex, params.keepOldKeys - quorum_keys.size())) { - const uint256 quorum_key = MakeQuorumKey(*pQuorum); - quorum_keys.insert(quorum_key); - cache.insert(pQuorum->m_quorum_base_block_index->GetBlockHash(), quorum_key); - } - dbKeysToSkip.merge(quorum_keys); - } - - if (!quorumThreadInterrupt) { - WITH_LOCK(cs_db, DataCleanupHelper(*db, dbKeysToSkip)); - } - - LogPrint(BCLog::LLMQ, "CQuorumManager::StartCleanupOldQuorumDataThread -- done. time=%d\n", t.count()); - }); -} - -// TODO: remove in v23 -void CQuorumManager::MigrateOldQuorumDB(CEvoDB& evoDb) const -{ - LOCK(cs_db); - if (!db->IsEmpty()) return; - - const auto prefixes = {DB_QUORUM_QUORUM_VVEC, DB_QUORUM_SK_SHARE}; - - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- start\n", __func__); - - CDBBatch batch(*db); - std::unique_ptr pcursor(evoDb.GetRawDB().NewIterator()); - - for (const auto& prefix : prefixes) { - auto start = std::make_tuple(prefix, uint256()); - pcursor->Seek(start); - - int count{0}; - while (pcursor->Valid()) { - decltype(start) k; - CDataStream s(SER_DISK, CLIENT_VERSION); - CBLSSecretKey sk; - - if (!pcursor->GetKey(k) || std::get<0>(k) != prefix) { - break; - } - - if (prefix == DB_QUORUM_QUORUM_VVEC) { - if (!evoDb.GetRawDB().ReadDataStream(k, s)) { - break; - } - batch.Write(k, s); - } - if (prefix == DB_QUORUM_SK_SHARE) { - if (!pcursor->GetValue(sk)) { - break; - } - batch.Write(k, sk); - } - - if (batch.SizeEstimate() >= (1 << 24)) { - db->WriteBatch(batch); - batch.Clear(); - } - - ++count; - pcursor->Next(); - } - - db->WriteBatch(batch); - - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s moved %d\n", __func__, prefix, count); - } - - pcursor.reset(); - db->CompactFull(); - - DataCleanupHelper(evoDb.GetRawDB(), {}); - evoDb.CommitRootTransaction(); - - LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- done\n", __func__); -} - -CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, const CChain& active_chain, const CQuorumManager& qman, - const uint256& selectionHash, int signHeight, int signOffset) -{ - size_t poolSize = llmq_params.signingActiveQuorumCount; - - CBlockIndex* pindexStart; - { - LOCK(::cs_main); - if (signHeight == -1) { - signHeight = active_chain.Height(); - } - int startBlockHeight = signHeight - signOffset; - if (startBlockHeight > active_chain.Height() || startBlockHeight < 0) { - return {}; - } - pindexStart = active_chain[startBlockHeight]; - } - - if (IsQuorumRotationEnabled(llmq_params, pindexStart)) { - auto quorums = qman.ScanQuorums(llmq_params.type, pindexStart, poolSize); - if (quorums.empty()) { - return nullptr; - } - //log2 int - int n = std::log2(llmq_params.signingActiveQuorumCount); - //Extract last 64 bits of selectionHash - uint64_t b = selectionHash.GetUint64(3); - //Take last n bits of b - uint64_t signer = (((1ull << n) - 1) & (b >> (64 - n - 1))); - - if (signer > quorums.size()) { - return nullptr; - } - auto itQuorum = std::find_if(quorums.begin(), - quorums.end(), - [signer](const CQuorumCPtr& obj) { - return uint64_t(obj->qc->quorumIndex) == signer; - }); - if (itQuorum == quorums.end()) { - return nullptr; - } - return *itQuorum; - } else { - auto quorums = qman.ScanQuorums(llmq_params.type, pindexStart, poolSize); - if (quorums.empty()) { - return nullptr; - } - - std::vector> scores; - scores.reserve(quorums.size()); - for (const auto i : irange::range(quorums.size())) { - CHashWriter h(SER_NETWORK, 0); - h << llmq_params.type; - h << quorums[i]->qc->quorumHash; - h << selectionHash; - scores.emplace_back(h.GetHash(), i); - } - std::sort(scores.begin(), scores.end()); - return quorums[scores.front().second]; - } -} - -VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman, - int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig, - const int signOffset) -{ - const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt.has_value()); - auto quorum = SelectQuorumForSigning(llmq_params_opt.value(), active_chain, qman, id, signedAtHeight, signOffset); - if (!quorum) { - return VerifyRecSigStatus::NoQuorum; - } - - SignHash signHash{llmqType, quorum->qc->quorumHash, id, msgHash}; - const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get()); - return ret ? VerifyRecSigStatus::Valid : VerifyRecSigStatus::Invalid; -} } // namespace llmq diff --git a/src/llmq/quorums.h b/src/llmq/quorums.h index 551401118528..6e3899723c8c 100644 --- a/src/llmq/quorums.h +++ b/src/llmq/quorums.h @@ -1,60 +1,41 @@ // Copyright (c) 2018-2025 The Dash Core developers -// Distributed under the MIT/X11 software license, see the accompanying +// Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_LLMQ_QUORUMS_H #define BITCOIN_LLMQ_QUORUMS_H -#include #include -#include #include -#include #include #include -#include -#include - #include -#include -#include -#include +#include +#include +#include +#include +#include -#include -#include -#include +#include +#include +#include -class CActiveMasternodeManager; class CBlockIndex; -class CChain; -class CChainState; -class CConnman; -class ChainstateManager; -class CDataStream; -class CDeterministicMN; -class CDeterministicMNManager; class CDBWrapper; -class CEvoDB; -class CMasternodeSync; -class CNode; -class CSporkManager; -namespace util { -struct DbWrapperParams; -} // namespace util - -namespace llmq -{ -enum class VerifyRecSigStatus -{ - NoQuorum, - Invalid, - Valid, -}; +namespace llmq { +class CQuorum; +class CQuorumManager; +class QuorumObserver; +class QuorumParticipant; +}; // namespace llmq -class CDKGSessionManager; -class CQuorumBlockProcessor; -class CQuorumSnapshotManager; +namespace llmq { +extern const std::string DB_QUORUM_SK_SHARE; +extern const std::string DB_QUORUM_QUORUM_VVEC; + +uint256 MakeQuorumKey(const CQuorum& q); +void DataCleanupHelper(CDBWrapper& db, const std::set& skip_list, bool compact = false); /** * Object used as a key to store CQuorumDataRequest @@ -175,10 +156,12 @@ class CQuorumDataRequest * will also contain the secret key share and the quorum verification vector. The quorum vvec is then used to recover * the public key shares of individual members, which are needed to verify signature shares of these members. */ - class CQuorum { friend class CQuorumManager; + friend class llmq::QuorumObserver; + friend class llmq::QuorumParticipant; + public: const Consensus::LLMQParams params; CFinalCommitmentPtr qc; @@ -208,7 +191,7 @@ class CQuorum LOCK(cs_vvec_shShare); quorumVvec = std::move(vvec_in); } - bool SetSecretKeyShare(const CBLSSecretKey& secretKeyShare, const CActiveMasternodeManager& mn_activeman) + bool SetSecretKeyShare(const CBLSSecretKey& secretKeyShare, const uint256& protx_hash) EXCLUSIVE_LOCKS_REQUIRED(!cs_vvec_shShare); bool HasVerificationVector() const EXCLUSIVE_LOCKS_REQUIRED(!cs_vvec_shShare); @@ -224,145 +207,6 @@ class CQuorum void WriteContributions(CDBWrapper& db) const EXCLUSIVE_LOCKS_REQUIRED(!cs_vvec_shShare); bool ReadContributions(const CDBWrapper& db) EXCLUSIVE_LOCKS_REQUIRED(!cs_vvec_shShare); }; - -/** - * The quorum manager maintains quorums which were mined on chain. When a quorum is requested from the manager, - * it will lookup the commitment (through CQuorumBlockProcessor) and build a CQuorum object from it. - * - * It is also responsible for initialization of the intra-quorum connections for new quorums. - */ -class CQuorumManager -{ -private: - CBLSWorker& blsWorker; - CDeterministicMNManager& m_dmnman; - CQuorumBlockProcessor& quorumBlockProcessor; - CQuorumSnapshotManager& m_qsnapman; - const CActiveMasternodeManager* const m_mn_activeman; - const ChainstateManager& m_chainman; - const CMasternodeSync& m_mn_sync; - const CSporkManager& m_sporkman; - llmq::CDKGSessionManager* m_qdkgsman{nullptr}; - const llmq::QvvecSyncModeMap m_sync_map; - const bool m_quorums_recovery{false}; - const bool m_quorums_watch{false}; - -private: - mutable Mutex cs_db; - std::unique_ptr db GUARDED_BY(cs_db){nullptr}; - - mutable Mutex cs_data_requests; - mutable std::unordered_map mapQuorumDataRequests - GUARDED_BY(cs_data_requests); - - mutable Mutex cs_map_quorums; - mutable std::map> mapQuorumsCache GUARDED_BY(cs_map_quorums); - - mutable Mutex cs_scan_quorums; // TODO: merge cs_map_quorums, cs_scan_quorums mutexes - mutable std::map>> scanQuorumsCache - GUARDED_BY(cs_scan_quorums); - - mutable Mutex cs_cleanup; - mutable std::map> cleanupQuorumsCache GUARDED_BY(cs_cleanup); - - // On mainnet, we have around 62 quorums active at any point; let's cache a little more than double that to be safe. - // it maps `quorum_hash` to `pindex` - mutable Mutex cs_quorumBaseBlockIndexCache; - mutable Uint256LruHashMap quorumBaseBlockIndexCache - GUARDED_BY(cs_quorumBaseBlockIndexCache); - - mutable ctpl::thread_pool workerPool; - mutable CThreadInterrupt quorumThreadInterrupt; - -public: - CQuorumManager() = delete; - CQuorumManager(const CQuorumManager&) = delete; - CQuorumManager& operator=(const CQuorumManager&) = delete; - explicit CQuorumManager(CBLSWorker& _blsWorker, CDeterministicMNManager& dmnman, CEvoDB& _evoDb, - CQuorumBlockProcessor& _quorumBlockProcessor, CQuorumSnapshotManager& qsnapman, - const CActiveMasternodeManager* const mn_activeman, const ChainstateManager& chainman, - const CMasternodeSync& mn_sync, const CSporkManager& sporkman, - const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, - bool quorums_recovery, bool quorums_watch); - ~CQuorumManager(); - - void ConnectManager(gsl::not_null qdkgsman) - { - // Prohibit double initialization - assert(m_qdkgsman == nullptr); - m_qdkgsman = qdkgsman; - } - void DisconnectManager() { m_qdkgsman = nullptr; } - - void Start(); - void Stop(); - - void TriggerQuorumDataRecoveryThreads(CConnman& connman, gsl::not_null pIndex) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_data_requests, !cs_scan_quorums, !cs_map_quorums); - - void UpdatedBlockTip(const CBlockIndex* pindexNew, CConnman& connman, bool fInitialDownload) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_data_requests, !cs_scan_quorums, !cs_map_quorums); - - [[nodiscard]] MessageProcessingResult ProcessMessage(CNode& pfrom, CConnman& connman, std::string_view msg_type, - CDataStream& vRecv) - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_data_requests, !cs_map_quorums); - - static bool HasQuorum(Consensus::LLMQType llmqType, const CQuorumBlockProcessor& quorum_block_processor, const uint256& quorumHash); - - bool RequestQuorumData(CNode* pfrom, CConnman& connman, const CQuorum& quorum, uint16_t nDataMask, - const uint256& proTxHash = uint256()) const EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); - - // all these methods will lock cs_main for a short period of time - CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums); - std::vector ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums); - - // this one is cs_main-free - std::vector ScanQuorums(Consensus::LLMQType llmqType, gsl::not_null pindexStart, - size_t nCountRequested) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums); - - bool IsWatching() const { return m_quorums_watch; } - -private: - // all private methods here are cs_main-free - void CheckQuorumConnections(CConnman& connman, const Consensus::LLMQParams& llmqParams, - gsl::not_null pindexNew) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_scan_quorums, !cs_map_quorums); - - CQuorumPtr BuildQuorumFromCommitment(Consensus::LLMQType llmqType, - gsl::not_null pQuorumBaseBlockIndex, - bool populate_cache) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums); - bool BuildQuorumContributions(const CFinalCommitmentPtr& fqc, const std::shared_ptr& quorum) const; - - CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, gsl::not_null pindex, - bool populate_cache = true) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums); - /// Returns the start offset for the masternode with the given proTxHash. This offset is applied when picking data recovery members of a quorum's - /// memberlist and is calculated based on a list of all member of all active quorums for the given llmqType in a way that each member - /// should receive the same number of request if all active llmqType members requests data from one llmqType quorum. - size_t GetQuorumRecoveryStartOffset(const CQuorum& quorum, gsl::not_null pIndex) const; - - void StartCachePopulatorThread(CQuorumCPtr pQuorum) const; - void StartQuorumDataRecoveryThread(CConnman& connman, CQuorumCPtr pQuorum, gsl::not_null pIndex, - uint16_t nDataMask) const EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); - - void StartCleanupOldQuorumDataThread(gsl::not_null pIndex) const; - void MigrateOldQuorumDB(CEvoDB& evoDb) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); -}; - -// when selecting a quorum for signing and verification, we use CQuorumManager::SelectQuorum with this offset as -// starting height for scanning. This is because otherwise the resulting signatures would not be verifiable by nodes -// which are not 100% at the chain tip. -static constexpr int SIGN_HEIGHT_OFFSET{8}; - -CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, const CChain& active_chain, const CQuorumManager& qman, - const uint256& selectionHash, int signHeight = -1 /*chain tip*/, int signOffset = SIGN_HEIGHT_OFFSET); - -// Verifies a recovered sig that was signed while the chain tip was at signedAtTip -VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman, - int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig, - int signOffset = SIGN_HEIGHT_OFFSET); } // namespace llmq template struct SaltedHasherImpl; diff --git a/src/llmq/quorumsman.cpp b/src/llmq/quorumsman.cpp new file mode 100644 index 000000000000..ffcb90878fbf --- /dev/null +++ b/src/llmq/quorumsman.cpp @@ -0,0 +1,753 @@ +// Copyright (c) 2018-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace llmq { +CQuorumManager::CQuorumManager(CBLSWorker& _blsWorker, CDeterministicMNManager& dmnman, CEvoDB& _evoDb, + CQuorumBlockProcessor& _quorumBlockProcessor, CQuorumSnapshotManager& qsnapman, + const ChainstateManager& chainman, const util::DbWrapperParams& db_params) : + blsWorker{_blsWorker}, + m_dmnman{dmnman}, + quorumBlockProcessor{_quorumBlockProcessor}, + m_qsnapman{qsnapman}, + m_chainman{chainman}, + db{util::MakeDbWrapper({db_params.path / "llmq" / "quorumdb", db_params.memory, db_params.wipe, /*cache_size=*/1 << 20})} +{ + utils::InitQuorumsCache(mapQuorumsCache, m_chainman.GetConsensus(), /*limit_by_connections=*/false); + m_cache_interrupt.reset(); + m_cache_thread = std::thread(&util::TraceThread, "q-cache", [this] { CacheWarmingThreadMain(); }); + MigrateOldQuorumDB(_evoDb); +} + +CQuorumManager::~CQuorumManager() +{ + if (m_cache_thread.joinable()) { + m_cache_interrupt(); + m_cache_thread.join(); + } +} + +bool CQuorumManager::GetEncryptedContributions(Consensus::LLMQType llmq_type, const CBlockIndex* block_index, + const std::vector& valid_members, const uint256& protx_hash, + std::vector>& vec_enc) const +{ + if (m_qdkgsman) { + return m_qdkgsman->GetEncryptedContributions(llmq_type, block_index, valid_members, protx_hash, vec_enc); + } + return false; +} + +CQuorumPtr CQuorumManager::BuildQuorumFromCommitment(const Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, bool populate_cache) const +{ + const uint256& quorumHash{pQuorumBaseBlockIndex->GetBlockHash()}; + + auto [qc, minedBlockHash] = quorumBlockProcessor.GetMinedCommitment(llmqType, quorumHash); + if (minedBlockHash == uint256::ZERO) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- No mined commitment for llmqType[%d] nHeight[%d] quorumHash[%s]\n", __func__, ToUnderlying(llmqType), pQuorumBaseBlockIndex->nHeight, pQuorumBaseBlockIndex->GetBlockHash().ToString()); + return nullptr; + } + assert(qc.quorumHash == pQuorumBaseBlockIndex->GetBlockHash()); + + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + assert(llmq_params_opt.has_value()); + auto quorum = std::make_shared(llmq_params_opt.value(), blsWorker); + auto members = utils::GetAllQuorumMembers(qc.llmqType, {m_dmnman, m_qsnapman, m_chainman, pQuorumBaseBlockIndex}); + + quorum->Init(std::make_unique(std::move(qc)), pQuorumBaseBlockIndex, minedBlockHash, members); + + if (populate_cache && llmq_params_opt->size == 1) { + WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum)); + + return quorum; + } + + bool hasValidVvec = false; + if (WITH_LOCK(cs_db, return quorum->ReadContributions(*db))) { + hasValidVvec = true; + } else { + if (BuildQuorumContributions(quorum->qc, quorum)) { + WITH_LOCK(cs_db, quorum->WriteContributions(*db)); + hasValidVvec = true; + } else { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- llmqType[%d] quorumIndex[%d] quorum.ReadContributions and BuildQuorumContributions for quorumHash[%s] failed\n", __func__, ToUnderlying(llmqType), quorum->qc->quorumIndex, quorum->qc->quorumHash.ToString()); + } + } + + if (hasValidVvec && populate_cache) { + // pre-populate caches in the background + // recovering public key shares is quite expensive and would result in serious lags for the first few signing + // sessions if the shares would be calculated on-demand + QueueQuorumForWarming(quorum); + } + + WITH_LOCK(cs_map_quorums, mapQuorumsCache[llmqType].insert(quorumHash, quorum)); + + return quorum; +} + +bool CQuorumManager::BuildQuorumContributions(const CFinalCommitmentPtr& fqc, const std::shared_ptr& quorum) const +{ + std::vector memberIndexes; + std::vector vvecs; + std::vector skContributions; + if (!m_qdkgsman || + !m_qdkgsman->GetVerifiedContributions((Consensus::LLMQType)fqc->llmqType, quorum->m_quorum_base_block_index, + fqc->validMembers, memberIndexes, vvecs, skContributions)) { + return false; + } + + cxxtimer::Timer t2(true); + quorum->SetVerificationVector(blsWorker.BuildQuorumVerificationVector(vvecs)); + if (!quorum->HasVerificationVector()) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build quorumVvec\n", __func__); + // without the quorum vvec, there can't be a skShare, so we fail here. Failure is not fatal here, as it still + // allows to use the quorum as a non-member (verification through the quorum pub key) + return false; + } + if (!m_handler || !m_handler->SetQuorumSecretKeyShare(*quorum, skContributions)) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- failed to build skShare\n", __func__); + // We don't bail out here as this is not a fatal error and still allows us to recover public key shares (as we + // have a valid quorum vvec at this point) + } + t2.stop(); + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- built quorum vvec and skShare. time=%d\n", __func__, t2.count()); + + return true; +} + +bool CQuorumManager::HasQuorum(Consensus::LLMQType llmqType, const CQuorumBlockProcessor& quorum_block_processor, const uint256& quorumHash) +{ + return quorum_block_processor.HasMinedCommitment(llmqType, quorumHash); +} + +bool CQuorumManager::RequestQuorumData(CNode* pfrom, CConnman& connman, const CQuorum& quorum, uint16_t nDataMask, + const uint256& proTxHash) const +{ + if (pfrom == nullptr) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid pfrom: nullptr\n", __func__); + return false; + } + if (pfrom->GetVerifiedProRegTxHash().IsNull()) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- pfrom is not a verified masternode\n", __func__); + return false; + } + const Consensus::LLMQType llmqType = quorum.qc->llmqType; + if (!Params().GetLLMQ(llmqType).has_value()) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid llmqType: %d\n", __func__, ToUnderlying(llmqType)); + return false; + } + const CBlockIndex* pindex{quorum.m_quorum_base_block_index}; + if (pindex == nullptr) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Invalid m_quorum_base_block_index : nullptr\n", __func__); + return false; + } + + LOCK(cs_data_requests); + const CQuorumDataRequestKey key(pfrom->GetVerifiedProRegTxHash(), true, pindex->GetBlockHash(), llmqType); + const CQuorumDataRequest request(llmqType, pindex->GetBlockHash(), nDataMask, proTxHash); + auto [old_pair, inserted] = mapQuorumDataRequests.emplace(key, request); + if (!inserted) { + if (old_pair->second.IsExpired(/*add_bias=*/true)) { + old_pair->second = request; + } else { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- Already requested\n", __func__); + return false; + } + } + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- sending QGETDATA quorumHash[%s] llmqType[%d] proRegTx[%s]\n", __func__, key.quorumHash.ToString(), + ToUnderlying(key.llmqType), key.proRegTx.ToString()); + + CNetMsgMaker msgMaker(pfrom->GetCommonVersion()); + connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::QGETDATA, request)); + + return true; +} + +std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const +{ + const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.ActiveTip()); + return ScanQuorums(llmqType, pindex, nCountRequested); +} + +std::vector CQuorumManager::ScanQuorums(Consensus::LLMQType llmqType, + gsl::not_null pindexStart, + size_t nCountRequested) const +{ + if (nCountRequested == 0 || !m_chainman.IsQuorumTypeEnabled(llmqType, pindexStart)) { + return {}; + } + + gsl::not_null pindexStore{pindexStart}; + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + assert(llmq_params_opt.has_value()); + + // Quorum sets can only change during the mining phase of DKG. + // Find the closest known block index. + const int quorumCycleStartHeight = pindexStart->nHeight - (pindexStart->nHeight % llmq_params_opt->dkgInterval); + const int quorumCycleMiningStartHeight = quorumCycleStartHeight + llmq_params_opt->dkgMiningWindowStart; + const int quorumCycleMiningEndHeight = quorumCycleStartHeight + llmq_params_opt->dkgMiningWindowEnd; + + if (pindexStart->nHeight < quorumCycleMiningStartHeight) { + // too early for this cycle, use the previous one + // bail out if it's below genesis block + if (quorumCycleMiningEndHeight < llmq_params_opt->dkgInterval) return {}; + pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight - llmq_params_opt->dkgInterval); + } else if (pindexStart->nHeight > quorumCycleMiningEndHeight) { + // we are past the mining phase of this cycle, use it + pindexStore = pindexStart->GetAncestor(quorumCycleMiningEndHeight); + } + // everything else is inside the mining phase of this cycle, no pindexStore adjustment needed + + gsl::not_null pIndexScanCommitments{pindexStore}; + size_t nScanCommitments{nCountRequested}; + std::vector vecResultQuorums; + + { + LOCK(cs_scan_quorums); + if (scanQuorumsCache.empty()) { + for (const auto& llmq : Params().GetConsensus().llmqs) { + // NOTE: We store it for each block hash in the DKG mining phase here + // and not for a single quorum hash per quorum like we do for other caches. + // And we only do this for max_cycles() of the most recent quorums + // because signing by old quorums requires the exact quorum hash to be specified + // and quorum scanning isn't needed there. + scanQuorumsCache.try_emplace(llmq.type, llmq.max_cycles(llmq.keepOldConnections) * (llmq.dkgMiningWindowEnd - llmq.dkgMiningWindowStart)); + } + } + auto& cache = scanQuorumsCache[llmqType]; + bool fCacheExists = cache.get(pindexStore->GetBlockHash(), vecResultQuorums); + if (fCacheExists) { + // We have exactly what requested so just return it + if (vecResultQuorums.size() == nCountRequested) { + return vecResultQuorums; + } + // If we have more cached than requested return only a subvector + if (vecResultQuorums.size() > nCountRequested) { + return {vecResultQuorums.begin(), vecResultQuorums.begin() + nCountRequested}; + } + // If we have cached quorums but not enough, subtract what we have from the count and the set correct index where to start + // scanning for the rests + if (!vecResultQuorums.empty()) { + nScanCommitments -= vecResultQuorums.size(); + // bail out if it's below genesis block + if (vecResultQuorums.back()->m_quorum_base_block_index->pprev == nullptr) return {}; + pIndexScanCommitments = vecResultQuorums.back()->m_quorum_base_block_index->pprev; + } + } else { + // If there is nothing in cache request at least keepOldConnections because this gets cached then later + nScanCommitments = std::max(nCountRequested, static_cast(llmq_params_opt->keepOldConnections)); + } + } + + // Get the block indexes of the mined commitments to build the required quorums from + std::vector pQuorumBaseBlockIndexes{ llmq_params_opt->useRotation ? + quorumBlockProcessor.GetMinedCommitmentsIndexedUntilBlock(llmqType, pIndexScanCommitments, nScanCommitments) : + quorumBlockProcessor.GetMinedCommitmentsUntilBlock(llmqType, pIndexScanCommitments, nScanCommitments) + }; + vecResultQuorums.reserve(vecResultQuorums.size() + pQuorumBaseBlockIndexes.size()); + + for (auto& pQuorumBaseBlockIndex : pQuorumBaseBlockIndexes) { + assert(pQuorumBaseBlockIndex); + // populate cache for keepOldConnections most recent quorums only + bool populate_cache = vecResultQuorums.size() < static_cast(llmq_params_opt->keepOldConnections); + + // We assume that every quorum asked for is available to us on hand, if this + // fails then we can assume that something has gone wrong and we should stop + // trying to process any further and return a blank. + auto quorum = GetQuorum(llmqType, pQuorumBaseBlockIndex, populate_cache); + if (!quorum) { + LogPrintf("%s: ERROR! Unexpected missing quorum with llmqType=%d, blockHash=%s, populate_cache=%s\n", + __func__, ToUnderlying(llmqType), pQuorumBaseBlockIndex->GetBlockHash().ToString(), + populate_cache ? "true" : "false"); + return {}; + } + vecResultQuorums.emplace_back(quorum); + } + + const size_t nCountResult{vecResultQuorums.size()}; + if (nCountResult > 0) { + LOCK(cs_scan_quorums); + // Don't cache more than keepOldConnections elements + // because signing by old quorums requires the exact quorum hash + // to be specified and quorum scanning isn't needed there. + auto& cache = scanQuorumsCache[llmqType]; + const size_t nCacheEndIndex = std::min(nCountResult, static_cast(llmq_params_opt->keepOldConnections)); + cache.emplace(pindexStore->GetBlockHash(), {vecResultQuorums.begin(), vecResultQuorums.begin() + nCacheEndIndex}); + } + // Don't return more than nCountRequested elements + const size_t nResultEndIndex = std::min(nCountResult, nCountRequested); + return {vecResultQuorums.begin(), vecResultQuorums.begin() + nResultEndIndex}; +} + +bool CQuorumManager::IsMasternode() const +{ + if (m_handler) { + return m_handler->IsMasternode(); + } + return false; +} + +bool CQuorumManager::IsWatching() const +{ + if (m_handler) { + return m_handler->IsWatching(); + } + return false; +} + +bool CQuorumManager::IsDataRequestPending(const uint256& proRegTx, bool we_requested, const uint256& quorumHash, + Consensus::LLMQType llmqType) const +{ + const CQuorumDataRequestKey key{proRegTx, we_requested, quorumHash, llmqType}; + LOCK(cs_data_requests); + const auto it = mapQuorumDataRequests.find(key); + return it != mapQuorumDataRequests.end() && !it->second.IsExpired(/*add_bias=*/true); +} + +DataRequestStatus CQuorumManager::GetDataRequestStatus(const uint256& proRegTx, bool we_requested, + const uint256& quorumHash, Consensus::LLMQType llmqType) const +{ + const CQuorumDataRequestKey key{proRegTx, we_requested, quorumHash, llmqType}; + LOCK(cs_data_requests); + const auto it = mapQuorumDataRequests.find(key); + if (it == mapQuorumDataRequests.end()) { + return DataRequestStatus::NotFound; + } + if (it->second.IsProcessed()) { + return DataRequestStatus::Processed; + } + return DataRequestStatus::Pending; +} + +void CQuorumManager::CleanupExpiredDataRequests() const +{ + LOCK(cs_data_requests); + auto it = mapQuorumDataRequests.begin(); + while (it != mapQuorumDataRequests.end()) { + if (it->second.IsExpired(/*add_bias=*/true)) { + it = mapQuorumDataRequests.erase(it); + } else { + ++it; + } + } +} + +void CQuorumManager::CleanupOldQuorumData(const std::set& dbKeysToSkip) const +{ + LOCK(cs_db); + DataCleanupHelper(*db, dbKeysToSkip); +} + +CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const +{ + const CBlockIndex* pQuorumBaseBlockIndex = [&]() { + // Lock contention may still be high here; consider using a shared lock + // We cannot hold cs_quorumBaseBlockIndexCache the whole time as that creates lock-order inversion with cs_main; + // We cannot acquire cs_main if we have cs_quorumBaseBlockIndexCache held + const CBlockIndex* pindex; + if (!WITH_LOCK(cs_quorumBaseBlockIndexCache, return quorumBaseBlockIndexCache.get(quorumHash, pindex))) { + pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(quorumHash)); + if (pindex) { + LOCK(cs_quorumBaseBlockIndexCache); + quorumBaseBlockIndexCache.insert(quorumHash, pindex); + } + } + return pindex; + }(); + if (!pQuorumBaseBlockIndex) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- block %s not found\n", __func__, quorumHash.ToString()); + return nullptr; + } + return GetQuorum(llmqType, pQuorumBaseBlockIndex); +} + +CQuorumCPtr CQuorumManager::GetQuorum(Consensus::LLMQType llmqType, gsl::not_null pQuorumBaseBlockIndex, bool populate_cache) const +{ + auto quorumHash = pQuorumBaseBlockIndex->GetBlockHash(); + + // we must check this before we look into the cache. Reorgs might have happened which would mean we might have + // cached quorums which are not in the active chain anymore + if (!HasQuorum(llmqType, quorumBlockProcessor, quorumHash)) { + return nullptr; + } + + CQuorumPtr pQuorum; + if (LOCK(cs_map_quorums); mapQuorumsCache[llmqType].get(quorumHash, pQuorum)) { + return pQuorum; + } + + return BuildQuorumFromCommitment(llmqType, pQuorumBaseBlockIndex, populate_cache); +} + +MessageProcessingResult CQuorumManager::ProcessMessage(CNode& pfrom, CConnman& connman, std::string_view msg_type, CDataStream& vRecv) +{ + if (msg_type == NetMsgType::QGETDATA) { + if (!IsMasternode() || (pfrom.GetVerifiedProRegTxHash().IsNull() && !pfrom.qwatch)) { + return MisbehavingError{10, "not a verified masternode or a qwatch connection"}; + } + + CQuorumDataRequest request; + vRecv >> request; + + auto sendQDATA = [&](CQuorumDataRequest::Errors nError, + bool request_limit_exceeded, + const CDataStream& body = CDataStream(SER_NETWORK, PROTOCOL_VERSION)) -> MessageProcessingResult { + MessageProcessingResult ret{}; + switch (nError) { + case (CQuorumDataRequest::Errors::NONE): + case (CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID): + case (CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND): + case (CQuorumDataRequest::Errors::QUORUM_NOT_FOUND): + case (CQuorumDataRequest::Errors::MASTERNODE_IS_NO_MEMBER): + case (CQuorumDataRequest::Errors::UNDEFINED): + if (request_limit_exceeded) ret = MisbehavingError{25, "request limit exceeded"}; + break; + case (CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING): + case (CQuorumDataRequest::Errors::ENCRYPTED_CONTRIBUTIONS_MISSING): + // Do not punish limit exceed if we don't have the requested data + break; + } + request.SetError(nError); + CDataStream ssResponse{SER_NETWORK, pfrom.GetCommonVersion()}; + ssResponse << request << body; + connman.PushMessage(&pfrom, CNetMsgMaker(pfrom.GetCommonVersion()).Make(NetMsgType::QDATA, ssResponse)); + return ret; + }; + + bool request_limit_exceeded(false); + { + LOCK2(::cs_main, cs_data_requests); + const CQuorumDataRequestKey key(pfrom.GetVerifiedProRegTxHash(), false, request.GetQuorumHash(), request.GetLLMQType()); + auto it = mapQuorumDataRequests.find(key); + if (it == mapQuorumDataRequests.end()) { + it = mapQuorumDataRequests.emplace(key, request).first; + } else if (it->second.IsExpired(/*add_bias=*/false)) { + it->second = request; + } else { + request_limit_exceeded = true; + } + } + + if (!Params().GetLLMQ(request.GetLLMQType()).has_value()) { + return sendQDATA(CQuorumDataRequest::Errors::QUORUM_TYPE_INVALID, request_limit_exceeded); + } + + const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(request.GetQuorumHash())); + if (pQuorumBaseBlockIndex == nullptr) { + return sendQDATA(CQuorumDataRequest::Errors::QUORUM_BLOCK_NOT_FOUND, request_limit_exceeded); + } + + const auto pQuorum = GetQuorum(request.GetLLMQType(), pQuorumBaseBlockIndex); + if (pQuorum == nullptr) { + return sendQDATA(CQuorumDataRequest::Errors::QUORUM_NOT_FOUND, request_limit_exceeded); + } + + CDataStream ssResponseData(SER_NETWORK, pfrom.GetCommonVersion()); + + // Check if request wants QUORUM_VERIFICATION_VECTOR data + if (request.GetDataMask() & CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR) { + if (!pQuorum->HasVerificationVector()) { + return sendQDATA(CQuorumDataRequest::Errors::QUORUM_VERIFICATION_VECTOR_MISSING, request_limit_exceeded); + } + + WITH_LOCK(pQuorum->cs_vvec_shShare, ssResponseData << *pQuorum->quorumVvec); + } + + // Check if request wants ENCRYPTED_CONTRIBUTIONS data + CQuorumDataRequest::Errors ret_err{CQuorumDataRequest::Errors::NONE}; + MessageProcessingResult qdata_ret{}, ret{}; + if (m_handler) { + ret = m_handler->ProcessContribQGETDATA(request_limit_exceeded, ssResponseData, *pQuorum, request, pQuorumBaseBlockIndex); + if (auto request_err = request.GetError(); request_err != CQuorumDataRequest::Errors::NONE && + request_err != CQuorumDataRequest::Errors::UNDEFINED) { + ret_err = request_err; + } + } + // sendQDATA also pushes a message independent of the returned value + if (ret_err != CQuorumDataRequest::Errors::NONE) { + qdata_ret = sendQDATA(ret_err, request_limit_exceeded); + } else { + qdata_ret = sendQDATA(CQuorumDataRequest::Errors::NONE, request_limit_exceeded, ssResponseData); + } + return ret.empty() ? qdata_ret : ret; + } + + if (msg_type == NetMsgType::QDATA) { + if ((!IsMasternode() && !IsWatching()) || pfrom.GetVerifiedProRegTxHash().IsNull()) { + return MisbehavingError{10, "not a verified masternode and -watchquorums is not enabled"}; + } + + CQuorumDataRequest request; + vRecv >> request; + + { + LOCK2(::cs_main, cs_data_requests); + const CQuorumDataRequestKey key(pfrom.GetVerifiedProRegTxHash(), true, request.GetQuorumHash(), request.GetLLMQType()); + auto it = mapQuorumDataRequests.find(key); + if (it == mapQuorumDataRequests.end()) { + return MisbehavingError{10, "not requested"}; + } + if (it->second.IsProcessed()) { + return MisbehavingError(10, "already received"); + } + if (request != it->second) { + return MisbehavingError(10, "not like requested"); + } + it->second.SetProcessed(); + } + + if (request.GetError() != CQuorumDataRequest::Errors::NONE) { + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s: Error %d (%s), from peer=%d\n", __func__, msg_type, request.GetError(), request.GetErrorString(), pfrom.GetId()); + return {}; + } + + CQuorumPtr pQuorum; + { + if (LOCK(cs_map_quorums); !mapQuorumsCache[request.GetLLMQType()].get(request.GetQuorumHash(), pQuorum)) { + // Don't bump score because we asked for it + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s: Quorum not found, from peer=%d\n", __func__, msg_type, pfrom.GetId()); + return {}; + } + } + + // Check if request has QUORUM_VERIFICATION_VECTOR data + if (request.GetDataMask() & CQuorumDataRequest::QUORUM_VERIFICATION_VECTOR) { + + std::vector verificationVector; + vRecv >> verificationVector; + + if (pQuorum->SetVerificationVector(verificationVector)) { + QueueQuorumForWarming(pQuorum); + } else { + return MisbehavingError{10, "invalid quorum verification vector"}; + } + } + + // Check if request has ENCRYPTED_CONTRIBUTIONS data + if (m_handler) { + if (auto ret = m_handler->ProcessContribQDATA(pfrom, vRecv, *pQuorum, request); !ret.empty()) { + return ret; + } + } + + WITH_LOCK(cs_db, pQuorum->WriteContributions(*db)); + return {}; + } + + return {}; +} + +void CQuorumManager::CacheWarmingThreadMain() const +{ + while (!m_cache_interrupt) { + CQuorumCPtr pQuorum; + { + LOCK(m_cache_cs); + if (!m_cache_queue.empty()) { + pQuorum = std::move(m_cache_queue.front()); + m_cache_queue.pop_front(); + }; + } + + if (!pQuorum) { + m_cache_interrupt.sleep_for(std::chrono::milliseconds(100)); + continue; + } + + cxxtimer::Timer t(true); + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- type=%d height=%d hash=%s start\n", __func__, + ToUnderlying(pQuorum->params.type), pQuorum->m_quorum_base_block_index->nHeight, + pQuorum->m_quorum_base_block_index->GetBlockHash().ToString()); + + // when then later some other thread tries to get keys, it will be much faster + for (const auto i : irange::range(pQuorum->members.size())) { + if (m_cache_interrupt) { + break; + } + if (pQuorum->qc->validMembers[i]) { + pQuorum->GetPubKeyShare(i); + } + } + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- type=%d height=%d hash=%s done. time=%d\n", __func__, + ToUnderlying(pQuorum->params.type), pQuorum->m_quorum_base_block_index->nHeight, + pQuorum->m_quorum_base_block_index->GetBlockHash().ToString(), t.count()); + } +} + +void CQuorumManager::QueueQuorumForWarming(CQuorumCPtr pQuorum) const +{ + if (pQuorum->HasVerificationVector()) { + LOCK(m_cache_cs); + m_cache_queue.push_back(std::move(pQuorum)); + } +} + +// TODO: remove in v23 +void CQuorumManager::MigrateOldQuorumDB(CEvoDB& evoDb) const +{ + LOCK(cs_db); + if (!db->IsEmpty()) return; + + const auto prefixes = {DB_QUORUM_QUORUM_VVEC, DB_QUORUM_SK_SHARE}; + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- start\n", __func__); + + CDBBatch batch(*db); + std::unique_ptr pcursor(evoDb.GetRawDB().NewIterator()); + + for (const auto& prefix : prefixes) { + auto start = std::make_tuple(prefix, uint256()); + pcursor->Seek(start); + + int count{0}; + while (pcursor->Valid()) { + decltype(start) k; + CDataStream s(SER_DISK, CLIENT_VERSION); + CBLSSecretKey sk; + + if (!pcursor->GetKey(k) || std::get<0>(k) != prefix) { + break; + } + + if (prefix == DB_QUORUM_QUORUM_VVEC) { + if (!evoDb.GetRawDB().ReadDataStream(k, s)) { + break; + } + batch.Write(k, s); + } + if (prefix == DB_QUORUM_SK_SHARE) { + if (!pcursor->GetValue(sk)) { + break; + } + batch.Write(k, sk); + } + + if (batch.SizeEstimate() >= (1 << 24)) { + db->WriteBatch(batch); + batch.Clear(); + } + + ++count; + pcursor->Next(); + } + + db->WriteBatch(batch); + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- %s moved %d\n", __func__, prefix, count); + } + + pcursor.reset(); + db->CompactFull(); + + DataCleanupHelper(evoDb.GetRawDB(), {}); + evoDb.CommitRootTransaction(); + + LogPrint(BCLog::LLMQ, "CQuorumManager::%s -- done\n", __func__); +} + +CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, const CChain& active_chain, const CQuorumManager& qman, + const uint256& selectionHash, int signHeight, int signOffset) +{ + size_t poolSize = llmq_params.signingActiveQuorumCount; + + CBlockIndex* pindexStart; + { + LOCK(::cs_main); + if (signHeight == -1) { + signHeight = active_chain.Height(); + } + int startBlockHeight = signHeight - signOffset; + if (startBlockHeight > active_chain.Height() || startBlockHeight < 0) { + return {}; + } + pindexStart = active_chain[startBlockHeight]; + } + + if (IsQuorumRotationEnabled(llmq_params, pindexStart)) { + auto quorums = qman.ScanQuorums(llmq_params.type, pindexStart, poolSize); + if (quorums.empty()) { + return nullptr; + } + //log2 int + int n = std::log2(llmq_params.signingActiveQuorumCount); + //Extract last 64 bits of selectionHash + uint64_t b = selectionHash.GetUint64(3); + //Take last n bits of b + uint64_t signer = (((1ull << n) - 1) & (b >> (64 - n - 1))); + + if (signer > quorums.size()) { + return nullptr; + } + auto itQuorum = std::find_if(quorums.begin(), + quorums.end(), + [signer](const CQuorumCPtr& obj) { + return uint64_t(obj->qc->quorumIndex) == signer; + }); + if (itQuorum == quorums.end()) { + return nullptr; + } + return *itQuorum; + } else { + auto quorums = qman.ScanQuorums(llmq_params.type, pindexStart, poolSize); + if (quorums.empty()) { + return nullptr; + } + + std::vector> scores; + scores.reserve(quorums.size()); + for (const auto i : irange::range(quorums.size())) { + CHashWriter h(SER_NETWORK, 0); + h << llmq_params.type; + h << quorums[i]->qc->quorumHash; + h << selectionHash; + scores.emplace_back(h.GetHash(), i); + } + std::sort(scores.begin(), scores.end()); + return quorums[scores.front().second]; + } +} + +VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman, + int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig, + const int signOffset) +{ + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + assert(llmq_params_opt.has_value()); + auto quorum = SelectQuorumForSigning(llmq_params_opt.value(), active_chain, qman, id, signedAtHeight, signOffset); + if (!quorum) { + return VerifyRecSigStatus::NoQuorum; + } + + SignHash signHash{llmqType, quorum->qc->quorumHash, id, msgHash}; + const bool ret = sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get()); + return ret ? VerifyRecSigStatus::Valid : VerifyRecSigStatus::Invalid; +} +} // namespace llmq diff --git a/src/llmq/quorumsman.h b/src/llmq/quorumsman.h new file mode 100644 index 000000000000..98fdaf5f6c65 --- /dev/null +++ b/src/llmq/quorumsman.h @@ -0,0 +1,195 @@ +// Copyright (c) 2018-2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_LLMQ_QUORUMSMAN_H +#define BITCOIN_LLMQ_QUORUMSMAN_H + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +class CBLSSignature; +class CBLSWorker; +class CBlockIndex; +class CChain; +class CConnman; +class CDataStream; +class CDeterministicMNManager; +class CDBWrapper; +class CEvoDB; +class ChainstateManager; +class CNode; +namespace util { +struct DbWrapperParams; +} // namespace util + +namespace llmq { +enum class VerifyRecSigStatus : uint8_t { + NoQuorum, + Invalid, + Valid, +}; + +class CDKGSessionManager; +class CQuorumBlockProcessor; +class CQuorumSnapshotManager; +class QuorumObserver; +class QuorumParticipant; + +/** + * The quorum manager maintains quorums which were mined on chain. When a quorum is requested from the manager, + * it will lookup the commitment (through CQuorumBlockProcessor) and build a CQuorum object from it. + * + * It is also responsible for initialization of the intra-quorum connections for new quorums. + */ +class CQuorumManager final : public QuorumObserverParent +{ + friend class llmq::QuorumObserver; + friend class llmq::QuorumParticipant; + +private: + CBLSWorker& blsWorker; + CDeterministicMNManager& m_dmnman; + CQuorumBlockProcessor& quorumBlockProcessor; + CQuorumSnapshotManager& m_qsnapman; + const ChainstateManager& m_chainman; + llmq::CDKGSessionManager* m_qdkgsman{nullptr}; + llmq::QuorumObserver* m_handler{nullptr}; + +private: + mutable Mutex cs_db; + std::unique_ptr db GUARDED_BY(cs_db){nullptr}; + + mutable Mutex cs_data_requests; + mutable std::unordered_map mapQuorumDataRequests + GUARDED_BY(cs_data_requests); + + mutable Mutex cs_map_quorums; + mutable std::map> mapQuorumsCache GUARDED_BY(cs_map_quorums); + + mutable Mutex cs_scan_quorums; // TODO: merge cs_map_quorums, cs_scan_quorums mutexes + mutable std::map>> scanQuorumsCache + GUARDED_BY(cs_scan_quorums); + + // On mainnet, we have around 62 quorums active at any point; let's cache a little more than double that to be safe. + // it maps `quorum_hash` to `pindex` + mutable Mutex cs_quorumBaseBlockIndexCache; + mutable Uint256LruHashMap quorumBaseBlockIndexCache + GUARDED_BY(cs_quorumBaseBlockIndexCache); + + mutable Mutex m_cache_cs; + mutable std::deque m_cache_queue GUARDED_BY(m_cache_cs); + mutable CThreadInterrupt m_cache_interrupt; + mutable std::thread m_cache_thread; + +public: + CQuorumManager() = delete; + CQuorumManager(const CQuorumManager&) = delete; + CQuorumManager& operator=(const CQuorumManager&) = delete; + explicit CQuorumManager(CBLSWorker& _blsWorker, CDeterministicMNManager& dmnman, CEvoDB& _evoDb, + CQuorumBlockProcessor& _quorumBlockProcessor, CQuorumSnapshotManager& qsnapman, + const ChainstateManager& chainman, const util::DbWrapperParams& db_params); + ~CQuorumManager(); + + void ConnectManagers(gsl::not_null handler, gsl::not_null qdkgsman) + { + // Prohibit double initialization + assert(m_handler == nullptr); + m_handler = handler; + assert(m_qdkgsman == nullptr); + m_qdkgsman = qdkgsman; + } + void DisconnectManagers() + { + m_handler = nullptr; + m_qdkgsman = nullptr; + } + + bool GetEncryptedContributions(Consensus::LLMQType llmq_type, const CBlockIndex* block_index, + const std::vector& valid_members, const uint256& protx_hash, + std::vector>& vec_enc) const override; + + [[nodiscard]] MessageProcessingResult ProcessMessage(CNode& pfrom, CConnman& connman, std::string_view msg_type, + CDataStream& vRecv) + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_data_requests, !cs_map_quorums, !m_cache_cs); + + static bool HasQuorum(Consensus::LLMQType llmqType, const CQuorumBlockProcessor& quorum_block_processor, const uint256& quorumHash); + + bool RequestQuorumData(CNode* pfrom, CConnman& connman, const CQuorum& quorum, uint16_t nDataMask, + const uint256& proTxHash = uint256()) const override + EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); + + // all these methods will lock cs_main for a short period of time + CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, const uint256& quorumHash) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !m_cache_cs); + std::vector ScanQuorums(Consensus::LLMQType llmqType, size_t nCountRequested) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums, !m_cache_cs); + + // this one is cs_main-free + std::vector ScanQuorums(Consensus::LLMQType llmqType, gsl::not_null pindexStart, + size_t nCountRequested) const override + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !cs_scan_quorums, !m_cache_cs); + + bool IsMasternode() const; + bool IsWatching() const; + + bool IsDataRequestPending(const uint256& proRegTx, bool we_requested, const uint256& quorumHash, + Consensus::LLMQType llmqType) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); + DataRequestStatus GetDataRequestStatus(const uint256& proRegTx, bool we_requested, const uint256& quorumHash, + Consensus::LLMQType llmqType) const override + EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); + void CleanupExpiredDataRequests() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_data_requests); + void CleanupOldQuorumData(const std::set& dbKeysToSkip) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_db); + +private: + // all private methods here are cs_main-free + bool BuildQuorumContributions(const CFinalCommitmentPtr& fqc, const std::shared_ptr& quorum) const; + + CQuorumPtr BuildQuorumFromCommitment(Consensus::LLMQType llmqType, + gsl::not_null pQuorumBaseBlockIndex, + bool populate_cache) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !m_cache_cs); + + CQuorumCPtr GetQuorum(Consensus::LLMQType llmqType, gsl::not_null pindex, + bool populate_cache = true) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_db, !cs_map_quorums, !m_cache_cs); + + void QueueQuorumForWarming(CQuorumCPtr pQuorum) const EXCLUSIVE_LOCKS_REQUIRED(!m_cache_cs); + void CacheWarmingThreadMain() const EXCLUSIVE_LOCKS_REQUIRED(!m_cache_cs); + void MigrateOldQuorumDB(CEvoDB& evoDb) const EXCLUSIVE_LOCKS_REQUIRED(!cs_db); +}; + +// when selecting a quorum for signing and verification, we use CQuorumManager::SelectQuorum with this offset as +// starting height for scanning. This is because otherwise the resulting signatures would not be verifiable by nodes +// which are not 100% at the chain tip. +static constexpr int SIGN_HEIGHT_OFFSET{8}; + +CQuorumCPtr SelectQuorumForSigning(const Consensus::LLMQParams& llmq_params, const CChain& active_chain, const CQuorumManager& qman, + const uint256& selectionHash, int signHeight = -1 /*chain tip*/, int signOffset = SIGN_HEIGHT_OFFSET); + +// Verifies a recovered sig that was signed while the chain tip was at signedAtTip +VerifyRecSigStatus VerifyRecoveredSig(Consensus::LLMQType llmqType, const CChain& active_chain, const CQuorumManager& qman, + int signedAtHeight, const uint256& id, const uint256& msgHash, const CBLSSignature& sig, + int signOffset = SIGN_HEIGHT_OFFSET); +} // namespace llmq + +#endif // BITCOIN_LLMQ_QUORUMSMAN_H diff --git a/src/llmq/signing.cpp b/src/llmq/signing.cpp index b87a679a9d44..0e12e31bf547 100644 --- a/src/llmq/signing.cpp +++ b/src/llmq/signing.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include diff --git a/src/llmq/signing_shares.cpp b/src/llmq/signing_shares.cpp index c6e3e444f43d..6654ca3ed4dd 100644 --- a/src/llmq/signing_shares.cpp +++ b/src/llmq/signing_shares.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include diff --git a/src/masternode/active/context.cpp b/src/masternode/active/context.cpp index 4e9a79904561..84132b99ffa0 100644 --- a/src/masternode/active/context.cpp +++ b/src/masternode/active/context.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -14,7 +15,7 @@ #include #include #include -#include +#include #include #include @@ -22,7 +23,8 @@ ActiveContext::ActiveContext(CCoinJoinServer& cj_server, CConnman& connman, CDet CGovernanceManager& govman, ChainstateManager& chainman, CMasternodeMetaMan& mn_metaman, CMNHFManager& mnhfman, CSporkManager& sporkman, CTxMemPool& mempool, LLMQContext& llmq_ctx, PeerManager& peerman, const CActiveMasternodeManager& mn_activeman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params, bool quorums_watch) : + const CMasternodeSync& mn_sync, const llmq::QvvecSyncModeMap& sync_map, + const util::DbWrapperParams& db_params, bool quorums_recovery, bool quorums_watch) : m_llmq_ctx{llmq_ctx}, m_cj_server(cj_server), gov_signer{std::make_unique(connman, dmnman, govman, mn_activeman, chainman, mn_sync)}, @@ -34,6 +36,9 @@ ActiveContext::ActiveContext(CCoinJoinServer& cj_server, CConnman& connman, CDet mn_activeman, *llmq_ctx.qman, sporkman)}, ehf_sighandler{ std::make_unique(chainman, mnhfman, *llmq_ctx.sigman, *shareman, *llmq_ctx.qman)}, + qman_handler{std::make_unique(*llmq_ctx.bls_worker, connman, dmnman, *llmq_ctx.qman, + *llmq_ctx.qsnapman, mn_activeman, chainman, mn_sync, + sporkman, sync_map, quorums_recovery, quorums_watch)}, cl_signer{std::make_unique(chainman.ActiveChainstate(), *llmq_ctx.clhandler, *llmq_ctx.sigman, *shareman, sporkman, mn_sync)}, is_signer{std::make_unique(chainman.ActiveChainstate(), *llmq_ctx.clhandler, @@ -42,12 +47,12 @@ ActiveContext::ActiveContext(CCoinJoinServer& cj_server, CConnman& connman, CDet { m_llmq_ctx.clhandler->ConnectSigner(cl_signer.get()); m_llmq_ctx.isman->ConnectSigner(is_signer.get()); - m_llmq_ctx.qman->ConnectManager(qdkgsman.get()); + m_llmq_ctx.qman->ConnectManagers(qman_handler.get(), qdkgsman.get()); } ActiveContext::~ActiveContext() { - m_llmq_ctx.qman->DisconnectManager(); + m_llmq_ctx.qman->DisconnectManagers(); m_llmq_ctx.isman->DisconnectSigner(); m_llmq_ctx.clhandler->DisconnectSigner(); } @@ -59,6 +64,7 @@ void ActiveContext::Interrupt() void ActiveContext::Start(CConnman& connman, PeerManager& peerman) { + qman_handler->Start(); qdkgsman->StartThreads(connman, peerman); shareman->Start(); cl_signer->RegisterRecoveryInterface(); @@ -73,4 +79,5 @@ void ActiveContext::Stop() cl_signer->UnregisterRecoveryInterface(); shareman->Stop(); qdkgsman->StopThreads(); + qman_handler->Stop(); } diff --git a/src/masternode/active/context.h b/src/masternode/active/context.h index aeb13de1be6e..8896a7f7e86f 100644 --- a/src/masternode/active/context.h +++ b/src/masternode/active/context.h @@ -5,6 +5,8 @@ #ifndef BITCOIN_MASTERNODE_ACTIVE_CONTEXT_H #define BITCOIN_MASTERNODE_ACTIVE_CONTEXT_H +#include + #include class CActiveMasternodeManager; @@ -32,6 +34,7 @@ class CDKGDebugManager; class CDKGSessionManager; class CEHFSignalsHandler; class CSigSharesManager; +class QuorumParticipant; } // namespace llmq namespace util { struct DbWrapperParams; @@ -50,7 +53,8 @@ struct ActiveContext { CGovernanceManager& govman, ChainstateManager& chainman, CMasternodeMetaMan& mn_metaman, CMNHFManager& mnhfman, CSporkManager& sporkman, CTxMemPool& mempool, LLMQContext& llmq_ctx, PeerManager& peerman, const CActiveMasternodeManager& mn_activeman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params, bool quorums_watch); + const CMasternodeSync& mn_sync, const llmq::QvvecSyncModeMap& sync_map, + const util::DbWrapperParams& db_params, bool quorums_recovery, bool quorums_watch); ~ActiveContext(); void Interrupt(); @@ -68,6 +72,7 @@ struct ActiveContext { const std::unique_ptr qdkgsman; const std::unique_ptr shareman; const std::unique_ptr ehf_sighandler; + const std::unique_ptr qman_handler; private: /* diff --git a/src/masternode/active/notificationinterface.cpp b/src/masternode/active/notificationinterface.cpp index d9904f496b99..fa01867b17ff 100644 --- a/src/masternode/active/notificationinterface.cpp +++ b/src/masternode/active/notificationinterface.cpp @@ -4,6 +4,7 @@ #include +#include #include #include #include @@ -29,6 +30,7 @@ void ActiveNotificationInterface::UpdatedBlockTip(const CBlockIndex* pindexNew, m_active_ctx.ehf_sighandler->UpdatedBlockTip(pindexNew); m_active_ctx.gov_signer->UpdatedBlockTip(pindexNew); m_active_ctx.qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload); + m_active_ctx.qman_handler->UpdatedBlockTip(pindexNew, fInitialDownload); } void ActiveNotificationInterface::NotifyRecoveredSig(const std::shared_ptr& sig, bool proactive_relay) diff --git a/src/msg_result.h b/src/msg_result.h index 646d2142443c..e993778a11b1 100644 --- a/src/msg_result.h +++ b/src/msg_result.h @@ -67,6 +67,8 @@ struct MessageProcessingResult MessageProcessingResult(MisbehavingError error) : m_error(error) {} + + bool empty() const { return !m_error.has_value() && m_inventory.empty() && m_dsq.empty() && m_transactions.empty() && !m_to_erase.has_value(); } }; #endif // BITCOIN_MSG_RESULT_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 5a43dede1499..851848a60d5d 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -55,7 +55,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 9c2dbe91d39b..032e47318f90 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -54,7 +54,6 @@ std::optional LoadChainstate(bool fReset, bool is_spentindex_enabled, bool is_timeindex_enabled, const Consensus::Params& consensus_params, - const llmq::QvvecSyncModeMap& sync_map, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -62,8 +61,6 @@ std::optional LoadChainstate(bool fReset, bool block_tree_db_in_memory, bool coins_db_in_memory, bool dash_dbs_in_memory, - bool quorums_recovery, - bool quorums_watch, int8_t bls_threads, int64_t max_recsigs_age, std::function shutdown_requested, @@ -92,9 +89,8 @@ std::optional LoadChainstate(bool fReset, pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset)); DashChainstateSetup(chainman, govman, mn_metaman, mn_sync, sporkman, mn_activeman, chain_helper, cpoolman, - dmnman, evodb, mnhf_manager, llmq_ctx, mempool, data_dir, sync_map, dash_dbs_in_memory, - /*llmq_dbs_wipe=*/fReset || fReindexChainState, quorums_recovery, quorums_watch, - bls_threads, max_recsigs_age, consensus_params); + dmnman, evodb, mnhf_manager, llmq_ctx, mempool, data_dir, dash_dbs_in_memory, + /*llmq_dbs_wipe=*/fReset || fReindexChainState, bls_threads, max_recsigs_age, consensus_params); if (fReset) { pblocktree->WriteReindexing(true); @@ -228,11 +224,8 @@ void DashChainstateSetup(ChainstateManager& chainman, std::unique_ptr& llmq_ctx, CTxMemPool* mempool, const fs::path& data_dir, - const llmq::QvvecSyncModeMap& sync_map, bool llmq_dbs_in_memory, bool llmq_dbs_wipe, - bool quorums_recovery, - bool quorums_watch, int8_t bls_threads, int64_t max_recsigs_age, const Consensus::Params& consensus_params) @@ -248,9 +241,9 @@ void DashChainstateSetup(ChainstateManager& chainman, llmq_ctx->Stop(); } llmq_ctx.reset(); - llmq_ctx = std::make_unique(chainman, *dmnman, *evodb, sporkman, *mempool, mn_activeman.get(), mn_sync, sync_map, + llmq_ctx = std::make_unique(chainman, *dmnman, *evodb, sporkman, *mempool, mn_sync, util::DbWrapperParams{.path = data_dir, .memory = llmq_dbs_in_memory, .wipe = llmq_dbs_wipe}, - quorums_recovery, quorums_watch, bls_threads, max_recsigs_age); + bls_threads, max_recsigs_age); mempool->ConnectManagers(dmnman.get(), llmq_ctx->isman.get()); // Enable CMNHFManager::{Process, Undo}Block mnhf_manager->ConnectManagers(llmq_ctx->qman.get()); diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 3e28d5bf0447..f5c456c2abbe 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -5,8 +5,6 @@ #ifndef BITCOIN_NODE_CHAINSTATE_H #define BITCOIN_NODE_CHAINSTATE_H -#include - #include #include #include @@ -100,7 +98,6 @@ std::optional LoadChainstate(bool fReset, bool is_spentindex_enabled, bool is_timeindex_enabled, const Consensus::Params& consensus_params, - const llmq::QvvecSyncModeMap& sync_map, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -108,8 +105,6 @@ std::optional LoadChainstate(bool fReset, bool block_tree_db_in_memory, bool coins_db_in_memory, bool dash_dbs_in_memory, - bool quorums_recovery, - bool quorums_watch, int8_t bls_threads, int64_t max_recsigs_age, std::function shutdown_requested = nullptr, @@ -130,11 +125,8 @@ void DashChainstateSetup(ChainstateManager& chainman, std::unique_ptr& llmq_ctx, CTxMemPool* mempool, const fs::path& data_dir, - const llmq::QvvecSyncModeMap& sync_map, bool llmq_dbs_in_memory, bool llmq_dbs_wipe, - bool quorums_recovery, - bool quorums_watch, int8_t bls_threads, int64_t max_recsigs_age, const Consensus::Params& consensus_params); diff --git a/src/rpc/quorums.cpp b/src/rpc/quorums.cpp index 1fa468e1f48b..23474c50f6b4 100644 --- a/src/rpc/quorums.cpp +++ b/src/rpc/quorums.cpp @@ -23,7 +23,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index ec5c36eb9a62..cd999067b278 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -64,7 +64,6 @@ #include #include #include -#include #include #include #include @@ -148,8 +147,7 @@ void DashChainstateSetup(ChainstateManager& chainman, DashChainstateSetup(chainman, *Assert(node.govman.get()), *Assert(node.mn_metaman.get()), *Assert(node.mn_sync.get()), *Assert(node.sporkman.get()), node.mn_activeman, node.chain_helper, node.cpoolman, node.dmnman, node.evodb, node.mnhf_manager, node.llmq_ctx, Assert(node.mempool.get()), node.args->GetDataDirNet(), - llmq::QvvecSyncModeMap{}, llmq_dbs_in_memory, llmq_dbs_wipe, /*quorums_recovery=*/false, /*quorums_watch=*/false, - llmq::DEFAULT_BLSCHECK_THREADS, llmq::DEFAULT_MAX_RECOVERED_SIGS_AGE, consensus_params); + llmq_dbs_in_memory, llmq_dbs_wipe, llmq::DEFAULT_BLSCHECK_THREADS, llmq::DEFAULT_MAX_RECOVERED_SIGS_AGE, consensus_params); } void DashChainstateSetupClose(NodeContext& node) @@ -340,7 +338,6 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector wallet/walletdb -> wallet/wallet", "kernel/coinstats -> validation -> kernel/coinstats", # Dash - "banman -> common/bloom -> evo/assetlocktx -> llmq/quorums -> net -> banman", + "banman -> common/bloom -> evo/assetlocktx -> llmq/quorumsman -> net -> banman", "chainlock/chainlock -> instantsend/instantsend -> chainlock/chainlock", "chainlock/chainlock -> chainlock/signing -> llmq/signing_shares -> net_processing -> chainlock/chainlock", "chainlock/chainlock -> chainlock/signing -> llmq/signing_shares -> net_processing -> llmq/context -> chainlock/chainlock", "chainlock/chainlock -> chainlock/signing -> llmq/signing_shares -> net_processing -> masternode/active/context -> chainlock/chainlock", "chainlock/chainlock -> instantsend/instantsend -> instantsend/signing -> chainlock/chainlock", - "chainlock/chainlock -> llmq/quorums -> msg_result -> coinjoin/coinjoin -> chainlock/chainlock", + "chainlock/chainlock -> llmq/quorumsman -> msg_result -> coinjoin/coinjoin -> chainlock/chainlock", "chainlock/chainlock -> validation -> chainlock/chainlock", "chainlock/chainlock -> validation -> evo/chainhelper -> chainlock/chainlock", "chainlock/signing -> llmq/signing_shares -> net_processing -> masternode/active/context -> chainlock/signing", @@ -35,7 +35,7 @@ "coinjoin/coinjoin -> instantsend/instantsend -> instantsend/signing -> llmq/signing_shares -> net_processing -> coinjoin/coinjoin", "coinjoin/client -> coinjoin/coinjoin -> instantsend/instantsend -> instantsend/signing -> llmq/signing_shares -> net_processing -> coinjoin/walletman -> coinjoin/client", "common/bloom -> evo/assetlocktx -> llmq/commitment -> evo/deterministicmns -> evo/simplifiedmns -> merkleblock -> common/bloom", - "common/bloom -> evo/assetlocktx -> llmq/quorums -> net -> common/bloom", + "common/bloom -> evo/assetlocktx -> llmq/quorumsman -> net -> common/bloom", "consensus/tx_verify -> evo/assetlocktx -> llmq/commitment -> validation -> consensus/tx_verify", "consensus/tx_verify -> evo/assetlocktx -> llmq/commitment -> validation -> txmempool -> consensus/tx_verify", "evo/assetlocktx -> llmq/commitment -> validation -> txmempool -> evo/assetlocktx", diff --git a/test/util/data/non-backported.txt b/test/util/data/non-backported.txt index b9c3ecdf1ad5..57e15e517297 100644 --- a/test/util/data/non-backported.txt +++ b/test/util/data/non-backported.txt @@ -1,3 +1,5 @@ +src/active/*.cpp +src/active/*.h src/batchedlogger.* src/bench/bls*.cpp src/bls/*.cpp