diff --git a/doc/release-notes-7061.md b/doc/release-notes-7061.md new file mode 100644 index 000000000000..5af5d34d8e2e --- /dev/null +++ b/doc/release-notes-7061.md @@ -0,0 +1,8 @@ +# RPC changes + +- `quorum dkginfo` will now require that nodes run either in watch-only mode (`-watchquorums`) or as an + active masternode (i.e. masternode mode) as regular nodes do not have insight into network DKG activity. + +- `quorum dkgstatus` will no longer emit the return values `time`, `timeStr` and `session` on nodes that + do not run in either watch-only or masternode mode as regular nodes do not have insight into network + DKG activity. diff --git a/src/Makefile.am b/src/Makefile.am index d62df14146fd..105a3b9b1d86 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -145,6 +145,11 @@ endif .PHONY: FORCE check-symbols check-security # dash core # BITCOIN_CORE_H = \ + active/context.h \ + active/dkgsession.h \ + active/dkgsessionhandler.h \ + active/masternode.h \ + active/quorums.h \ addrdb.h \ addressindex.h \ spentindex.h \ @@ -271,6 +276,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 \ @@ -278,12 +284,11 @@ BITCOIN_CORE_H = \ llmq/snapshot.h \ llmq/types.h \ llmq/utils.h \ + llmq/observer/context.h \ + llmq/observer/quorums.h \ logging.h \ logging/timer.h \ mapport.h \ - masternode/active/context.h \ - masternode/active/notificationinterface.h \ - masternode/node.h \ masternode/meta.h \ masternode/payments.h \ masternode/sync.h \ @@ -470,6 +475,11 @@ 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/context.cpp \ + active/dkgsession.cpp \ + active/dkgsessionhandler.cpp \ + active/masternode.cpp \ + active/quorums.cpp \ addrdb.cpp \ addressindex.cpp \ addrman.cpp \ @@ -539,15 +549,15 @@ 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 \ - masternode/node.cpp \ masternode/meta.cpp \ masternode/payments.cpp \ masternode/sync.cpp \ @@ -910,6 +920,7 @@ libbitcoin_common_a_SOURCES = \ evo/netinfo.cpp \ external_signer.cpp \ governance/common.cpp \ + governance/core_write.cpp \ init/common.cpp \ key.cpp \ key_io.cpp \ diff --git a/src/active/context.cpp b/src/active/context.cpp new file mode 100644 index 000000000000..6af6a182e24e --- /dev/null +++ b/src/active/context.cpp @@ -0,0 +1,113 @@ +// Copyright (c) 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 + +ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman, CConnman& connman, + CDeterministicMNManager& dmnman, CDSTXManager& dstxman, CGovernanceManager& govman, + CMasternodeMetaMan& mn_metaman, CMNHFManager& mnhfman, CSporkManager& sporkman, + CTxMemPool& mempool, llmq::CChainLocksHandler& clhandler, llmq::CInstantSendManager& isman, + llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumManager& qman, + llmq::CQuorumSnapshotManager& qsnapman, llmq::CSigningManager& sigman, + PeerManager& peerman, const CMasternodeSync& mn_sync, const CBLSSecretKey& operator_sk, + const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, + bool quorums_recovery, bool quorums_watch) : + m_clhandler{clhandler}, + m_isman{isman}, + m_qman{qman}, + nodeman{std::make_unique(connman, dmnman, operator_sk)}, + cj_server{std::make_unique(chainman, connman, dmnman, dstxman, mn_metaman, mempool, peerman, + *nodeman, mn_sync, isman)}, + dkgdbgman{std::make_unique()}, + qdkgsman{std::make_unique(chainman.ActiveChainstate(), dmnman, qsnapman, sporkman, + db_params, quorums_watch)}, + shareman{std::make_unique(connman, chainman.ActiveChainstate(), sigman, peerman, *nodeman, + qman, sporkman)}, + gov_signer{std::make_unique(connman, dmnman, govman, *nodeman, chainman, mn_sync)}, + ehf_sighandler{std::make_unique(chainman, mnhfman, sigman, *shareman, qman)}, + qman_handler{std::make_unique(bls_worker, connman, dmnman, qman, qsnapman, *nodeman, + mn_sync, sporkman, sync_map, quorums_recovery, quorums_watch)}, + cl_signer{std::make_unique(chainman.ActiveChainstate(), clhandler, sigman, *shareman, + sporkman, mn_sync)}, + is_signer{std::make_unique(chainman.ActiveChainstate(), clhandler, isman, sigman, + *shareman, qman, sporkman, mempool, mn_sync)} +{ + qdkgsman->InitializeHandlers([&](const Consensus::LLMQParams& llmq_params, + int quorum_idx) -> std::unique_ptr { + return std::make_unique(bls_worker, chainman.ActiveChainstate(), dmnman, + mn_metaman, *dkgdbgman, *qdkgsman, qblockman, qsnapman, + *nodeman, sporkman, llmq_params, quorums_watch, + quorum_idx); + }); + m_clhandler.ConnectSigner(cl_signer.get()); + m_isman.ConnectSigner(is_signer.get()); + m_qman.ConnectManagers(qman_handler.get(), qdkgsman.get()); +} + +ActiveContext::~ActiveContext() +{ + m_qman.DisconnectManagers(); + m_isman.DisconnectSigner(); + m_clhandler.DisconnectSigner(); +} + +void ActiveContext::Interrupt() +{ + shareman->InterruptWorkerThread(); +} + +void ActiveContext::Start(CConnman& connman, PeerManager& peerman) +{ + qman_handler->Start(); + qdkgsman->StartThreads(connman, peerman); + shareman->Start(); + cl_signer->RegisterAsRecoveredSigsListener(); + is_signer->RegisterAsRecoveredSigsListener(); + shareman->RegisterAsRecoveredSigsListener(); +} + +void ActiveContext::Stop() +{ + shareman->UnregisterAsRecoveredSigsListener(); + is_signer->UnregisterAsRecoveredSigsListener(); + cl_signer->UnregisterAsRecoveredSigsListener(); + shareman->Stop(); + qdkgsman->StopThreads(); + qman_handler->Stop(); +} + +void ActiveContext::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +{ + if (fInitialDownload || pindexNew == pindexFork) // In IBD or blocks were disconnected without any new ones + return; + + nodeman->UpdatedBlockTip(pindexNew, pindexFork, fInitialDownload); + ehf_sighandler->UpdatedBlockTip(pindexNew); + gov_signer->UpdatedBlockTip(pindexNew); + qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload); + qman_handler->UpdatedBlockTip(pindexNew, fInitialDownload); +} + +void ActiveContext::NotifyRecoveredSig(const std::shared_ptr& sig, bool proactive_relay) +{ + shareman->NotifyRecoveredSig(sig, proactive_relay); +} diff --git a/src/active/context.h b/src/active/context.h new file mode 100644 index 000000000000..ef3e7a34aca1 --- /dev/null +++ b/src/active/context.h @@ -0,0 +1,102 @@ +// Copyright (c) 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_CONTEXT_H +#define BITCOIN_ACTIVE_CONTEXT_H + +#include + +#include + +#include + +class CActiveMasternodeManager; +class CBLSSecretKey; +class CBLSWorker; +class ChainstateManager; +class CCoinJoinServer; +class CConnman; +class CDeterministicMNManager; +class CDSTXManager; +class CGovernanceManager; +class CMasternodeMetaMan; +class CMasternodeSync; +class CMNHFManager; +class CSporkManager; +class CTxMemPool; +class GovernanceSigner; +class PeerManager; +namespace chainlock { +class ChainLockSigner; +} // namespace chainlock +namespace instantsend { +class InstantSendSigner; +} // namespace instantsend +namespace llmq { +class CChainLocksHandler; +class CDKGDebugManager; +class CDKGSessionManager; +class CEHFSignalsHandler; +class CInstantSendManager; +class CQuorumBlockProcessor; +class CQuorumManager; +class CQuorumSnapshotManager; +class CSigningManager; +class CSigSharesManager; +class QuorumParticipant; +} // namespace llmq +namespace util { +struct DbWrapperParams; +} // namespace util + +struct ActiveContext final : public CValidationInterface { +private: + llmq::CChainLocksHandler& m_clhandler; + llmq::CInstantSendManager& m_isman; + llmq::CQuorumManager& m_qman; + +public: + ActiveContext() = delete; + ActiveContext(const ActiveContext&) = delete; + ActiveContext& operator=(const ActiveContext&) = delete; + explicit ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman, CConnman& connman, + CDeterministicMNManager& dmnman, CDSTXManager& dstxman, CGovernanceManager& govman, + CMasternodeMetaMan& mn_metaman, CMNHFManager& mnhfman, CSporkManager& sporkman, + CTxMemPool& mempool, llmq::CChainLocksHandler& clhandler, llmq::CInstantSendManager& isman, + llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumManager& qman, + llmq::CQuorumSnapshotManager& qsnapman, llmq::CSigningManager& sigman, PeerManager& peerman, + const CMasternodeSync& mn_sync, const CBLSSecretKey& operator_sk, + const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, + bool quorums_recovery, bool quorums_watch); + ~ActiveContext(); + + void Interrupt(); + void Start(CConnman& connman, PeerManager& peerman); + void Stop(); + +protected: + // CValidationInterface + void NotifyRecoveredSig(const std::shared_ptr& sig, bool proactive_relay) override; + void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; + +public: + /* + * Entities that are only utilized when masternode mode is enabled + * and are accessible in their own right + */ + const std::unique_ptr nodeman; + const std::unique_ptr cj_server; + const std::unique_ptr dkgdbgman; + const std::unique_ptr qdkgsman; + const std::unique_ptr shareman; + +private: + const std::unique_ptr gov_signer; + const std::unique_ptr ehf_sighandler; + const std::unique_ptr qman_handler; + const std::unique_ptr cl_signer; + const std::unique_ptr is_signer; +}; + +#endif // BITCOIN_ACTIVE_CONTEXT_H diff --git a/src/active/dkgsession.cpp b/src/active/dkgsession.cpp new file mode 100644 index 000000000000..a289d7aed23b --- /dev/null +++ b/src/active/dkgsession.cpp @@ -0,0 +1,717 @@ +// 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 + +namespace llmq { +namespace dkg { +ActiveSession::ActiveSession(CBLSWorker& bls_worker, CDeterministicMNManager& dmnman, CDKGDebugManager& dkgdbgman, + CDKGSessionManager& qdkgsman, CMasternodeMetaMan& mn_metaman, CQuorumSnapshotManager& qsnapman, + const CActiveMasternodeManager& mn_activeman, const CSporkManager& sporkman, + const CBlockIndex* base_block_index, const Consensus::LLMQParams& params) : + CDKGSession(bls_worker, dmnman, dkgdbgman, qdkgsman, qsnapman, base_block_index, params), + m_mn_metaman{mn_metaman}, + m_mn_activeman{mn_activeman}, + m_sporkman{sporkman}, + m_use_legacy_bls{!DeploymentActiveAfter(m_quorum_base_block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_V19)} +{ +} + +ActiveSession::~ActiveSession() = default; + +void ActiveSession::Contribute(CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + CDKGLogger logger(*this, __func__, __LINE__); + + if (!AreWeMember()) { + return; + } + + assert(params.threshold > 1); // we should not get there with single-node-quorums + + cxxtimer::Timer t1(true); + logger.Batch("generating contributions"); + if (!blsWorker.GenerateContributions(params.threshold, memberIds, vvecContribution, m_sk_contributions)) { + // this should never happen actually + logger.Batch("GenerateContributions failed"); + return; + } + logger.Batch("generated contributions. time=%d", t1.count()); + logger.Flush(); + + SendContributions(pendingMessages, peerman); +} + +void ActiveSession::SendContributions(CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + CDKGLogger logger(*this, __func__, __LINE__); + + assert(AreWeMember()); + + logger.Batch("sending contributions"); + + if (ShouldSimulateError(DKGError::type::CONTRIBUTION_OMIT)) { + logger.Batch("omitting"); + return; + } + + CDKGContribution qc; + qc.llmqType = params.type; + qc.quorumHash = m_quorum_base_block_index->GetBlockHash(); + qc.proTxHash = myProTxHash; + qc.vvec = vvecContribution; + + cxxtimer::Timer t1(true); + qc.contributions = std::make_shared>(); + qc.contributions->InitEncrypt(members.size()); + + for (const auto i : irange::range(members.size())) { + const auto& m = members[i]; + CBLSSecretKey skContrib = m_sk_contributions[i]; + + if (i != myIdx && ShouldSimulateError(DKGError::type::CONTRIBUTION_LIE)) { + logger.Batch("lying for %s", m->dmn->proTxHash.ToString()); + skContrib.MakeNewKey(); + } + + if (!qc.contributions->Encrypt(i, m->dmn->pdmnState->pubKeyOperator.Get(), skContrib, PROTOCOL_VERSION)) { + logger.Batch("failed to encrypt contribution for %s", m->dmn->proTxHash.ToString()); + return; + } + } + + logger.Batch("encrypted contributions. time=%d", t1.count()); + + qc.sig = m_mn_activeman.Sign(qc.GetSignHash(), m_use_legacy_bls); + + logger.Flush(); + + dkgDebugManager.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + status.statusBits.sentContributions = true; + return true; + }); + + pendingMessages.PushPendingMessage(-1, qc, peerman); +} + +// Verifies all pending secret key contributions in one batch +// This is done by aggregating the verification vectors belonging to the secret key contributions +// The resulting aggregated vvec is then used to recover a public key share +// The public key share must match the public key belonging to the aggregated secret key contributions +// See CBLSWorker::VerifyContributionShares for more details. +void ActiveSession::VerifyPendingContributions() +{ + AssertLockHeld(cs_pending); + + CDKGLogger logger(*this, __func__, __LINE__); + + cxxtimer::Timer t1(true); + + if (pendingContributionVerifications.empty()) { + return; + } + + std::vector memberIndexes; + std::vector vvecs; + std::vector skContributions; + + for (const auto& idx : pendingContributionVerifications) { + const auto& m = members[idx]; + if (m->bad || m->weComplain) { + continue; + } + memberIndexes.emplace_back(idx); + vvecs.emplace_back(receivedVvecs[idx]); + skContributions.emplace_back(receivedSkContributions[idx]); + // Write here to definitely store one contribution for each member no matter if + // our share is valid or not, could be that others are still correct + dkgManager.WriteEncryptedContributions(params.type, m_quorum_base_block_index, m->dmn->proTxHash, *vecEncryptedContributions[idx]); + } + + auto result = blsWorker.VerifyContributionShares(myId, vvecs, skContributions); + if (result.size() != memberIndexes.size()) { + logger.Batch("VerifyContributionShares returned result of size %d but size %d was expected, something is wrong", result.size(), memberIndexes.size()); + return; + } + + for (const auto i : irange::range(memberIndexes.size())) { + if (!result[i]) { + const auto& m = members[memberIndexes[i]]; + logger.Batch("invalid contribution from %s. will complain later", m->dmn->proTxHash.ToString()); + m->weComplain = true; + dkgDebugManager.UpdateLocalMemberStatus(params.type, quorumIndex, m->idx, [&](CDKGDebugMemberStatus& status) { + status.statusBits.weComplain = true; + return true; + }); + } else { + size_t memberIdx = memberIndexes[i]; + dkgManager.WriteVerifiedSkContribution(params.type, m_quorum_base_block_index, members[memberIdx]->dmn->proTxHash, skContributions[i]); + } + } + + logger.Batch("verified %d pending contributions. time=%d", pendingContributionVerifications.size(), t1.count()); + pendingContributionVerifications.clear(); +} + +void ActiveSession::VerifyAndComplain(CConnman& connman, CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + if (!AreWeMember()) { + return; + } + + { + LOCK(cs_pending); + VerifyPendingContributions(); + } + + CDKGLogger logger(*this, __func__, __LINE__); + + // we check all members if they sent us their contributions + // we consider members as bad if they missed to send anything or if they sent multiple + // in both cases we won't give them a second chance as they might be either down, buggy or an adversary + // we assume that such a participant will be marked as bad by the whole network in most cases, + // as propagation will ensure that all nodes see the same vvecs/contributions. In case nodes come to + // different conclusions, the aggregation phase will handle this (most voted quorum key wins). + + cxxtimer::Timer t1(true); + + for (const auto& m : members) { + if (m->bad) { + continue; + } + if (m->contributions.empty()) { + logger.Batch("%s did not send any contribution", m->dmn->proTxHash.ToString()); + MarkBadMember(m->idx); + continue; + } + } + + logger.Batch("verified contributions. time=%d", t1.count()); + logger.Flush(); + + VerifyConnectionAndMinProtoVersions(connman); + + SendComplaint(pendingMessages, peerman); +} + +void ActiveSession::VerifyConnectionAndMinProtoVersions(CConnman& connman) const +{ + assert(m_mn_metaman.IsValid()); + + if (!IsQuorumPoseEnabled(params.type, m_sporkman)) { + return; + } + + CDKGLogger logger(*this, __func__, __LINE__); + + Uint256HashMap protoMap; + connman.ForEachNode([&](const CNode* pnode) { + auto verifiedProRegTxHash = pnode->GetVerifiedProRegTxHash(); + if (verifiedProRegTxHash.IsNull()) { + return; + } + protoMap.emplace(verifiedProRegTxHash, pnode->nVersion); + }); + + bool fShouldAllMembersBeConnected = IsAllMembersConnectedEnabled(params.type, m_sporkman); + for (const auto& m : members) { + if (m->dmn->proTxHash == myProTxHash) { + continue; + } + if (auto it = protoMap.find(m->dmn->proTxHash); it == protoMap.end()) { + m->badConnection = fShouldAllMembersBeConnected; + if (m->badConnection) { + logger.Batch("%s is not connected to us, badConnection=1", m->dmn->proTxHash.ToString()); + } + } else if (it->second < MIN_MASTERNODE_PROTO_VERSION) { + m->badConnection = true; + logger.Batch("%s does not have min proto version %d (has %d)", m->dmn->proTxHash.ToString(), MIN_MASTERNODE_PROTO_VERSION, it->second); + } + if (m_mn_metaman.OutboundFailedTooManyTimes(m->dmn->proTxHash)) { + m->badConnection = true; + logger.Batch("%s failed to connect to it too many times", m->dmn->proTxHash.ToString()); + } + if (m_mn_metaman.IsPlatformBanned(m->dmn->proTxHash)) { + m->badConnection = true; + logger.Batch("%s is Platform PoSe banned", m->dmn->proTxHash.ToString()); + } + } +} + +void ActiveSession::SendComplaint(CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + CDKGLogger logger(*this, __func__, __LINE__); + + assert(AreWeMember()); + + CDKGComplaint qc(params); + qc.llmqType = params.type; + qc.quorumHash = m_quorum_base_block_index->GetBlockHash(); + qc.proTxHash = myProTxHash; + + int badCount = 0; + int complaintCount = 0; + for (const auto i : irange::range(members.size())) { + const auto& m = members[i]; + if (m->bad || m->badConnection) { + qc.badMembers[i] = true; + badCount++; + } else if (m->weComplain) { + qc.complainForMembers[i] = true; + complaintCount++; + } + } + + if (badCount == 0 && complaintCount == 0) { + return; + } + + logger.Batch("sending complaint. badCount=%d, complaintCount=%d", badCount, complaintCount); + + qc.sig = m_mn_activeman.Sign(qc.GetSignHash(), m_use_legacy_bls); + + logger.Flush(); + + dkgDebugManager.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + status.statusBits.sentComplaint = true; + return true; + }); + + pendingMessages.PushPendingMessage(-1, qc, peerman); +} + +void ActiveSession::VerifyAndJustify(CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + if (!AreWeMember()) { + return; + } + + CDKGLogger logger(*this, __func__, __LINE__); + + std::set justifyFor; + + for (const auto& m : members) { + if (m->bad) { + continue; + } + if (m->badMemberVotes.size() >= size_t(params.dkgBadVotesThreshold)) { + logger.Batch("%s marked as bad as %d other members voted for this", m->dmn->proTxHash.ToString(), m->badMemberVotes.size()); + MarkBadMember(m->idx); + continue; + } + if (m->complaints.empty()) { + continue; + } + if (m->complaints.size() != 1) { + logger.Batch("%s sent multiple complaints", m->dmn->proTxHash.ToString()); + MarkBadMember(m->idx); + continue; + } + + LOCK(invCs); + if (const auto& qc = complaints.at(*m->complaints.begin()); + qc.complainForMembers[*myIdx]) { + justifyFor.emplace(qc.proTxHash); + } + } + + logger.Flush(); + if (!justifyFor.empty()) { + SendJustification(pendingMessages, peerman, justifyFor); + } +} + +void ActiveSession::SendJustification(CDKGPendingMessages& pendingMessages, PeerManager& peerman, + const std::set& forMembers) +{ + CDKGLogger logger(*this, __func__, __LINE__); + + assert(AreWeMember()); + + logger.Batch("sending justification for %d members", forMembers.size()); + + CDKGJustification qj; + qj.llmqType = params.type; + qj.quorumHash = m_quorum_base_block_index->GetBlockHash(); + qj.proTxHash = myProTxHash; + qj.contributions.reserve(forMembers.size()); + + for (const uint32_t i : irange::range(members.size())) { + const auto& m = members[i]; + if (forMembers.count(m->dmn->proTxHash) == 0) { + continue; + } + logger.Batch("justifying for %s", m->dmn->proTxHash.ToString()); + + CBLSSecretKey skContribution = m_sk_contributions[i]; + + if (i != myIdx && ShouldSimulateError(DKGError::type::JUSTIFY_LIE)) { + logger.Batch("lying for %s", m->dmn->proTxHash.ToString()); + skContribution.MakeNewKey(); + } + + qj.contributions.emplace_back(CDKGJustification::Contribution{i, skContribution}); + } + + if (ShouldSimulateError(DKGError::type::JUSTIFY_OMIT)) { + logger.Batch("omitting"); + return; + } + + qj.sig = m_mn_activeman.Sign(qj.GetSignHash(), m_use_legacy_bls); + + logger.Flush(); + + dkgDebugManager.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + status.statusBits.sentJustification = true; + return true; + }); + + pendingMessages.PushPendingMessage(-1, qj, peerman); +} + +void ActiveSession::VerifyAndCommit(CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + if (!AreWeMember()) { + return; + } + + CDKGLogger logger(*this, __func__, __LINE__); + + std::vector badMembers; + badMembers.reserve(members.size()); + std::vector openComplaintMembers; + openComplaintMembers.reserve(members.size()); + + for (const auto& m : members) { + if (m->bad) { + badMembers.emplace_back(m->idx); + continue; + } + if (!m->complaintsFromOthers.empty()) { + MarkBadMember(m->idx); + openComplaintMembers.emplace_back(m->idx); + } + } + + if (!badMembers.empty() || !openComplaintMembers.empty()) { + logger.Batch("verification result:"); + } + if (!badMembers.empty()) { + logger.Batch(" members previously determined as bad:"); + for (const auto& idx : badMembers) { + logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString()); + } + } + if (!openComplaintMembers.empty()) { + logger.Batch(" members with open complaints and now marked as bad:"); + for (const auto& idx : openComplaintMembers) { + logger.Batch(" %s", members[idx]->dmn->proTxHash.ToString()); + } + } + + logger.Flush(); + + SendCommitment(pendingMessages, peerman); +} + +void ActiveSession::SendCommitment(CDKGPendingMessages& pendingMessages, PeerManager& peerman) +{ + CDKGLogger logger(*this, __func__, __LINE__); + + assert(AreWeMember()); + + logger.Batch("sending commitment"); + + CDKGPrematureCommitment qc(params); + qc.llmqType = params.type; + qc.quorumHash = m_quorum_base_block_index->GetBlockHash(); + qc.proTxHash = myProTxHash; + + for (const auto i : irange::range(members.size())) { + const auto& m = members[i]; + if (!m->bad) { + qc.validMembers[i] = true; + } + } + + if (qc.CountValidMembers() < params.minSize) { + logger.Batch("not enough valid members. not sending commitment"); + return; + } + + if (ShouldSimulateError(DKGError::type::COMMIT_OMIT)) { + logger.Batch("omitting"); + return; + } + + cxxtimer::Timer timerTotal(true); + + cxxtimer::Timer t1(true); + std::vector memberIndexes; + std::vector vvecs; + std::vector skContributions; + if (!dkgManager.GetVerifiedContributions(params.type, m_quorum_base_block_index, qc.validMembers, memberIndexes, vvecs, skContributions)) { + logger.Batch("failed to get valid contributions"); + return; + } + + BLSVerificationVectorPtr vvec = cache.BuildQuorumVerificationVector(::SerializeHash(memberIndexes), vvecs); + if (vvec == nullptr) { + logger.Batch("failed to build quorum verification vector"); + return; + } + t1.stop(); + + cxxtimer::Timer t2(true); + CBLSSecretKey skShare = cache.AggregateSecretKeys(::SerializeHash(memberIndexes), skContributions); + if (!skShare.IsValid()) { + logger.Batch("failed to build own secret share"); + return; + } + t2.stop(); + + logger.Batch("pubKeyShare=%s", skShare.GetPublicKey().ToString()); + + cxxtimer::Timer t3(true); + qc.quorumPublicKey = (*vvec)[0]; + qc.quorumVvecHash = ::SerializeHash(*vvec); + + int lieType = -1; + if (ShouldSimulateError(DKGError::type::COMMIT_LIE)) { + lieType = GetRand(/*nMax=*/5); + logger.Batch("lying on commitment. lieType=%d", lieType); + } + + if (lieType == 0) { + CBLSSecretKey k; + k.MakeNewKey(); + qc.quorumPublicKey = k.GetPublicKey(); + } else if (lieType == 1) { + (*qc.quorumVvecHash.begin())++; + } + + uint256 commitmentHash = BuildCommitmentHash(qc.llmqType, qc.quorumHash, qc.validMembers, qc.quorumPublicKey, qc.quorumVvecHash); + + if (lieType == 2) { + (*commitmentHash.begin())++; + } + + qc.sig = m_mn_activeman.Sign(commitmentHash, m_use_legacy_bls); + qc.quorumSig = skShare.Sign(commitmentHash, m_use_legacy_bls); + + if (lieType == 3) { + auto buf = qc.sig.ToBytes(m_use_legacy_bls); + buf[5]++; + qc.sig.SetBytes(buf, m_use_legacy_bls); + } else if (lieType == 4) { + auto buf = qc.quorumSig.ToBytes(m_use_legacy_bls); + buf[5]++; + qc.quorumSig.SetBytes(buf, m_use_legacy_bls); + } + + t3.stop(); + timerTotal.stop(); + + logger.Batch("built premature commitment. time1=%d, time2=%d, time3=%d, totalTime=%d", + t1.count(), t2.count(), t3.count(), timerTotal.count()); + + + logger.Flush(); + + dkgDebugManager.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + status.statusBits.sentPrematureCommitment = true; + return true; + }); + + pendingMessages.PushPendingMessage(-1, qc, peerman); +} + +std::vector ActiveSession::FinalizeCommitments() +{ + if (!AreWeMember()) { + return {}; + } + + CDKGLogger logger(*this, __func__, __LINE__); + + using Key = std::vector; + std::map> commitmentsMap; + + { + LOCK(invCs); + + for (const auto& p : prematureCommitments) { + const auto& qc = p.second; + if (validCommitments.count(p.first) == 0) { + continue; + } + + // should have been verified before + assert(qc.CountValidMembers() >= params.minSize); + + auto it = commitmentsMap.find(qc.validMembers); + if (it == commitmentsMap.end()) { + it = commitmentsMap.emplace(qc.validMembers, std::vector()).first; + } + + it->second.emplace_back(qc); + } + } + + std::vector finalCommitments; + for (const auto& p : commitmentsMap) { + const auto& cvec = p.second; + if (cvec.size() < size_t(params.minSize)) { + // commitment was signed by a minority + continue; + } + + std::vector signerIds; + std::vector thresholdSigs; + + const auto& first = cvec[0]; + + CFinalCommitment fqc(params, first.quorumHash); + fqc.validMembers = first.validMembers; + fqc.quorumPublicKey = first.quorumPublicKey; + fqc.quorumVvecHash = first.quorumVvecHash; + + const bool isQuorumRotationEnabled{IsQuorumRotationEnabled(params, m_quorum_base_block_index)}; + // TODO: always put `true` here: so far as v19 is activated, we always write BASIC now + fqc.nVersion = CFinalCommitment::GetVersion(isQuorumRotationEnabled, DeploymentActiveAfter(m_quorum_base_block_index, Params().GetConsensus(), Consensus::DEPLOYMENT_V19)); + fqc.quorumIndex = isQuorumRotationEnabled ? quorumIndex : 0; + + uint256 commitmentHash = BuildCommitmentHash(fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey, fqc.quorumVvecHash); + + std::vector aggSigs; + std::vector aggPks; + aggSigs.reserve(cvec.size()); + aggPks.reserve(cvec.size()); + + for (const auto& qc : cvec) { + if (qc.quorumPublicKey != first.quorumPublicKey || qc.quorumVvecHash != first.quorumVvecHash) { + logger.Batch("quorumPublicKey or quorumVvecHash does not match, skipping"); + continue; + } + + size_t signerIndex = membersMap[qc.proTxHash]; + const auto& m = members[signerIndex]; + + fqc.signers[signerIndex] = true; + aggSigs.emplace_back(qc.sig); + aggPks.emplace_back(m->dmn->pdmnState->pubKeyOperator.Get()); + + signerIds.emplace_back(m->id); + thresholdSigs.emplace_back(qc.quorumSig); + } + + cxxtimer::Timer t1(true); + fqc.membersSig = CBLSSignature::AggregateSecure(aggSigs, aggPks, commitmentHash); + t1.stop(); + + cxxtimer::Timer t2(true); + if (!fqc.quorumSig.Recover(thresholdSigs, signerIds)) { + logger.Batch("failed to recover quorum sig"); + continue; + } + t2.stop(); + + cxxtimer::Timer t3(true); + if (!fqc.Verify(m_dmnman, m_qsnapman, m_quorum_base_block_index, true)) { + logger.Batch("failed to verify final commitment"); + continue; + } + t3.stop(); + + finalCommitments.emplace_back(fqc); + + logger.Batch("final commitment: validMembers=%d, signers=%d, quorumPublicKey=%s, time1=%d, time2=%d, time3=%d", + fqc.CountValidMembers(), fqc.CountSigners(), fqc.quorumPublicKey.ToString(), + t1.count(), t2.count(), t3.count()); + } + + logger.Flush(); + + return finalCommitments; +} + +CFinalCommitment ActiveSession::FinalizeSingleCommitment() +{ + if (!AreWeMember()) { + return {}; + } + + CDKGLogger logger(*this, __func__, __LINE__); + + std::vector signerIds; + std::vector thresholdSigs; + + CFinalCommitment fqc(params, m_quorum_base_block_index->GetBlockHash()); + + + fqc.signers = {true}; + fqc.validMembers = {true}; + + CBLSSecretKey sk1; + sk1.MakeNewKey(); + + fqc.quorumPublicKey = sk1.GetPublicKey(); + fqc.quorumVvecHash = {}; + + // use just MN's operator public key as quorum pubkey. + // TODO: use sk1 here instead and use recovery mechanism from shares, but that's not trivial to do + const bool workaround_qpublic_key = true; + if (workaround_qpublic_key) { + fqc.quorumPublicKey = m_mn_activeman.GetPubKey(); + } + const bool isQuorumRotationEnabled{false}; + fqc.nVersion = CFinalCommitment::GetVersion(isQuorumRotationEnabled, + DeploymentActiveAfter(m_quorum_base_block_index, Params().GetConsensus(), + Consensus::DEPLOYMENT_V19)); + fqc.quorumIndex = 0; + + uint256 commitmentHash = BuildCommitmentHash(fqc.llmqType, fqc.quorumHash, fqc.validMembers, fqc.quorumPublicKey, + fqc.quorumVvecHash); + fqc.quorumSig = sk1.Sign(commitmentHash, m_use_legacy_bls); + + fqc.membersSig = m_mn_activeman.Sign(commitmentHash, m_use_legacy_bls); + + if (workaround_qpublic_key) { + fqc.quorumSig = fqc.membersSig; + } + + if (!fqc.Verify(m_dmnman, m_qsnapman, m_quorum_base_block_index, true)) { + logger.Batch("failed to verify final commitment"); + assert(false); + } + + logger.Batch("final commitment: validMembers=%d, signers=%d, quorumPublicKey=%s", fqc.CountValidMembers(), + fqc.CountSigners(), fqc.quorumPublicKey.ToString()); + + logger.Flush(); + + return fqc; +} + +bool ActiveSession::MaybeDecrypt(const CBLSIESMultiRecipientObjects& obj, size_t idx, + CBLSSecretKey& ret_obj, int version) +{ + return m_mn_activeman.Decrypt(obj, idx, ret_obj, version); +} +} // namespace dkg +} // namespace llmq diff --git a/src/active/dkgsession.h b/src/active/dkgsession.h new file mode 100644 index 000000000000..81ba91143c7b --- /dev/null +++ b/src/active/dkgsession.h @@ -0,0 +1,66 @@ +// 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_DKGSESSION_H +#define BITCOIN_ACTIVE_DKGSESSION_H + +#include + +namespace llmq { +namespace dkg { +class ActiveSession final : public llmq::CDKGSession +{ +private: + CMasternodeMetaMan& m_mn_metaman; + const CActiveMasternodeManager& m_mn_activeman; + const CSporkManager& m_sporkman; + const bool m_use_legacy_bls{false}; + +public: + ActiveSession() = delete; + ActiveSession(const ActiveSession&) = delete; + ActiveSession& operator=(const ActiveSession&) = delete; + ActiveSession(CBLSWorker& bls_worker, CDeterministicMNManager& dmnman, CDKGDebugManager& dkgdbgman, + CDKGSessionManager& qdkgsman, CMasternodeMetaMan& mn_metaman, CQuorumSnapshotManager& qsnapman, + const CActiveMasternodeManager& mn_activeman, const CSporkManager& sporkman, + const CBlockIndex* base_block_index, const Consensus::LLMQParams& params); + ~ActiveSession(); + +public: + // Phase 1: contribution + void Contribute(CDKGPendingMessages& pendingMessages, PeerManager& peerman) override; + void SendContributions(CDKGPendingMessages& pendingMessages, PeerManager& peerman) override; + void VerifyPendingContributions() override EXCLUSIVE_LOCKS_REQUIRED(cs_pending); + + // Phase 2: complaint + void VerifyAndComplain(CConnman& connman, CDKGPendingMessages& pendingMessages, PeerManager& peerman) override + EXCLUSIVE_LOCKS_REQUIRED(!cs_pending); + void VerifyConnectionAndMinProtoVersions(CConnman& connman) const override; + void SendComplaint(CDKGPendingMessages& pendingMessages, PeerManager& peerman) override; + + // Phase 3: justification + void VerifyAndJustify(CDKGPendingMessages& pendingMessages, PeerManager& peerman) override + EXCLUSIVE_LOCKS_REQUIRED(!invCs); + void SendJustification(CDKGPendingMessages& pendingMessages, PeerManager& peerman, + const std::set& forMembers) override; + + // Phase 4: commit + void VerifyAndCommit(CDKGPendingMessages& pendingMessages, PeerManager& peerman) override; + void SendCommitment(CDKGPendingMessages& pendingMessages, PeerManager& peerman) override; + + // Phase 5: aggregate/finalize + std::vector FinalizeCommitments() EXCLUSIVE_LOCKS_REQUIRED(!invCs) override; + + // All Phases 5-in-1 for single-node-quorum + CFinalCommitment FinalizeSingleCommitment() override; + +private: + //! CDKGSession + bool MaybeDecrypt(const CBLSIESMultiRecipientObjects& obj, size_t idx, CBLSSecretKey& ret_obj, + int version) override; +}; +} // namespace dkg +} // namespace llmq + +#endif // BITCOIN_ACTIVE_DKGSESSION_H diff --git a/src/active/dkgsessionhandler.cpp b/src/active/dkgsessionhandler.cpp new file mode 100644 index 000000000000..81803a0cb647 --- /dev/null +++ b/src/active/dkgsessionhandler.cpp @@ -0,0 +1,538 @@ +// 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 + +namespace llmq { +namespace dkg { +ActiveSessionHandler::ActiveSessionHandler(CBLSWorker& bls_worker, CChainState& chainstate, + CDeterministicMNManager& dmnman, CMasternodeMetaMan& mn_metaman, + llmq::CDKGDebugManager& dkgdbgman, llmq::CDKGSessionManager& qdkgsman, + llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, + const CActiveMasternodeManager& mn_activeman, const CSporkManager& sporkman, + const Consensus::LLMQParams& llmq_params, bool quorums_watch, int quorums_idx) : + llmq::CDKGSessionHandler(bls_worker, dmnman, dkgdbgman, qdkgsman, qsnapman, llmq_params, quorums_watch, quorums_idx), + m_bls_worker{bls_worker}, + m_chainstate{chainstate}, + m_dmnman{dmnman}, + m_mn_metaman{mn_metaman}, + m_dkgdbgman{dkgdbgman}, + m_qdkgsman{qdkgsman}, + m_qblockman{qblockman}, + m_qsnapman{qsnapman}, + m_mn_activeman{mn_activeman}, + m_sporkman{sporkman}, + m_quorums_watch{quorums_watch} +{ + // Overwrite session initialized in parent + curSession.reset(); + curSession = std::make_unique(m_bls_worker, m_dmnman, m_dkgdbgman, m_qdkgsman, m_mn_metaman, + m_qsnapman, m_mn_activeman, m_sporkman, + /*pQuorumBaseBlockIndex=*/nullptr, llmq_params); +} + +ActiveSessionHandler::~ActiveSessionHandler() = default; + +void ActiveSessionHandler::UpdatedBlockTip(const CBlockIndex* pindexNew) +{ + //AssertLockNotHeld(cs_main); + //Indexed quorums (greater than 0) are enabled with Quorum Rotation + if (quorumIndex > 0 && !IsQuorumRotationEnabled(params, pindexNew)) { + return; + } + LOCK(cs_phase_qhash); + + int quorumStageInt = (pindexNew->nHeight - quorumIndex) % params.dkgInterval; + + const CBlockIndex* pQuorumBaseBlockIndex = pindexNew->GetAncestor(pindexNew->nHeight - quorumStageInt); + + currentHeight = pindexNew->nHeight; + quorumHash = pQuorumBaseBlockIndex->GetBlockHash(); + + bool fNewPhase = (quorumStageInt % params.dkgPhaseBlocks) == 0; + int phaseInt = quorumStageInt / params.dkgPhaseBlocks + 1; + QuorumPhase oldPhase = phase; + if (fNewPhase && phaseInt >= ToUnderlying(QuorumPhase::Initialized) && phaseInt <= ToUnderlying(QuorumPhase::Idle)) { + phase = static_cast(phaseInt); + } + + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] currentHeight=%d, pQuorumBaseBlockIndex->nHeight=%d, oldPhase=%d, newPhase=%d\n", __func__, + params.name, quorumIndex, currentHeight, pQuorumBaseBlockIndex->nHeight, ToUnderlying(oldPhase), ToUnderlying(phase)); +} + +void ActiveSessionHandler::StartThread(CConnman& connman, PeerManager& peerman) +{ + if (phaseHandlerThread.joinable()) { + throw std::runtime_error("Tried to start an already started ActiveSessionHandler thread."); + } + + m_thread_name = strprintf("llmq-%d-%d", ToUnderlying(params.type), quorumIndex); + phaseHandlerThread = std::thread(&util::TraceThread, m_thread_name.c_str(), + [this, &connman, &peerman] { PhaseHandlerThread(connman, peerman); }); +} + +void ActiveSessionHandler::StopThread() +{ + stopRequested = true; + if (phaseHandlerThread.joinable()) { + phaseHandlerThread.join(); + } +} + +std::pair ActiveSessionHandler::GetPhaseAndQuorumHash() const +{ + LOCK(cs_phase_qhash); + return std::make_pair(phase, quorumHash); +} + +bool ActiveSessionHandler::InitNewQuorum(const CBlockIndex* pQuorumBaseBlockIndex) +{ + if (!DeploymentDIP0003Enforced(pQuorumBaseBlockIndex->nHeight, Params().GetConsensus())) { + return false; + } + + curSession = std::make_unique(m_bls_worker, m_dmnman, m_dkgdbgman, m_qdkgsman, m_mn_metaman, + m_qsnapman, m_mn_activeman, m_sporkman, pQuorumBaseBlockIndex, params); + + if (!curSession->Init(m_mn_activeman.GetProTxHash(), quorumIndex)) { + LogPrintf("ActiveSessionHandler::%s -- height[%d] quorum initialization failed for %s qi[%d]\n", __func__, + pQuorumBaseBlockIndex->nHeight, curSession->params.name, quorumIndex); + return false; + } + + LogPrintf("ActiveSessionHandler::%s -- height[%d] quorum initialization OK for %s qi[%d]\n", __func__, pQuorumBaseBlockIndex->nHeight, curSession->params.name, quorumIndex); + return true; +} + +class AbortPhaseException : public std::exception { +}; + +void ActiveSessionHandler::WaitForNextPhase(std::optional curPhase, + QuorumPhase nextPhase, + const uint256& expectedQuorumHash, + const WhileWaitFunc& shouldNotWait) const +{ + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, curPhase.has_value() ? ToUnderlying(*curPhase) : -1, ToUnderlying(nextPhase)); + + while (true) { + if (stopRequested) { + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex); + throw AbortPhaseException(); + } + auto [_phase, _quorumHash] = GetPhaseAndQuorumHash(); + if (!expectedQuorumHash.IsNull() && _quorumHash != expectedQuorumHash) { + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborting due unexpected expectedQuorumHash change\n", __func__, params.name, quorumIndex); + throw AbortPhaseException(); + } + if (_phase == nextPhase) { + break; + } + if (curPhase.has_value() && _phase != curPhase) { + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborting due unexpected phase change, _phase=%d, curPhase=%d\n", __func__, params.name, quorumIndex, ToUnderlying(_phase), curPhase.has_value() ? ToUnderlying(*curPhase) : -1); + throw AbortPhaseException(); + } + if (!shouldNotWait()) { + UninterruptibleSleep(std::chrono::milliseconds{100}); + } + } + + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - done, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, curPhase.has_value() ? ToUnderlying(*curPhase) : -1, ToUnderlying(nextPhase)); + + if (nextPhase == QuorumPhase::Initialized) { + m_dkgdbgman.ResetLocalSessionStatus(params.type, quorumIndex); + } else { + m_dkgdbgman.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + bool changed = status.phase != nextPhase; + status.phase = nextPhase; + return changed; + }); + } +} + +void ActiveSessionHandler::WaitForNewQuorum(const uint256& oldQuorumHash) const +{ + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d]- starting\n", __func__, params.name, quorumIndex); + + while (true) { + if (stopRequested) { + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex); + throw AbortPhaseException(); + } + auto [_, _quorumHash] = GetPhaseAndQuorumHash(); + if (_quorumHash != oldQuorumHash) { + break; + } + UninterruptibleSleep(std::chrono::milliseconds{100}); + } + + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - done\n", __func__, params.name, quorumIndex); +} + +// Sleep some time to not fully overload the whole network +void ActiveSessionHandler::SleepBeforePhase(QuorumPhase curPhase, + const uint256& expectedQuorumHash, + double randomSleepFactor, + const WhileWaitFunc& runWhileWaiting) const +{ + if (!curSession->AreWeMember()) { + // Non-members do not participate and do not create any network load, no need to sleep. + return; + } + + if (Params().MineBlocksOnDemand()) { + // On regtest, blocks can be mined on demand without any significant time passing between these. + // We shouldn't wait before phases in this case. + return; + } + + // Two blocks can come very close to each other, this happens pretty regularly. We don't want to be + // left behind and marked as a bad member. This means that we should not count the last block of the + // phase as a safe one to keep sleeping, that's why we calculate the phase sleep time as a time of + // the full phase minus one block here. + double phaseSleepTime = (params.dkgPhaseBlocks - 1) * Params().GetConsensus().nPowTargetSpacing * 1000; + // Expected phase sleep time per member + double phaseSleepTimePerMember = phaseSleepTime / params.size; + // Don't expect perfect block times and thus reduce the phase time to be on the secure side (caller chooses factor) + double adjustedPhaseSleepTimePerMember = phaseSleepTimePerMember * randomSleepFactor; + + int64_t sleepTime = (int64_t)(adjustedPhaseSleepTimePerMember * curSession->GetMyMemberIndex().value_or(0)); + int64_t endTime = TicksSinceEpoch(SystemClock::now()) + sleepTime; + int heightTmp{currentHeight.load()}; + int heightStart{heightTmp}; + + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - starting sleep for %d ms, curPhase=%d\n", __func__, params.name, quorumIndex, sleepTime, ToUnderlying(curPhase)); + + while (TicksSinceEpoch(SystemClock::now()) < endTime) { + if (stopRequested) { + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborting due to stop/shutdown requested\n", __func__, params.name, quorumIndex); + throw AbortPhaseException(); + } + auto cur_height = currentHeight.load(); + if (cur_height > heightTmp) { + // New block(s) just came in + int64_t expectedBlockTime = (cur_height - heightStart) * Params().GetConsensus().nPowTargetSpacing * 1000; + if (expectedBlockTime > sleepTime) { + // Blocks came faster than we expected, jump into the phase func asap + break; + } + heightTmp = cur_height; + } + if (WITH_LOCK(cs_phase_qhash, return phase != curPhase || quorumHash != expectedQuorumHash)) { + // Something went wrong and/or we missed quite a few blocks and it's just too late now + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborting due unexpected phase/expectedQuorumHash change\n", __func__, params.name, quorumIndex); + throw AbortPhaseException(); + } + if (!runWhileWaiting()) { + UninterruptibleSleep(std::chrono::milliseconds{100}); + } + } + + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - done, curPhase=%d\n", __func__, params.name, quorumIndex, ToUnderlying(curPhase)); +} + +void ActiveSessionHandler::HandlePhase(QuorumPhase curPhase, + QuorumPhase nextPhase, + const uint256& expectedQuorumHash, + double randomSleepFactor, + const StartPhaseFunc& startPhaseFunc, + const WhileWaitFunc& runWhileWaiting) +{ + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - starting, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, ToUnderlying(curPhase), ToUnderlying(nextPhase)); + + SleepBeforePhase(curPhase, expectedQuorumHash, randomSleepFactor, runWhileWaiting); + startPhaseFunc(); + WaitForNextPhase(curPhase, nextPhase, expectedQuorumHash, runWhileWaiting); + + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - done, curPhase=%d, nextPhase=%d\n", __func__, params.name, quorumIndex, ToUnderlying(curPhase), ToUnderlying(nextPhase)); +} + +// returns a set of NodeIds which sent invalid messages +template +std::set BatchVerifyMessageSigs(CDKGSession& session, const std::vector>>& messages) +{ + if (messages.empty()) { + return {}; + } + + std::set ret; + bool revertToSingleVerification = false; + + CBLSSignature aggSig; + std::vector pubKeys; + std::vector messageHashes; + std::set messageHashesSet; + pubKeys.reserve(messages.size()); + messageHashes.reserve(messages.size()); + bool first = true; + for (const auto& [nodeId, msg] : messages) { + auto member = session.GetMember(msg->proTxHash); + if (!member) { + // should not happen as it was verified before + ret.emplace(nodeId); + continue; + } + + if (first) { + aggSig = msg->sig; + } else { + aggSig.AggregateInsecure(msg->sig); + } + first = false; + + auto msgHash = msg->GetSignHash(); + if (!messageHashesSet.emplace(msgHash).second) { + // can only happen in 2 cases: + // 1. Someone sent us the same message twice but with differing signature, meaning that at least one of them + // must be invalid. In this case, we'd have to revert to single message verification nevertheless + // 2. Someone managed to find a way to create two different binary representations of a message that deserializes + // to the same object representation. This would be some form of malleability. However, this shouldn't be + // possible as only deterministic/unique BLS signatures and very simple data types are involved + revertToSingleVerification = true; + break; + } + + pubKeys.emplace_back(member->dmn->pdmnState->pubKeyOperator.Get()); + messageHashes.emplace_back(msgHash); + } + if (!revertToSingleVerification) { + if (aggSig.VerifyInsecureAggregated(pubKeys, messageHashes)) { + // all good + return ret; + } + + // are all messages from the same node? + bool nodeIdsAllSame = std::adjacent_find( messages.begin(), messages.end(), [](const auto& first, const auto& second){ + return first.first != second.first; + }) == messages.end(); + + // if yes, take a short path and return a set with only him + if (nodeIdsAllSame) { + ret.emplace(messages[0].first); + return ret; + } + // different nodes, let's figure out who are the bad ones + } + + for (const auto& [nodeId, msg] : messages) { + if (ret.count(nodeId)) { + continue; + } + + auto member = session.GetMember(msg->proTxHash); + bool valid = msg->sig.VerifyInsecure(member->dmn->pdmnState->pubKeyOperator.Get(), msg->GetSignHash()); + if (!valid) { + ret.emplace(nodeId); + } + } + return ret; +} + +static void RelayInvToParticipants(const CDKGSession& session, const CConnman& connman, PeerManager& peerman, + const CInv& inv) +{ + CDKGLogger logger(session, __func__, __LINE__); + std::stringstream ss; + const auto& relayMembers = session.RelayMembers(); + for (const auto& r : relayMembers) { + ss << r.ToString().substr(0, 4) << " | "; + } + logger.Batch("RelayInvToParticipants inv[%s] relayMembers[%d] GetNodeCount[%d] GetNetworkActive[%d] " + "HasMasternodeQuorumNodes[%d] for quorumHash[%s] forMember[%s] relayMembers[%s]", + inv.ToString(), relayMembers.size(), connman.GetNodeCount(ConnectionDirection::Both), + connman.GetNetworkActive(), + connman.HasMasternodeQuorumNodes(session.GetParams().type, session.BlockIndex()->GetBlockHash()), + session.BlockIndex()->GetBlockHash().ToString(), session.ProTx().ToString().substr(0, 4), ss.str()); + + std::stringstream ss2; + connman.ForEachNode([&](const CNode* pnode) { + if (pnode->qwatch || + (!pnode->GetVerifiedProRegTxHash().IsNull() && (relayMembers.count(pnode->GetVerifiedProRegTxHash()) != 0))) { + peerman.PushInventory(pnode->GetId(), inv); + } + + if (pnode->GetVerifiedProRegTxHash().IsNull()) { + logger.Batch("node[%d:%s] not mn", pnode->GetId(), pnode->m_addr_name); + } else if (relayMembers.count(pnode->GetVerifiedProRegTxHash()) == 0) { + ss2 << pnode->GetVerifiedProRegTxHash().ToString().substr(0, 4) << " | "; + } + }); + logger.Batch("forMember[%s] NOTrelayMembers[%s]", session.ProTx().ToString().substr(0, 4), ss2.str()); + logger.Flush(); +} + +template +bool ProcessPendingMessageBatch(const CConnman& connman, CDKGSession& session, CDKGPendingMessages& pendingMessages, + PeerManager& peerman, size_t maxCount) +{ + auto msgs = pendingMessages.PopAndDeserializeMessages(maxCount); + if (msgs.empty()) { + return false; + } + + std::vector>> preverifiedMessages; + preverifiedMessages.reserve(msgs.size()); + + for (const auto& p : msgs) { + const NodeId &nodeId = p.first; + if (!p.second) { + LogPrint(BCLog::LLMQ_DKG, "%s -- failed to deserialize message, peer=%d\n", __func__, nodeId); + { + pendingMessages.Misbehaving(nodeId, 100, peerman); + } + continue; + } + bool ban = false; + if (!session.PreVerifyMessage(*p.second, ban)) { + if (ban) { + LogPrint(BCLog::LLMQ_DKG, "%s -- banning node due to failed preverification, peer=%d\n", __func__, nodeId); + { + pendingMessages.Misbehaving(nodeId, 100, peerman); + } + } + LogPrint(BCLog::LLMQ_DKG, "%s -- skipping message due to failed preverification, peer=%d\n", __func__, nodeId); + continue; + } + preverifiedMessages.emplace_back(p); + } + if (preverifiedMessages.empty()) { + return true; + } + + auto badNodes = BatchVerifyMessageSigs(session, preverifiedMessages); + if (!badNodes.empty()) { + for (auto nodeId : badNodes) { + LogPrint(BCLog::LLMQ_DKG, "%s -- failed to verify signature, peer=%d\n", __func__, nodeId); + pendingMessages.Misbehaving(nodeId, 100, peerman); + } + } + + for (const auto& p : preverifiedMessages) { + const NodeId &nodeId = p.first; + if (badNodes.count(nodeId)) { + continue; + } + const std::optional inv = session.ReceiveMessage(*p.second); + if (inv) { + RelayInvToParticipants(session, connman, peerman, *inv); + } + } + + return true; +} + +void ActiveSessionHandler::HandleDKGRound(CConnman& connman, PeerManager& peerman) +{ + WaitForNextPhase(std::nullopt, QuorumPhase::Initialized); + + pendingContributions.Clear(); + pendingComplaints.Clear(); + pendingJustifications.Clear(); + pendingPrematureCommitments.Clear(); + uint256 curQuorumHash = WITH_LOCK(cs_phase_qhash, return quorumHash); + + const CBlockIndex* pQuorumBaseBlockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(curQuorumHash)); + + if (!pQuorumBaseBlockIndex || !InitNewQuorum(pQuorumBaseBlockIndex)) { + // should actually never happen + WaitForNewQuorum(curQuorumHash); + throw AbortPhaseException(); + } + + m_dkgdbgman.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + bool changed = status.phase != QuorumPhase::Initialized; + status.phase = QuorumPhase::Initialized; + return changed; + }); + + if (params.is_single_member()) { + auto finalCommitment = curSession->FinalizeSingleCommitment(); + if (!finalCommitment.IsNull()) { // it can be null only if we are not member + if (auto inv_opt = m_qblockman.AddMineableCommitment(finalCommitment); inv_opt.has_value()) { + peerman.RelayInv(inv_opt.value()); + } + } + WaitForNextPhase(QuorumPhase::Initialized, QuorumPhase::Contribute, curQuorumHash); + return; + } + + const auto tip_mn_list = m_dmnman.GetListAtChainTip(); + utils::EnsureQuorumConnections(params, connman, m_dmnman, m_sporkman, m_qsnapman, tip_mn_list, pQuorumBaseBlockIndex, + curSession->myProTxHash, /*is_masternode=*/true, m_quorums_watch); + if (curSession->AreWeMember()) { + utils::AddQuorumProbeConnections(params, connman, m_dmnman, m_mn_metaman, m_qsnapman, m_sporkman, tip_mn_list, + pQuorumBaseBlockIndex, curSession->myProTxHash); + } + + WaitForNextPhase(QuorumPhase::Initialized, QuorumPhase::Contribute, curQuorumHash); + + // Contribute + auto fContributeStart = [this, &peerman]() { curSession->Contribute(pendingContributions, peerman); }; + auto fContributeWait = [this, &connman, &peerman] { + return ProcessPendingMessageBatch(connman, *curSession, pendingContributions, peerman, 8); + }; + HandlePhase(QuorumPhase::Contribute, QuorumPhase::Complain, curQuorumHash, 0.05, fContributeStart, fContributeWait); + + // Complain + auto fComplainStart = [this, &connman, &peerman]() { + curSession->VerifyAndComplain(connman, pendingComplaints, peerman); + }; + auto fComplainWait = [this, &connman, &peerman] { + return ProcessPendingMessageBatch(connman, *curSession, pendingComplaints, peerman, 8); + }; + HandlePhase(QuorumPhase::Complain, QuorumPhase::Justify, curQuorumHash, 0.05, fComplainStart, fComplainWait); + + // Justify + auto fJustifyStart = [this, &peerman]() { curSession->VerifyAndJustify(pendingJustifications, peerman); }; + auto fJustifyWait = [this, &connman, &peerman] { + return ProcessPendingMessageBatch(connman, *curSession, pendingJustifications, peerman, 8); + }; + HandlePhase(QuorumPhase::Justify, QuorumPhase::Commit, curQuorumHash, 0.05, fJustifyStart, fJustifyWait); + + // Commit + auto fCommitStart = [this, &peerman]() { curSession->VerifyAndCommit(pendingPrematureCommitments, peerman); }; + auto fCommitWait = [this, &connman, &peerman] { + return ProcessPendingMessageBatch(connman, *curSession, pendingPrematureCommitments, + peerman, 8); + }; + HandlePhase(QuorumPhase::Commit, QuorumPhase::Finalize, curQuorumHash, 0.1, fCommitStart, fCommitWait); + + auto finalCommitments = curSession->FinalizeCommitments(); + for (const auto& fqc : finalCommitments) { + if (auto inv_opt = m_qblockman.AddMineableCommitment(fqc); inv_opt.has_value()) { + peerman.RelayInv(inv_opt.value()); + } + } +} + +void ActiveSessionHandler::PhaseHandlerThread(CConnman& connman, PeerManager& peerman) +{ + while (!stopRequested) { + try { + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - starting HandleDKGRound\n", __func__, params.name, quorumIndex); + HandleDKGRound(connman, peerman); + } catch (AbortPhaseException& e) { + m_dkgdbgman.UpdateLocalSessionStatus(params.type, quorumIndex, [&](CDKGDebugSessionStatus& status) { + status.statusBits.aborted = true; + return true; + }); + LogPrint(BCLog::LLMQ_DKG, "ActiveSessionHandler::%s -- %s qi[%d] - aborted current DKG session\n", __func__, params.name, quorumIndex); + } + } +} +} // namespace dkg +} // namespace llmq diff --git a/src/active/dkgsessionhandler.h b/src/active/dkgsessionhandler.h new file mode 100644 index 000000000000..bf10efd4ac47 --- /dev/null +++ b/src/active/dkgsessionhandler.h @@ -0,0 +1,85 @@ +// 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_DKGSESSIONHANDLER_H +#define BITCOIN_ACTIVE_DKGSESSIONHANDLER_H + +#include + +namespace llmq { +namespace dkg { +class ActiveSessionHandler final : public llmq::CDKGSessionHandler +{ + using StartPhaseFunc = std::function; + using WhileWaitFunc = std::function; + +private: + CBLSWorker& m_bls_worker; + CChainState& m_chainstate; + CDeterministicMNManager& m_dmnman; + CMasternodeMetaMan& m_mn_metaman; + llmq::CDKGDebugManager& m_dkgdbgman; + llmq::CDKGSessionManager& m_qdkgsman; + llmq::CQuorumBlockProcessor& m_qblockman; + llmq::CQuorumSnapshotManager& m_qsnapman; + const CActiveMasternodeManager& m_mn_activeman; + const CSporkManager& m_sporkman; + const bool m_quorums_watch{false}; + +private: + std::atomic stopRequested{false}; + std::atomic currentHeight {-1}; + std::string m_thread_name; + std::thread phaseHandlerThread; + + mutable Mutex cs_phase_qhash; + QuorumPhase phase GUARDED_BY(cs_phase_qhash) {QuorumPhase::Idle}; + uint256 quorumHash GUARDED_BY(cs_phase_qhash); + +public: + ActiveSessionHandler() = delete; + ActiveSessionHandler(const ActiveSessionHandler&) = delete; + ActiveSessionHandler& operator=(const ActiveSessionHandler&) = delete; + ActiveSessionHandler(CBLSWorker& bls_worker, CChainState& chainstate, CDeterministicMNManager& dmnman, + CMasternodeMetaMan& mn_metaman, llmq::CDKGDebugManager& dkgdbgman, + llmq::CDKGSessionManager& qdkgsman, llmq::CQuorumBlockProcessor& qblockman, + llmq::CQuorumSnapshotManager& qsnapman, const CActiveMasternodeManager& mn_activeman, + const CSporkManager& sporkman, const Consensus::LLMQParams& llmq_params, bool quorums_watch, + int quorums_idx); + ~ActiveSessionHandler(); + +public: + //! CDKGSessionHandler + QuorumPhase GetPhase() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash) { return WITH_LOCK(cs_phase_qhash, return phase); } + void StartThread(CConnman& connman, PeerManager& peerman) override; + void StopThread() override; + void UpdatedBlockTip(const CBlockIndex* pindexNew) override EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + +private: + std::pair GetPhaseAndQuorumHash() const EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + + bool InitNewQuorum(const CBlockIndex* pQuorumBaseBlockIndex); + + /** + * @param curPhase current QuorumPhase + * @param nextPhase next QuorumPhase + * @param expectedQuorumHash expected QuorumHash, defaults to null + * @param shouldNotWait function that returns bool, defaults to function that returns false. If the function returns false, we will wait in the loop, if true, we don't wait + */ + void WaitForNextPhase( + std::optional curPhase, QuorumPhase nextPhase, const uint256& expectedQuorumHash = uint256(), + const WhileWaitFunc& shouldNotWait = [] { return false; }) const EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + void WaitForNewQuorum(const uint256& oldQuorumHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + void SleepBeforePhase(QuorumPhase curPhase, const uint256& expectedQuorumHash, double randomSleepFactor, + const WhileWaitFunc& runWhileWaiting) const EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + void HandlePhase(QuorumPhase curPhase, QuorumPhase nextPhase, const uint256& expectedQuorumHash, + double randomSleepFactor, const StartPhaseFunc& startPhaseFunc, + const WhileWaitFunc& runWhileWaiting) EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + void HandleDKGRound(CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); + void PhaseHandlerThread(CConnman& connman, PeerManager& peerman) EXCLUSIVE_LOCKS_REQUIRED(!cs_phase_qhash); +}; +} // namespace dkg +} // namespace llmq + +#endif // BITCOIN_ACTIVE_DKGSESSIONHANDLER_H diff --git a/src/masternode/node.cpp b/src/active/masternode.cpp similarity index 87% rename from src/masternode/node.cpp rename to src/active/masternode.cpp index 7c48ce7f4fba..883bc26df187 100644 --- a/src/masternode/node.cpp +++ b/src/active/masternode.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include +#include #include #include @@ -49,16 +49,17 @@ bool GetLocal(CService& addr, const CNetAddr* paddrPeer) } } // anonymous namespace -CActiveMasternodeManager::CActiveMasternodeManager(const CBLSSecretKey& sk, CConnman& connman, - const std::unique_ptr& dmnman) : - m_info{sk, sk.GetPublicKey()}, +CActiveMasternodeManager::CActiveMasternodeManager(CConnman& connman, CDeterministicMNManager& dmnman, + const CBLSSecretKey& sk) : m_connman{connman}, - m_dmnman{dmnman} + m_dmnman{dmnman}, + m_operator_pk{sk.GetPublicKey()}, + m_operator_sk{sk} { assert(sk.IsValid()); /* We can assume pk is valid if sk is valid */ LogPrintf("MASTERNODE:\n blsPubKeyOperator legacy: %s\n blsPubKeyOperator basic: %s\n", - m_info.blsPubKeyOperator.ToString(/*specificLegacyScheme=*/ true), - m_info.blsPubKeyOperator.ToString(/*specificLegacyScheme=*/ false)); + m_operator_pk.ToString(/*specificLegacyScheme=*/true), + m_operator_pk.ToString(/*specificLegacyScheme=*/false)); } CActiveMasternodeManager::~CActiveMasternodeManager() = default; @@ -123,14 +124,14 @@ void CActiveMasternodeManager::InitInternal(const CBlockIndex* pindex) return; } - if (!GetLocalAddress(m_info.service)) { + if (!GetLocalAddress(m_service)) { m_state = MasternodeState::SOME_ERROR; return; } - CDeterministicMNList mnList = Assert(m_dmnman)->GetListForBlock(pindex); + CDeterministicMNList mnList = m_dmnman.GetListForBlock(pindex); - auto dmn = mnList.GetMNByOperatorKey(m_info.blsPubKeyOperator); + auto dmn = mnList.GetMNByOperatorKey(m_operator_pk); if (!dmn) { // MN not appeared on the chain yet return; @@ -147,7 +148,7 @@ void CActiveMasternodeManager::InitInternal(const CBlockIndex* pindex) LogPrintf("CActiveMasternodeManager::Init -- proTxHash=%s, proTx=%s\n", dmn->proTxHash.ToString(), dmn->ToString()); - if (m_info.service != dmn->pdmnState->netInfo->GetPrimary()) { + if (m_service != dmn->pdmnState->netInfo->GetPrimary()) { m_state = MasternodeState::SOME_ERROR; m_error = "Local address does not match the address from ProTx"; LogPrintf("CActiveMasternodeManager::Init -- ERROR: %s\n", m_error); @@ -155,19 +156,19 @@ void CActiveMasternodeManager::InitInternal(const CBlockIndex* pindex) } // Check socket connectivity - LogPrintf("CActiveMasternodeManager::Init -- Checking inbound connection to '%s'\n", m_info.service.ToStringAddrPort()); - std::unique_ptr sock{ConnectDirectly(m_info.service, /*manual_connection=*/true)}; + LogPrintf("CActiveMasternodeManager::Init -- Checking inbound connection to '%s'\n", m_service.ToStringAddrPort()); + std::unique_ptr sock{ConnectDirectly(m_service, /*manual_connection=*/true)}; bool fConnected{sock && sock->IsSelectable(/*is_select=*/::g_socket_events_mode == SocketEventsMode::Select)}; sock = std::make_unique(INVALID_SOCKET); if (!fConnected && Params().RequireRoutableExternalIP()) { m_state = MasternodeState::SOME_ERROR; - m_error = "Could not connect to " + m_info.service.ToStringAddrPort(); + m_error = "Could not connect to " + m_service.ToStringAddrPort(); LogPrintf("CActiveMasternodeManager::Init -- ERROR: %s\n", m_error); return; } - m_info.proTxHash = dmn->proTxHash; - m_info.outpoint = dmn->collateralOutpoint; + m_protx_hash = dmn->proTxHash; + m_outpoint = dmn->collateralOutpoint; m_state = MasternodeState::READY; } @@ -175,15 +176,15 @@ void CActiveMasternodeManager::UpdatedBlockTip(const CBlockIndex* pindexNew, con { if (!DeploymentDIP0003Enforced(pindexNew->nHeight, Params().GetConsensus())) return; - const auto [cur_state, cur_protx_hash] = WITH_READ_LOCK(cs, return std::make_pair(m_state, m_info.proTxHash)); + const auto [cur_state, cur_protx_hash] = WITH_READ_LOCK(cs, return std::make_pair(m_state, m_protx_hash)); if (cur_state == MasternodeState::READY) { - auto oldMNList = Assert(m_dmnman)->GetListForBlock(pindexNew->pprev); - auto newMNList = m_dmnman->GetListForBlock(pindexNew); + auto oldMNList = m_dmnman.GetListForBlock(pindexNew->pprev); + auto newMNList = m_dmnman.GetListForBlock(pindexNew); auto reset = [this, pindexNew](MasternodeState state) -> void { LOCK(cs); m_state = state; - m_info.proTxHash = uint256(); - m_info.outpoint.SetNull(); + m_protx_hash = uint256(); + m_outpoint.SetNull(); // MN might have reappeared in same block with a new ProTx InitInternal(pindexNew); }; @@ -266,7 +267,7 @@ template