diff --git a/src/Makefile.am b/src/Makefile.am index d62df14146fd..5b93496ea800 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -220,8 +220,9 @@ BITCOIN_CORE_H = \ dsnotificationinterface.h \ governance/classes.h \ governance/common.h \ - governance/governance.h \ governance/exceptions.h \ + governance/governance.h \ + governance/net_governance.h \ governance/object.h \ governance/signing.h \ governance/validators.h \ @@ -311,6 +312,7 @@ BITCOIN_CORE_H = \ node/miner.h \ node/minisketchwrapper.h \ node/psbt.h \ + node/sync_manager.h \ node/transaction.h \ node/txreconciliation.h \ node/interface_ui.h \ @@ -508,6 +510,7 @@ libbitcoin_node_a_SOURCES = \ governance/classes.cpp \ governance/exceptions.cpp \ governance/governance.cpp \ + governance/net_governance.cpp \ governance/object.cpp \ governance/signing.cpp \ governance/validators.cpp \ @@ -567,6 +570,7 @@ libbitcoin_node_a_SOURCES = \ node/miner.cpp \ node/minisketchwrapper.cpp \ node/psbt.cpp \ + node/sync_manager.cpp \ node/transaction.cpp \ node/txreconciliation.cpp \ node/interface_ui.cpp \ diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index 108f3690b163..2a421fd2db7a 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -25,7 +24,7 @@ #include #include #include -#include +#include const std::string GovernanceStore::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-16"; @@ -85,38 +84,6 @@ CGovernanceManager::~CGovernanceManager() m_db->Store(*this); } -void CGovernanceManager::Schedule(CScheduler& scheduler, CConnman& connman, PeerManager& peerman) -{ - AssertLockNotHeld(cs_store); - AssertLockNotHeld(cs_relay); - - assert(IsValid()); - - scheduler.scheduleEvery( - [this, &connman]() -> void { - if (!m_mn_sync.IsSynced()) return; - - // CHECK OBJECTS WE'VE ASKED FOR, REMOVE OLD ENTRIES - CleanOrphanObjects(); - RequestOrphanObjects(connman); - - // CHECK AND REMOVE - REPROCESS GOVERNANCE OBJECTS - CheckAndRemove(); - }, - std::chrono::minutes{5}); - - scheduler.scheduleEvery( - [this, &peerman]() -> void { - LOCK(cs_relay); - for (const auto& inv : m_relay_invs) { - peerman.RelayInv(inv); - } - m_relay_invs.clear(); - }, - // Tests need tighter timings to avoid timeouts, use more relaxed pacing otherwise - Params().IsMockableChain() ? std::chrono::seconds{1} : std::chrono::seconds{5}); -} - bool CGovernanceManager::LoadCache(bool load_cache) { AssertLockNotHeld(cs_store); @@ -213,151 +180,54 @@ void CGovernanceManager::AddPostponedObjectInternal(const CGovernanceObject& gov mapPostponedObjects.emplace(govobj.GetHash(), std::make_shared(govobj)); } -MessageProcessingResult CGovernanceManager::ProcessMessage(CNode& peer, CConnman& connman, std::string_view msg_type, - CDataStream& vRecv) +bool CGovernanceManager::ProcessObject(const CNode& peer, const uint256& nHash, CGovernanceObject& govobj) { - AssertLockNotHeld(cs_store); - AssertLockNotHeld(cs_relay); - if (!IsValid()) return {}; - if (!m_mn_sync.IsBlockchainSynced()) return {}; + std::string strHash = nHash.ToString(); - const auto tip_mn_list = Assert(m_dmnman)->GetListAtChainTip(); - // ANOTHER USER IS ASKING US TO HELP THEM SYNC GOVERNANCE OBJECT DATA - if (msg_type == NetMsgType::MNGOVERNANCESYNC) { - // Ignore such requests until we are fully synced. - // We could start processing this after masternode list is synced - // but this is a heavy one so it's better to finish sync first. - if (!m_mn_sync.IsSynced()) return {}; - - uint256 nProp; - CBloomFilter filter; - vRecv >> nProp; - vRecv >> filter; - - LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- syncing governance objects to our peer %s\n", peer.GetLogString()); - LOCK(cs_store); - if (nProp == uint256()) { - return SyncObjects(peer, connman); - } else { - return SyncSingleObjVotes(peer, nProp, filter, connman); - } + LOCK(cs_store); - return {}; + if (mapObjects.count(nHash) || mapPostponedObjects.count(nHash) || mapErasedGovernanceObjects.count(nHash)) { + // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE? + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received already seen object: %s\n", strHash); + return true; } - // A NEW GOVERNANCE OBJECT HAS ARRIVED - else if (msg_type == NetMsgType::MNGOVERNANCEOBJECT) { - // MAKE SURE WE HAVE A VALID REFERENCE TO THE TIP BEFORE CONTINUING - - CGovernanceObject govobj; - vRecv >> govobj; - - uint256 nHash = govobj.GetHash(); - - MessageProcessingResult ret{}; - ret.m_to_erase = CInv{MSG_GOVERNANCE_OBJECT, nHash}; - - if (!m_mn_sync.IsBlockchainSynced()) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode list not synced\n"); - return ret; - } - - std::string strHash = nHash.ToString(); - - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received object: %s\n", strHash); - - if (!AcceptMessage(nHash)) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received unrequested object: %s\n", strHash); - return ret; - } - - LOCK2(::cs_main, cs_store); - - if (mapObjects.count(nHash) || mapPostponedObjects.count(nHash) || mapErasedGovernanceObjects.count(nHash)) { - // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE? - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received already seen object: %s\n", strHash); - return ret; - } - - bool fRateCheckBypassed = false; - if (!MasternodeRateCheck(govobj, true, false, fRateCheckBypassed)) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", strHash, nCachedBlockHeight); - return ret; - } - - std::string strError; - // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN - - bool fMissingConfirmations = false; - bool fIsValid = govobj.IsValidLocally(tip_mn_list, m_chainman, strError, fMissingConfirmations, true); - - bool unused_rcb; - if (fRateCheckBypassed && fIsValid && !MasternodeRateCheck(govobj, true, true, unused_rcb)) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - %s - (current block height %d)\n", strHash, nCachedBlockHeight); - return ret; - } - - if (!fIsValid) { - if (fMissingConfirmations) { - AddPostponedObjectInternal(govobj); - LogPrintf("MNGOVERNANCEOBJECT -- Not enough fee confirmations for: %s, strError = %s\n", strHash, strError); - } else { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Governance object is invalid - %s\n", strError); - // apply node's ban score - ret.m_error = MisbehavingError{20}; - return ret; - } - - return ret; - } - - AddGovernanceObjectInternal(govobj, &peer); - return ret; + bool fRateCheckBypassed = false; + if (!MasternodeRateCheck(govobj, true, false, fRateCheckBypassed)) { + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", + strHash, nCachedBlockHeight); + return true; } - // A NEW GOVERNANCE OBJECT VOTE HAS ARRIVED - else if (msg_type == NetMsgType::MNGOVERNANCEOBJECTVOTE) { - CGovernanceVote vote; - vRecv >> vote; - - uint256 nHash = vote.GetHash(); - - MessageProcessingResult ret{}; - ret.m_to_erase = CInv{MSG_GOVERNANCE_OBJECT_VOTE, nHash}; - - // Ignore such messages until masternode list is synced - if (!m_mn_sync.IsBlockchainSynced()) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- masternode list not synced\n"); - return ret; - } - - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Received vote: %s\n", vote.ToString(tip_mn_list)); + std::string strError; + // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN - std::string strHash = nHash.ToString(); + const auto tip_mn_list = GetMNManager().GetListAtChainTip(); + bool fMissingConfirmations = false; + bool fIsValid = govobj.IsValidLocally(tip_mn_list, m_chainman, strError, fMissingConfirmations, true); - if (!AcceptMessage(nHash)) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Received unrequested vote object: %s, hash: %s, peer = %d\n", - vote.ToString(tip_mn_list), strHash, peer.GetId()); - return ret; - } + bool unused_rcb; + if (fRateCheckBypassed && fIsValid && !MasternodeRateCheck(govobj, true, true, unused_rcb)) { + LogPrint(BCLog::GOBJECT, /* Continued */ + "MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - %s - (current " + "block height %d)\n", + strHash, nCachedBlockHeight); + return true; + } - CGovernanceException exception; - if (ProcessVote(&peer, vote, exception, connman)) { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- %s new\n", strHash); - m_mn_sync.BumpAssetLastTime("MNGOVERNANCEOBJECTVOTE"); - RelayVote(vote); + if (!fIsValid) { + if (fMissingConfirmations) { + AddPostponedObjectInternal(govobj); + LogPrintf("MNGOVERNANCEOBJECT -- Not enough fee confirmations for: %s, strError = %s\n", strHash, strError); + return true; } else { - LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Rejected vote, error = %s\n", exception.what()); - if ((exception.GetNodePenalty() != 0) && m_mn_sync.IsSynced()) { - ret.m_error = MisbehavingError{exception.GetNodePenalty()}; - return ret; - } - return ret; + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Governance object is invalid - %s\n", strError); + return false; } - return ret; } - return {}; + AddGovernanceObjectInternal(govobj, &peer); + return true; } void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj) @@ -576,6 +446,14 @@ void CGovernanceManager::CheckAndRemove() ToString(), m_requested_hash_time.size()); } +std::vector CGovernanceManager::FetchRelayInventory() +{ + std::vector ret; + LOCK(cs_relay); + swap(ret, m_relay_invs); + return ret; +} + std::shared_ptr CGovernanceManager::FindConstGovernanceObject(const uint256& nHash) const { LOCK(cs_store); @@ -718,7 +596,7 @@ bool CGovernanceManager::ConfirmInventoryRequest(const CInv& inv) MessageProcessingResult CGovernanceManager::SyncSingleObjVotes(CNode& peer, const uint256& nProp, const CBloomFilter& filter, CConnman& connman) { - AssertLockHeld(cs_store); + LOCK(cs_store); // do not provide any data until our node is synced if (!m_mn_sync.IsSynced()) return {}; @@ -771,7 +649,7 @@ MessageProcessingResult CGovernanceManager::SyncSingleObjVotes(CNode& peer, cons MessageProcessingResult CGovernanceManager::SyncObjects(CNode& peer, CConnman& connman) const { - AssertLockHeld(cs_store); + LOCK(cs_store); assert(m_netfulfilledman.IsValid()); // do not provide any data until our node is synced @@ -1079,52 +957,16 @@ void CGovernanceManager::RequestGovernanceObject(CNode* pfrom, const uint256& nH connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, nHash, filter)); } -int CGovernanceManager::RequestGovernanceObjectVotes(CNode& peer, CConnman& connman, const PeerManager& peerman) const -{ - const std::vector vNodeCopy{&peer}; - return RequestGovernanceObjectVotes(vNodeCopy, connman, peerman); -} +CDeterministicMNManager& CGovernanceManager::GetMNManager() { return *Assert(m_dmnman); } -int CGovernanceManager::RequestGovernanceObjectVotes(const std::vector& vNodesCopy, CConnman& connman, - const PeerManager& peerman) const +std::pair, std::vector> CGovernanceManager::FetchGovernanceObjectVotes( + size_t nPeersPerHashMax, int64_t nNow, std::map>& mapAskedRecently) const { - AssertLockNotHeld(cs_store); - - static std::map > mapAskedRecently; - // Maximum number of nodes to request votes from for the same object hash on real networks - // (mainnet, testnet, devnets). Keep this low to avoid unnecessary bandwidth usage. - static constexpr size_t REALNET_PEERS_PER_HASH{3}; - // Maximum number of nodes to request votes from for the same object hash on regtest. - // During testing, nodes are isolated to create conflicting triggers. Using the real - // networks limit of 3 nodes often results in querying only "non-isolated" nodes, missing the - // isolated ones we need to test. This high limit ensures all available nodes are queried. - static constexpr size_t REGTEST_PEERS_PER_HASH{std::numeric_limits::max()}; - - if (vNodesCopy.empty()) return -1; - - int64_t nNow = GetTime(); - int nTimeout = 60 * 60; - size_t nPeersPerHashMax = Params().IsMockableChain() ? REGTEST_PEERS_PER_HASH : REALNET_PEERS_PER_HASH; - std::vector vTriggerObjHashes; std::vector vOtherObjHashes; - - // This should help us to get some idea about an impact this can bring once deployed on mainnet. - // Testnet is ~40 times smaller in masternode count, but only ~1000 masternodes usually vote, - // so 1 obj on mainnet == ~10 objs or ~1000 votes on testnet. However we want to test a higher - // number of votes to make sure it's robust enough, so aim at 2000 votes per masternode per request. - // On mainnet nMaxObjRequestsPerNode is always set to 1. - int nMaxObjRequestsPerNode = 1; - size_t nProjectedVotes = 2000; - if (Params().NetworkIDString() != CBaseChainParams::MAIN) { - nMaxObjRequestsPerNode = std::max(1, int(nProjectedVotes / std::max(1, (int)Assert(m_dmnman)->GetListAtChainTip().GetValidMNsCount()))); - } - { LOCK(cs_store); - if (mapObjects.empty()) return -2; - for (const auto& [nHash, govobj] : mapObjects) { if (Assert(govobj)->IsSetCachedDelete()) continue; if (mapAskedRecently.count(nHash)) { @@ -1145,56 +987,7 @@ int CGovernanceManager::RequestGovernanceObjectVotes(const std::vector& } } } - - LogPrint(BCLog::GOBJECT, "CGovernanceManager::RequestGovernanceObjectVotes -- start: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", - vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size()); - - Shuffle(vTriggerObjHashes.begin(), vTriggerObjHashes.end(), FastRandomContext()); - Shuffle(vOtherObjHashes.begin(), vOtherObjHashes.end(), FastRandomContext()); - - for (int i = 0; i < nMaxObjRequestsPerNode; ++i) { - uint256 nHashGovobj; - - // ask for triggers first - if (!vTriggerObjHashes.empty()) { - nHashGovobj = vTriggerObjHashes.back(); - } else { - if (vOtherObjHashes.empty()) break; - nHashGovobj = vOtherObjHashes.back(); - } - bool fAsked = false; - for (const auto& pnode : vNodesCopy) { - // Don't try to sync any data from outbound non-relay "masternode" connections. - // Inbound connection this early is most likely a "masternode" connection - // initiated from another node, so skip it too. - if (!pnode->CanRelay() || (connman.IsActiveMasternode() && pnode->IsInboundConn())) continue; - // stop early to prevent setAskFor overflow - { - LOCK(::cs_main); - size_t nProjectedSize = peerman.GetRequestedObjectCount(pnode->GetId()) + nProjectedVotes; - if (nProjectedSize > MAX_INV_SZ) continue; - // to early to ask the same node - if (mapAskedRecently[nHashGovobj].count(pnode->addr)) continue; - } - - RequestGovernanceObject(pnode, nHashGovobj, connman, true); - mapAskedRecently[nHashGovobj][pnode->addr] = nNow + nTimeout; - fAsked = true; - // stop loop if max number of peers per obj was asked - if (mapAskedRecently[nHashGovobj].size() >= nPeersPerHashMax) break; - } - // NOTE: this should match `if` above (the one before `while`) - if (!vTriggerObjHashes.empty()) { - vTriggerObjHashes.pop_back(); - } else { - vOtherObjHashes.pop_back(); - } - if (!fAsked) i--; - } - LogPrint(BCLog::GOBJECT, "CGovernanceManager::RequestGovernanceObjectVotes -- end: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", - vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size()); - - return int(vTriggerObjHashes.size() + vOtherObjHashes.size()); + return {vTriggerObjHashes, vOtherObjHashes}; } bool CGovernanceManager::AcceptMessage(const uint256& nHash) @@ -1376,11 +1169,23 @@ void CGovernanceManager::UpdatedBlockTip(const CBlockIndex* pindex) void CGovernanceManager::RequestOrphanObjects(CConnman& connman) { AssertLockNotHeld(cs_store); - const CConnman::NodesSnapshot snap{connman, /* cond = */ CConnman::FullyConnectedOnly}; std::vector vecHashesFiltered; + int64_t nNow = GetTime().count(); { LOCK(cs_store); + + // clean up outdated before requesting + const vote_cmm_t::list_t& items = cmmapOrphanVotes.GetItemList(); + for (auto it = items.begin(); it != items.end();) { + auto prevIt = it; + ++it; + const auto& [_, time] = prevIt->value; + if (time < nNow) { + cmmapOrphanVotes.Erase(prevIt->key, prevIt->value); + } + } + std::vector vecHashes; cmmapOrphanVotes.GetKeys(vecHashes); for (const uint256& nHash : vecHashes) { @@ -1390,6 +1195,7 @@ void CGovernanceManager::RequestOrphanObjects(CConnman& connman) } } + const CConnman::NodesSnapshot snap{connman, /* cond = */ CConnman::FullyConnectedOnly}; LogPrint(BCLog::GOBJECT, "CGovernanceObject::RequestOrphanObjects -- number objects = %d\n", vecHashesFiltered.size()); for (const uint256& nHash : vecHashesFiltered) { for (CNode* pnode : snap.Nodes()) { @@ -1401,23 +1207,6 @@ void CGovernanceManager::RequestOrphanObjects(CConnman& connman) } } -void CGovernanceManager::CleanOrphanObjects() -{ - LOCK(cs_store); - const vote_cmm_t::list_t& items = cmmapOrphanVotes.GetItemList(); - - int64_t nNow = GetTime().count(); - - for (auto it = items.begin(); it != items.end();) { - auto prevIt = it; - ++it; - const auto& [vote, time] = prevIt->value; - if (time < nNow) { - cmmapOrphanVotes.Erase(prevIt->key, prevIt->value); - } - } -} - void CGovernanceManager::RemoveInvalidVotes() { AssertLockHeld(cs_store); diff --git a/src/governance/governance.h b/src/governance/governance.h index 6d4a9bbf9b02..781ac81fe3c4 100644 --- a/src/governance/governance.h +++ b/src/governance/governance.h @@ -17,7 +17,6 @@ #include #include #include -#include #include #include #include @@ -32,8 +31,6 @@ template class CFlatDB; class CInv; class CNode; -class CScheduler; -class PeerManager; class CDeterministicMNList; class CDeterministicMNManager; @@ -46,11 +43,9 @@ class CMasternodeSync; class CNetFulfilledRequestManager; class CSporkManager; class CSuperblock; -class GovernanceSigner; class UniValue; -using CDeterministicMNListPtr = std::shared_ptr; using CSuperblock_sptr = std::shared_ptr; using vote_time_pair_t = std::pair; @@ -293,10 +288,6 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_store); void Clear() EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - void CheckAndRemove() - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - void Schedule(CScheduler& scheduler, CConnman& connman, PeerManager& peerman) - EXCLUSIVE_LOCKS_REQUIRED(!cs_store, !cs_relay); // CGovernanceObject bool AreRateChecksEnabled() const { return fRateChecksEnabled; } @@ -320,14 +311,6 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_store); bool ProcessVoteAndRelay(const CGovernanceVote& vote, CGovernanceException& exception, CConnman& connman) override EXCLUSIVE_LOCKS_REQUIRED(!cs_store, !cs_relay); - int RequestGovernanceObjectVotes(CNode& peer, CConnman& connman, const PeerManager& peerman) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - int RequestGovernanceObjectVotes(const std::vector& vNodesCopy, CConnman& connman, - const PeerManager& peerman) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - [[nodiscard]] MessageProcessingResult ProcessMessage(CNode& peer, CConnman& connman, std::string_view msg_type, - CDataStream& vRecv) - EXCLUSIVE_LOCKS_REQUIRED(!cs_store, !cs_relay); void RelayObject(const CGovernanceObject& obj) EXCLUSIVE_LOCKS_REQUIRED(!cs_relay); void RelayVote(const CGovernanceVote& vote) @@ -381,14 +364,30 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent std::shared_ptr FindConstGovernanceObject(const uint256& nHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_store); -private: - // Branches of ProcessMessage + // Used by NetGovernance + std::vector FetchRelayInventory() EXCLUSIVE_LOCKS_REQUIRED(!cs_relay); + void CheckAndRemove() EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + void RequestOrphanObjects(CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + std::pair, std::vector> FetchGovernanceObjectVotes( + size_t peers_per_hash_max, int64_t now, std::map>& map_asked_recently) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + void RequestGovernanceObject(CNode* pfrom, const uint256& nHash, CConnman& connman, bool fUseFilter = false) const + EXCLUSIVE_LOCKS_REQUIRED(!cs_store); [[nodiscard]] MessageProcessingResult SyncObjects(CNode& peer, CConnman& connman) const - EXCLUSIVE_LOCKS_REQUIRED(cs_store); + EXCLUSIVE_LOCKS_REQUIRED(!cs_store); [[nodiscard]] MessageProcessingResult SyncSingleObjVotes(CNode& peer, const uint256& nProp, const CBloomFilter& filter, - CConnman& connman) - EXCLUSIVE_LOCKS_REQUIRED(cs_store); + CConnman& connman) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + /// Called to indicate a requested object or vote has been received + bool AcceptMessage(const uint256& nHash) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + bool ProcessObject(const CNode& peer, const uint256& hash, CGovernanceObject& govobj) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_store, !cs_relay); + + CDeterministicMNManager& GetMNManager(); + bool ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception, CConnman& connman) + EXCLUSIVE_LOCKS_REQUIRED(!cs_store); + +private: // Internal counterparts to "Thread-safe accessors" void AddPostponedObjectInternal(const CGovernanceObject& govobj) EXCLUSIVE_LOCKS_REQUIRED(cs_store); @@ -433,16 +432,6 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent void ExecuteBestSuperblock(const CDeterministicMNList& tip_mn_list, int nBlockHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_store); - void RequestGovernanceObject(CNode* pfrom, const uint256& nHash, CConnman& connman, bool fUseFilter = false) const - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - - bool ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception, CConnman& connman) - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - - /// Called to indicate a requested object or vote has been received - bool AcceptMessage(const uint256& nHash) - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - void CheckOrphanVotes(CGovernanceObject& govobj) EXCLUSIVE_LOCKS_REQUIRED(cs_store, !cs_relay); @@ -452,12 +441,6 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent void AddCachedTriggers() EXCLUSIVE_LOCKS_REQUIRED(cs_store); - void RequestOrphanObjects(CConnman& connman) - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - - void CleanOrphanObjects() - EXCLUSIVE_LOCKS_REQUIRED(!cs_store); - void RemoveInvalidVotes() EXCLUSIVE_LOCKS_REQUIRED(cs_store); }; diff --git a/src/governance/net_governance.cpp b/src/governance/net_governance.cpp new file mode 100644 index 000000000000..63ca68f85671 --- /dev/null +++ b/src/governance/net_governance.cpp @@ -0,0 +1,149 @@ +// Copyright (c) 2024-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 + +class CConnman; + +void NetGovernance::Schedule(CScheduler& scheduler) +{ + // Code below is meant to be running only if governance validation is enabled + // + if (!m_gov_manager.IsValid()) return; + scheduler.scheduleEvery( + [this]() -> void { + if (!m_node_sync.IsSynced()) return; + + // CHECK OBJECTS WE'VE ASKED FOR, REMOVE OLD ENTRIES + m_gov_manager.RequestOrphanObjects(m_connman); + + // CHECK AND REMOVE - REPROCESS GOVERNANCE OBJECTS + m_gov_manager.CheckAndRemove(); + }, + std::chrono::minutes{5}); + + scheduler.scheduleEvery( + [this]() -> void { + auto relay_invs = m_gov_manager.FetchRelayInventory(); + for (const auto& inv : relay_invs) { + m_peer_manager->PeerRelayInv(inv); + } + }, + // Tests need tighter timings to avoid timeouts, use more relaxed pacing otherwise + Params().IsMockableChain() ? std::chrono::seconds{1} : std::chrono::seconds{5}); +} + +void NetGovernance::ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv) +{ + if (!m_gov_manager.IsValid()) return; + if (!m_node_sync.IsBlockchainSynced()) return; + + // ANOTHER USER IS ASKING US TO HELP THEM SYNC GOVERNANCE OBJECT DATA + if (msg_type == NetMsgType::MNGOVERNANCESYNC) { + // Ignore such requests until we are fully synced. + // We could start processing this after masternode list is synced + // but this is a heavy one so it's better to finish sync first. + if (!m_node_sync.IsSynced()) return; + + uint256 nProp; + CBloomFilter filter; + vRecv >> nProp; + vRecv >> filter; + + LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- syncing governance objects to our peer %s\n", peer.GetLogString()); + if (nProp == uint256()) { + m_peer_manager->PeerPostProcessMessage(m_gov_manager.SyncObjects(peer, m_connman)); + } else { + m_peer_manager->PeerPostProcessMessage(m_gov_manager.SyncSingleObjVotes(peer, nProp, filter, m_connman)); + } + } + // A NEW GOVERNANCE OBJECT HAS ARRIVED + else if (msg_type == NetMsgType::MNGOVERNANCEOBJECT) { + // MAKE SURE WE HAVE A VALID REFERENCE TO THE TIP BEFORE CONTINUING + CGovernanceObject govobj; + vRecv >> govobj; + + uint256 nHash = govobj.GetHash(); + + WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(peer.GetId(), CInv{MSG_GOVERNANCE_OBJECT, nHash})); + + if (!m_node_sync.IsBlockchainSynced()) { + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode list not synced\n"); + return; + } + + std::string strHash = nHash.ToString(); + + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received object: %s\n", strHash); + + if (!m_gov_manager.AcceptMessage(nHash)) { + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received unrequested object: %s\n", strHash); + return; + } + + if (!WITH_LOCK(::cs_main, return m_gov_manager.ProcessObject(peer, nHash, govobj))) { + // apply node's ban score + m_peer_manager->PeerMisbehaving(peer.GetId(), 20); + } + } + + // A NEW GOVERNANCE OBJECT VOTE HAS ARRIVED + else if (msg_type == NetMsgType::MNGOVERNANCEOBJECTVOTE) { + CGovernanceVote vote; + vRecv >> vote; + + uint256 nHash = vote.GetHash(); + + WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(peer.GetId(), CInv{MSG_GOVERNANCE_OBJECT_VOTE, nHash})); + + // Ignore such messages until masternode list is synced + if (!m_node_sync.IsBlockchainSynced()) { + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- masternode list not synced\n"); + return; + } + + const auto tip_mn_list = m_gov_manager.GetMNManager().GetListAtChainTip(); + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Received vote: %s\n", vote.ToString(tip_mn_list)); + + std::string strHash = nHash.ToString(); + + if (!m_gov_manager.AcceptMessage(nHash)) { + LogPrint(BCLog::GOBJECT, /* Continued */ + "MNGOVERNANCEOBJECTVOTE -- Received unrequested vote object: %s, hash: %s, peer = %d\n", + vote.ToString(tip_mn_list), strHash, peer.GetId()); + return; + } + + CGovernanceException exception; + if (m_gov_manager.ProcessVote(&peer, vote, exception, m_connman)) { + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- %s new\n", strHash); + m_node_sync.BumpAssetLastTime("MNGOVERNANCEOBJECTVOTE"); + + if (!m_node_sync.IsSynced()) { + LogPrint(BCLog::GOBJECT, "%s -- won't relay until fully synced\n", __func__); + return; + } + auto dmn = tip_mn_list.GetMNByCollateral(vote.GetMasternodeOutpoint()); + if (!dmn) { + return; + } + m_gov_manager.RelayVote(vote); + // TODO: figure out why immediate sending of inventory doesn't work here! + // m_peer_manager->PeerRelayInv(CInv{MSG_GOVERNANCE_OBJECT_VOTE, nHash}); + } else { + LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Rejected vote, error = %s\n", exception.what()); + if ((exception.GetNodePenalty() != 0) && m_node_sync.IsSynced()) { + m_peer_manager->PeerMisbehaving(peer.GetId(), exception.GetNodePenalty()); + } + } + } +} diff --git a/src/governance/net_governance.h b/src/governance/net_governance.h new file mode 100644 index 000000000000..bfa14bb05939 --- /dev/null +++ b/src/governance/net_governance.h @@ -0,0 +1,34 @@ +// 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_GOVERNANCE_NET_GOVERNANCE_H +#define BITCOIN_GOVERNANCE_NET_GOVERNANCE_H + +#include + +class CGovernanceManager; +class CMasternodeSync; + +class NetGovernance final : public NetHandler +{ +public: + NetGovernance(PeerManagerInternal* peer_manager, CGovernanceManager& gov_manager, CMasternodeSync& node_sync, + CConnman& connman) : + NetHandler(peer_manager), + m_gov_manager(gov_manager), + m_node_sync(node_sync), + m_connman(connman) + { + } + void Schedule(CScheduler& scheduler) override; + + void ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv) override; + +private: + CGovernanceManager& m_gov_manager; + CMasternodeSync& m_node_sync; + CConnman& m_connman; +}; + +#endif // BITCOIN_GOVERNANCE_NET_GOVERNANCE_H diff --git a/src/init.cpp b/src/init.cpp index 65d2bd8c9256..23c61b3319b6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -88,6 +89,7 @@ #include #include #include +#include #include #include #include @@ -1988,7 +1990,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) * need it or not further down and then query if the database is initialized * to check if validation is enabled. */ - node.mn_sync = std::make_unique(*node.connman, *node.netfulfilledman); + node.mn_sync = std::make_unique(std::make_unique(*node.connman, *node.netfulfilledman)); node.govman = std::make_unique(*node.mn_metaman, *node.netfulfilledman, *node.chainman, node.dmnman, *node.mn_sync); @@ -2016,12 +2018,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) args.GetDataDirNet(), fPruneMode, args.GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX), - is_governance_enabled, args.GetBoolArg("-spentindex", DEFAULT_SPENTINDEX), args.GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX), - args.GetBoolArg("-txindex", DEFAULT_TXINDEX), chainparams.GetConsensus(), - chainparams.NetworkIDString(), fReindexChainState, cache_sizes.block_tree_db, cache_sizes.coins_db, @@ -2050,8 +2049,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); case ChainstateLoadingError::ERROR_BAD_DEVNET_GENESIS_BLOCK: return InitError(_("Incorrect or no devnet genesis block found. Wrong datadir for devnet specified?")); - case ChainstateLoadingError::ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED: - return InitError(_("Transaction index can't be disabled with governance validation enabled. Either start with -disablegovernance command line switch or enable transaction index.")); case ChainstateLoadingError::ERROR_ADDRIDX_NEEDS_REINDEX: strLoadError = _("You need to rebuild the database using -reindex to enable -addressindex"); break; @@ -2238,7 +2235,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } return InitError(strprintf(_("Failed to clear governance cache at %s"), file_path)); } + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.govman, *node.mn_sync, *node.connman)); } + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.govman, *node.mn_sync, *node.connman, *node.netfulfilledman)); // ********************************************************* Step 8: start indexers if (args.GetBoolArg("-txindex", DEFAULT_TXINDEX)) { @@ -2308,13 +2307,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) if (node.active_ctx) node.active_ctx->Start(*node.connman, *node.peerman); node.scheduler->scheduleEvery(std::bind(&CNetFulfilledRequestManager::DoMaintenance, std::ref(*node.netfulfilledman)), std::chrono::minutes{1}); - node.scheduler->scheduleEvery(std::bind(&CMasternodeSync::DoMaintenance, std::ref(*node.mn_sync), std::cref(*node.peerman), std::cref(*node.govman)), std::chrono::seconds{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}); node.scheduler->scheduleEvery(std::bind(&CDeterministicMNManager::DoMaintenance, std::ref(*node.dmnman)), std::chrono::seconds{10}); - - if (node.govman->IsValid()) { - node.govman->Schedule(*node.scheduler, *node.connman, *node.peerman); - } + node.peerman->ScheduleHandlers(*node.scheduler); if (node.mn_activeman) { node.scheduler->scheduleEvery(std::bind(&CCoinJoinServer::DoMaintenance, std::ref(*node.active_ctx->cj_server)), std::chrono::seconds{1}); diff --git a/src/masternode/sync.cpp b/src/masternode/sync.cpp index 674e6d7994c1..14fc2d50ad4b 100644 --- a/src/masternode/sync.cpp +++ b/src/masternode/sync.cpp @@ -4,22 +4,17 @@ #include -#include -#include -#include -#include -#include -#include +#include +#include #include #include -#include -CMasternodeSync::CMasternodeSync(CConnman& _connman, CNetFulfilledRequestManager& netfulfilledman) : +CMasternodeSync::CMasternodeSync(std::unique_ptr&& sync_notifier) : nTimeAssetSyncStarted{GetTime()}, nTimeLastBumped{GetTime()}, - connman{_connman}, - m_netfulfilledman{netfulfilledman} + m_sync_notifier{std::move(sync_notifier)} { + assert(m_sync_notifier != nullptr); } CMasternodeSync::~CMasternodeSync() = default; @@ -39,7 +34,7 @@ void CMasternodeSync::Reset(bool fForce, bool fNotifyReset) nTimeLastUpdateBlockTip = 0; fReachedBestHeader = false; if (fNotifyReset) { - uiInterface.NotifyAdditionalDataSyncProgressChanged(-1); + m_sync_notifier->SyncReset(); } } @@ -63,8 +58,6 @@ std::string CMasternodeSync::GetAssetName() const void CMasternodeSync::SwitchToNextAsset() { - assert(m_netfulfilledman.IsValid()); - switch(nCurrentAsset) { case(MASTERNODE_SYNC_BLOCKCHAIN): @@ -75,11 +68,7 @@ void CMasternodeSync::SwitchToNextAsset() case(MASTERNODE_SYNC_GOVERNANCE): LogPrintf("CMasternodeSync::SwitchToNextAsset -- Completed %s in %llds\n", GetAssetName(), GetTime() - nTimeAssetSyncStarted); nCurrentAsset = MASTERNODE_SYNC_FINISHED; - uiInterface.NotifyAdditionalDataSyncProgressChanged(1); - - connman.ForEachNode(CConnman::AllNodes, [this](const CNode* pnode) { - m_netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync"); - }); + m_sync_notifier->SyncFinished(); LogPrintf("CMasternodeSync::SwitchToNextAsset -- Sync has finished\n"); break; @@ -99,209 +88,6 @@ std::string CMasternodeSync::GetSyncStatus() const } } -void CMasternodeSync::ProcessMessage(const CNode& peer, std::string_view msg_type, CDataStream& vRecv) const -{ - //Sync status count - if (msg_type != NetMsgType::SYNCSTATUSCOUNT) return; - - //do not care about stats if sync process finished - if (IsSynced()) return; - - int nItemID; - int nCount; - vRecv >> nItemID >> nCount; - - LogPrint(BCLog::MNSYNC, "SYNCSTATUSCOUNT -- got inventory count: nItemID=%d nCount=%d peer=%d\n", nItemID, nCount, peer.GetId()); -} - -void CMasternodeSync::ProcessTick(const PeerManager& peerman, const CGovernanceManager& govman) -{ - assert(m_netfulfilledman.IsValid()); - - static int nTick = 0; - nTick++; - - const static int64_t nSyncStart = TicksSinceEpoch(SystemClock::now()); - const static std::string strAllow = strprintf("allow-sync-%lld", nSyncStart); - - // reset the sync process if the last call to this function was more than 60 minutes ago (client was in sleep mode) - static int64_t nTimeLastProcess = GetTime(); - if (!Params().IsMockableChain() && GetTime() - nTimeLastProcess > 60 * 60 && !connman.IsActiveMasternode()) { - LogPrintf("CMasternodeSync::ProcessTick -- WARNING: no actions for too long, restarting sync...\n"); - Reset(true); - nTimeLastProcess = GetTime(); - return; - } - - if(GetTime() - nTimeLastProcess < MASTERNODE_SYNC_TICK_SECONDS) { - // too early, nothing to do here - return; - } - - nTimeLastProcess = GetTime(); - const CConnman::NodesSnapshot snap{connman, /* cond = */ CConnman::FullyConnectedOnly}; - - // gradually request the rest of the votes after sync finished - if(IsSynced()) { - govman.RequestGovernanceObjectVotes(snap.Nodes(), connman, peerman); - return; - } - - // Calculate "progress" for LOG reporting / GUI notification - double nSyncProgress = double(nTriedPeerCount + (nCurrentAsset - 1) * 8) / (8*4); - LogPrint(BCLog::MNSYNC, "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d nTriedPeerCount %d nSyncProgress %f\n", nTick, nCurrentAsset, nTriedPeerCount, nSyncProgress); - uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); - - for (auto& pnode : snap.Nodes()) - { - CNetMsgMaker msgMaker(pnode->GetCommonVersion()); - - // Don't try to sync any data from outbound non-relay "masternode" connections. - // Inbound connection this early is most likely a "masternode" connection - // initiated from another node, so skip it too. - if (!pnode->CanRelay() || (connman.IsActiveMasternode() && pnode->IsInboundConn())) continue; - - { - if ((pnode->HasPermission(NetPermissionFlags::NoBan) || pnode->IsManualConn()) && !m_netfulfilledman.HasFulfilledRequest(pnode->addr, strAllow)) { - m_netfulfilledman.RemoveAllFulfilledRequests(pnode->addr); - m_netfulfilledman.AddFulfilledRequest(pnode->addr, strAllow); - LogPrintf("CMasternodeSync::ProcessTick -- skipping mnsync restrictions for peer=%d\n", pnode->GetId()); - } - - if(m_netfulfilledman.HasFulfilledRequest(pnode->addr, "full-sync")) { - // We already fully synced from this node recently, - // disconnect to free this connection slot for another peer. - pnode->fDisconnect = true; - LogPrintf("CMasternodeSync::ProcessTick -- disconnecting from recently synced peer=%d\n", pnode->GetId()); - continue; - } - - // SPORK : ALWAYS ASK FOR SPORKS AS WE SYNC - - if(!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "spork-sync")) { - // always get sporks first, only request once from each peer - m_netfulfilledman.AddFulfilledRequest(pnode->addr, "spork-sync"); - // get current network sporks - connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS)); - LogPrint(BCLog::MNSYNC, "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d -- requesting sporks from peer=%d\n", nTick, nCurrentAsset, pnode->GetId()); - } - - if (nCurrentAsset == MASTERNODE_SYNC_BLOCKCHAIN) { - int64_t nTimeSyncTimeout = snap.Nodes().size() > 3 ? MASTERNODE_SYNC_TICK_SECONDS : MASTERNODE_SYNC_TIMEOUT_SECONDS; - if (fReachedBestHeader && (GetTime() - nTimeLastBumped > nTimeSyncTimeout)) { - // At this point we know that: - // a) there are peers (because we are looping on at least one of them); - // b) we waited for at least MASTERNODE_SYNC_TICK_SECONDS/MASTERNODE_SYNC_TIMEOUT_SECONDS - // (depending on the number of connected peers) since we reached the headers tip the last - // time (i.e. since fReachedBestHeader has been set to true); - // c) there were no blocks (UpdatedBlockTip, NotifyHeaderTip) or headers (AcceptedBlockHeader) - // for at least MASTERNODE_SYNC_TICK_SECONDS/MASTERNODE_SYNC_TIMEOUT_SECONDS (depending on - // the number of connected peers). - // We must be at the tip already, let's move to the next asset. - SwitchToNextAsset(); - uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); - - if (gArgs.GetBoolArg("-syncmempool", DEFAULT_SYNC_MEMPOOL)) { - // Now that the blockchain is synced request the mempool from the connected outbound nodes if possible - for (auto pNodeTmp : snap.Nodes()) { - bool fRequestedEarlier = m_netfulfilledman.HasFulfilledRequest(pNodeTmp->addr, "mempool-sync"); - if (!pNodeTmp->IsInboundConn() && !fRequestedEarlier && !pNodeTmp->IsBlockRelayOnly()) { - m_netfulfilledman.AddFulfilledRequest(pNodeTmp->addr, "mempool-sync"); - connman.PushMessage(pNodeTmp, msgMaker.Make(NetMsgType::MEMPOOL)); - LogPrint(BCLog::MNSYNC, "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d -- syncing mempool from peer=%d\n", nTick, nCurrentAsset, pNodeTmp->GetId()); - } - } - } - } - } - - // GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS - - if(nCurrentAsset == MASTERNODE_SYNC_GOVERNANCE) { - if (!govman.IsValid()) { - SwitchToNextAsset(); - return; - } - LogPrint(BCLog::GOBJECT, "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d nTimeLastBumped %lld GetTime() %lld diff %lld\n", nTick, nCurrentAsset, nTimeLastBumped, GetTime(), GetTime() - nTimeLastBumped); - - // check for timeout first - if(GetTime() - nTimeLastBumped > MASTERNODE_SYNC_TIMEOUT_SECONDS) { - LogPrint(BCLog::MNSYNC, "CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d -- timeout\n", nTick, nCurrentAsset); - if(nTriedPeerCount == 0) { - LogPrintf("CMasternodeSync::ProcessTick -- WARNING: failed to sync %s\n", GetAssetName()); - // it's kind of ok to skip this for now, hopefully we'll catch up later? - } - SwitchToNextAsset(); - return; - } - - // only request obj sync once from each peer - if(m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) { - // will request votes on per-obj basis from each node in a separate loop below - // to avoid deadlocks here - continue; - } - m_netfulfilledman.AddFulfilledRequest(pnode->addr, "governance-sync"); - - nTriedPeerCount++; - - SendGovernanceSyncRequest(pnode); - - break; //this will cause each peer to get one request each six seconds for the various assets we need - } - } - } - - - if (nCurrentAsset != MASTERNODE_SYNC_GOVERNANCE) { - // looped through all nodes and not syncing governance yet/already, release them - return; - } - - // request votes on per-obj basis from each node - for (const auto& pnode : snap.Nodes()) { - if(!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) { - continue; // to early for this node - } - int nObjsLeftToAsk = govman.RequestGovernanceObjectVotes(*pnode, connman, peerman); - // check for data - if(nObjsLeftToAsk == 0) { - static int64_t nTimeNoObjectsLeft = 0; - static int nLastTick = 0; - static int nLastVotes = 0; - if(nTimeNoObjectsLeft == 0) { - // asked all objects for votes for the first time - nTimeNoObjectsLeft = GetTime(); - } - // make sure the condition below is checked only once per tick - if(nLastTick == nTick) continue; - if (GetTime() - nTimeNoObjectsLeft > MASTERNODE_SYNC_TIMEOUT_SECONDS && - govman.GetVoteCount() - nLastVotes < std::max(int(0.0001 * nLastVotes), MASTERNODE_SYNC_TICK_SECONDS)) { - // We already asked for all objects, waited for MASTERNODE_SYNC_TIMEOUT_SECONDS - // after that and less then 0.01% or MASTERNODE_SYNC_TICK_SECONDS - // (i.e. 1 per second) votes were received during the last tick. - // We can be pretty sure that we are done syncing. - LogPrintf("CMasternodeSync::ProcessTick -- nTick %d nCurrentAsset %d -- asked for all objects, nothing to do\n", nTick, MASTERNODE_SYNC_GOVERNANCE); - // reset nTimeNoObjectsLeft to be able to use the same condition on resync - nTimeNoObjectsLeft = 0; - SwitchToNextAsset(); - return; - } - nLastTick = nTick; - nLastVotes = govman.GetVoteCount(); - } - } -} - -void CMasternodeSync::SendGovernanceSyncRequest(CNode* pnode) const -{ - CNetMsgMaker msgMaker(pnode->GetCommonVersion()); - - CBloomFilter filter; - - connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256(), filter)); -} - void CMasternodeSync::AcceptedBlockHeader(const CBlockIndex *pindexNew) { LogPrint(BCLog::MNSYNC, "CMasternodeSync::AcceptedBlockHeader -- pindexNew->nHeight: %d\n", pindexNew->nHeight); @@ -365,10 +151,3 @@ void CMasternodeSync::UpdatedBlockTip(const CBlockIndex *pindexTip, const CBlock LogPrint(BCLog::MNSYNC, "CMasternodeSync::UpdatedBlockTip -- pindexNew->nHeight: %d pindexTip->nHeight: %d fInitialDownload=%d fReachedBestHeader=%d\n", pindexNew->nHeight, pindexTip->nHeight, fInitialDownload, fReachedBestHeader); } - -void CMasternodeSync::DoMaintenance(const PeerManager& peerman, const CGovernanceManager& govman) -{ - if (ShutdownRequested()) return; - - ProcessTick(peerman, govman); -} diff --git a/src/masternode/sync.h b/src/masternode/sync.h index 2af331c64e36..5bae8cdf9774 100644 --- a/src/masternode/sync.h +++ b/src/masternode/sync.h @@ -8,14 +8,10 @@ #include #include -class CConnman; class CBlockIndex; -class CDataStream; -class CGovernanceManager; -class CMasternodeSync; -class CNetFulfilledRequestManager; -class CNode; -class PeerManager; + +/** Default for -syncmempool */ +static const bool DEFAULT_SYNC_MEMPOOL = true; static constexpr int MASTERNODE_SYNC_BLOCKCHAIN = 1; static constexpr int MASTERNODE_SYNC_GOVERNANCE = 4; @@ -27,10 +23,18 @@ static constexpr int MASTERNODE_SYNC_TICK_SECONDS = 6; static constexpr int MASTERNODE_SYNC_TIMEOUT_SECONDS = 30; // our blocks are 2.5 minutes so 30 seconds should be fine static constexpr int MASTERNODE_SYNC_RESET_SECONDS = 900; // Reset fReachedBestHeader in CMasternodeSync::Reset if UpdateBlockTip hasn't been called for this seconds +class NodeSyncNotifier +{ +public: + virtual void SyncReset() = 0; + virtual void SyncFinished() = 0; + + virtual ~NodeSyncNotifier() = default; +}; + // // CMasternodeSync : Sync masternode assets in stages // - class CMasternodeSync { private: @@ -49,39 +53,34 @@ class CMasternodeSync /// Last time UpdateBlockTip has been called std::atomic nTimeLastUpdateBlockTip{0}; - CConnman& connman; - CNetFulfilledRequestManager& m_netfulfilledman; + std::unique_ptr m_sync_notifier; public: CMasternodeSync() = delete; CMasternodeSync(const CMasternodeSync&) = delete; CMasternodeSync& operator=(const CMasternodeSync&) = delete; - explicit CMasternodeSync(CConnman& _connman, CNetFulfilledRequestManager& netfulfilledman); + explicit CMasternodeSync(std::unique_ptr&& sync_notifier); ~CMasternodeSync(); - void SendGovernanceSyncRequest(CNode* pnode) const; - bool IsBlockchainSynced() const { return nCurrentAsset > MASTERNODE_SYNC_BLOCKCHAIN; } bool IsSynced() const { return nCurrentAsset == MASTERNODE_SYNC_FINISHED; } int GetAssetID() const { return nCurrentAsset; } int GetAttempt() const { return nTriedPeerCount; } + void BumpAttempt() { ++nTriedPeerCount; } void BumpAssetLastTime(const std::string& strFuncName); + int64_t GetLastBump() const { return nTimeLastBumped; } int64_t GetAssetStartTime() const { return nTimeAssetSyncStarted; } std::string GetAssetName() const; std::string GetSyncStatus() const; + bool IsReachedBestHeader() const { return fReachedBestHeader; } void Reset(bool fForce = false, bool fNotifyReset = true); void SwitchToNextAsset(); - void ProcessMessage(const CNode& peer, std::string_view msg_type, CDataStream& vRecv) const; - void ProcessTick(const PeerManager& peerman, const CGovernanceManager& govman); - void AcceptedBlockHeader(const CBlockIndex *pindexNew); void NotifyHeaderTip(const CBlockIndex *pindexNew, bool fInitialDownload); void UpdatedBlockTip(const CBlockIndex *pindexTip, const CBlockIndex *pindexNew, bool fInitialDownload); - - void DoMaintenance(const PeerManager& peerman, const CGovernanceManager& govman); }; #endif // BITCOIN_MASTERNODE_SYNC_H diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 5bbbe2e3c510..d69a44eeb4cb 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -644,6 +644,7 @@ class PeerManagerImpl final : public PeerManager void StartHandlers() override; void StopHandlers() override; void InterruptHandlers() override; + void ScheduleHandlers(CScheduler& scheduler) override; /** Implement PeerManagerInternal */ void PeerMisbehaving(const NodeId pnode, const int howmuch, const std::string& message = "") override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); @@ -652,6 +653,7 @@ class PeerManagerImpl final : public PeerManager void PeerRelayInvFiltered(const CInv& inv, const CTransaction& relatedTx) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void PeerRelayInvFiltered(const CInv& inv, const uint256& relatedTxHash) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); void PeerAskPeersForTransaction(const uint256& txid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + size_t PeerGetRequestedObjectCount(NodeId nodeid) const override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, ::cs_main); void PeerPostProcessMessage(MessageProcessingResult&& ret) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); private: @@ -1691,6 +1693,13 @@ void PeerManagerImpl::InterruptHandlers() } } +void PeerManagerImpl::ScheduleHandlers(CScheduler& scheduler) +{ + for (auto& handler : m_handlers) { + handler->Schedule(scheduler); + } +} + void PeerManagerImpl::UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { LOCK(cs_main); @@ -5437,8 +5446,6 @@ void PeerManagerImpl::ProcessMessage( m_active_ctx->shareman->ProcessMessage(pfrom, msg_type, vRecv); } PostProcessMessage(m_sporkman.ProcessMessage(pfrom, m_connman, msg_type, vRecv), pfrom.GetId()); - m_mn_sync.ProcessMessage(pfrom, msg_type, vRecv); - PostProcessMessage(m_govman.ProcessMessage(pfrom, m_connman, msg_type, vRecv), pfrom.GetId()); PostProcessMessage(CMNAuth::ProcessMessage(pfrom, peer->m_their_services, m_connman, m_mn_metaman, m_mn_activeman, m_mn_sync, m_dmnman->GetListAtChainTip(), msg_type, vRecv), pfrom.GetId()); PostProcessMessage(m_llmq_ctx->quorum_block_processor->ProcessMessage(pfrom, msg_type, vRecv), pfrom.GetId()); PostProcessMessage(m_llmq_ctx->qdkgsman->ProcessMessage(pfrom, is_masternode, msg_type, vRecv), pfrom.GetId()); @@ -6570,6 +6577,11 @@ void PeerManagerImpl::PeerAskPeersForTransaction(const uint256& txid) AskPeersForTransaction(txid); } +size_t PeerManagerImpl::PeerGetRequestedObjectCount(NodeId nodeid) const +{ + return GetRequestedObjectCount(nodeid); +} + void PeerManagerImpl::PeerPostProcessMessage(MessageProcessingResult&& ret) { PostProcessMessage(std::move(ret), /*node=*/-1); diff --git a/src/net_processing.h b/src/net_processing.h index f0ab54554686..a449ee2a625b 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -64,6 +64,7 @@ class PeerManagerInternal virtual void PeerRelayInvFiltered(const CInv& inv, const CTransaction& relatedTx) = 0; virtual void PeerRelayInvFiltered(const CInv& inv, const uint256& relatedTxHash) = 0; virtual void PeerAskPeersForTransaction(const uint256& txid) = 0; + virtual size_t PeerGetRequestedObjectCount(NodeId nodeid) const = 0; virtual void PeerPostProcessMessage(MessageProcessingResult&& ret) = 0; }; @@ -79,6 +80,7 @@ class NetHandler virtual void Start() {} virtual void Stop() {} virtual void Interrupt() {} + virtual void Schedule(CScheduler& scheduler) {} virtual void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv) {} protected: PeerManagerInternal* m_peer_manager; @@ -169,6 +171,7 @@ class PeerManager : public CValidationInterface, public NetEventsInterface, publ virtual void StartHandlers() = 0; virtual void StopHandlers() = 0; virtual void InterruptHandlers() = 0; + virtual void ScheduleHandlers(CScheduler& scheduler) = 0; }; #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 8812f7b64829..98f3ae44e15f 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -51,12 +51,9 @@ std::optional LoadChainstate(bool fReset, const fs::path& data_dir, bool fPruneMode, bool is_addrindex_enabled, - bool is_governance_enabled, bool is_spentindex_enabled, bool is_timeindex_enabled, - bool is_txindex_enabled, const Consensus::Params& consensus_params, - const std::string& network_id, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, @@ -111,12 +108,6 @@ std::optional LoadChainstate(bool fReset, return ChainstateLoadingError::ERROR_LOADING_BLOCK_DB; } - // TODO: Remove this when pruning is fixed. - // See https://github.com/dashpay/dash/pull/1817 and https://github.com/dashpay/dash/pull/1743 - if (is_governance_enabled && !is_txindex_enabled && network_id != CBaseChainParams::REGTEST) { - return ChainstateLoadingError::ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED; - } - if (!chainman.BlockIndex().empty() && !chainman.m_blockman.LookupBlockIndex(consensus_params.hashGenesisBlock)) { return ChainstateLoadingError::ERROR_BAD_GENESIS_BLOCK; diff --git a/src/node/chainstate.h b/src/node/chainstate.h index aba1ccaa5fc6..c8d321b89931 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -37,7 +37,6 @@ enum class ChainstateLoadingError { ERROR_LOADING_BLOCK_DB, ERROR_BAD_GENESIS_BLOCK, ERROR_BAD_DEVNET_GENESIS_BLOCK, - ERROR_TXINDEX_DISABLED_WHEN_GOV_ENABLED, ERROR_ADDRIDX_NEEDS_REINDEX, ERROR_SPENTIDX_NEEDS_REINDEX, ERROR_TIMEIDX_NEEDS_REINDEX, @@ -96,12 +95,9 @@ std::optional LoadChainstate(bool fReset, const fs::path& data_dir, bool fPruneMode, bool is_addrindex_enabled, - bool is_governance_enabled, bool is_spentindex_enabled, bool is_timeindex_enabled, - bool is_txindex_enabled, const Consensus::Params& consensus_params, - const std::string& network_id, bool fReindexChainState, int64_t nBlockTreeDBCache, int64_t nCoinDBCache, diff --git a/src/node/sync_manager.cpp b/src/node/sync_manager.cpp new file mode 100644 index 000000000000..fb368f03e5df --- /dev/null +++ b/src/node/sync_manager.cpp @@ -0,0 +1,350 @@ +// Copyright (c) 2024-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 + +class CConnman; + +void SyncManager::Schedule(CScheduler& scheduler) +{ + scheduler.scheduleEvery( + [this]() -> void { + if (ShutdownRequested()) return; + ProcessTick(); + }, + std::chrono::seconds{1}); +} + +void SyncManager::SendGovernanceSyncRequest(CNode* pnode) const +{ + CNetMsgMaker msgMaker(pnode->GetCommonVersion()); + CBloomFilter filter; + m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256(), filter)); +} + +int SyncManager::RequestGovernanceObjectVotes(const std::vector& vNodesCopy) const +{ + // Maximum number of nodes to request votes from for the same object hash on real networks + // (mainnet, testnet, devnets). Keep this low to avoid unnecessary bandwidth usage. + static constexpr size_t REALNET_PEERS_PER_HASH{3}; + // Maximum number of nodes to request votes from for the same object hash on regtest. + // During testing, nodes are isolated to create conflicting triggers. Using the real + // networks limit of 3 nodes often results in querying only "non-isolated" nodes, missing the + // isolated ones we need to test. This high limit ensures all available nodes are queried. + static constexpr size_t REGTEST_PEERS_PER_HASH{std::numeric_limits::max()}; + + if (vNodesCopy.empty()) return -1; + + int64_t nNow = GetTime(); + int nTimeout = 60 * 60; + size_t nPeersPerHashMax = Params().IsMockableChain() ? REGTEST_PEERS_PER_HASH : REALNET_PEERS_PER_HASH; + + + // This should help us to get some idea about an impact this can bring once deployed on mainnet. + // Testnet is ~40 times smaller in masternode count, but only ~1000 masternodes usually vote, + // so 1 obj on mainnet == ~10 objs or ~1000 votes on testnet. However we want to test a higher + // number of votes to make sure it's robust enough, so aim at 2000 votes per masternode per request. + // On mainnet nMaxObjRequestsPerNode is always set to 1. + int nMaxObjRequestsPerNode = 1; + size_t nProjectedVotes = 2000; + if (Params().NetworkIDString() != CBaseChainParams::MAIN) { + nMaxObjRequestsPerNode = + std::max(1, int(nProjectedVotes / + std::max(1, (int)m_gov_manager.GetMNManager().GetListAtChainTip().GetValidMNsCount()))); + } + + static Mutex cs_recently; + static std::map> mapAskedRecently GUARDED_BY(cs_recently); + LOCK(cs_recently); + + auto [vTriggerObjHashes, vOtherObjHashes] = m_gov_manager.FetchGovernanceObjectVotes(nMaxObjRequestsPerNode, nNow, + mapAskedRecently); + + if (vTriggerObjHashes.empty() && vOtherObjHashes.empty()) return -2; + + LogPrint(BCLog::GOBJECT, "%s -- start: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", __func__, + vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size()); + + Shuffle(vTriggerObjHashes.begin(), vTriggerObjHashes.end(), FastRandomContext()); + Shuffle(vOtherObjHashes.begin(), vOtherObjHashes.end(), FastRandomContext()); + + for (int i = 0; i < nMaxObjRequestsPerNode; ++i) { + uint256 nHashGovobj; + + // ask for triggers first + if (!vTriggerObjHashes.empty()) { + nHashGovobj = vTriggerObjHashes.back(); + } else { + if (vOtherObjHashes.empty()) break; + nHashGovobj = vOtherObjHashes.back(); + } + bool fAsked = false; + for (const auto& pnode : vNodesCopy) { + // Don't try to sync any data from outbound non-relay "masternode" connections. + // Inbound connection this early is most likely a "masternode" connection + // initiated from another node, so skip it too. + if (!pnode->CanRelay() || (m_connman.IsActiveMasternode() && pnode->IsInboundConn())) continue; + // stop early to prevent setAskFor overflow + { + LOCK(::cs_main); + size_t nProjectedSize = m_peer_manager->PeerGetRequestedObjectCount(pnode->GetId()) + nProjectedVotes; + if (nProjectedSize > MAX_INV_SZ) continue; + } + // to early to ask the same node + if (mapAskedRecently[nHashGovobj].count(pnode->addr)) continue; + + m_gov_manager.RequestGovernanceObject(pnode, nHashGovobj, m_connman, true); + mapAskedRecently[nHashGovobj][pnode->addr] = nNow + nTimeout; + fAsked = true; + // stop loop if max number of peers per obj was asked + if (mapAskedRecently[nHashGovobj].size() >= nPeersPerHashMax) break; + } + // NOTE: this should match `if` above (the one before `while`) + if (!vTriggerObjHashes.empty()) { + vTriggerObjHashes.pop_back(); + } else { + vOtherObjHashes.pop_back(); + } + if (!fAsked) i--; + } + LogPrint(BCLog::GOBJECT, "%s -- end: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", __func__, + vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size()); + + return int(vTriggerObjHashes.size() + vOtherObjHashes.size()); +} + +void SyncManager::ProcessTick() +{ + assert(m_netfulfilledman.IsValid()); + + static int nTick = 0; + nTick++; + + const static int64_t nSyncStart = TicksSinceEpoch(SystemClock::now()); + const static std::string strAllow = strprintf("allow-sync-%lld", nSyncStart); + + // reset the sync process if the last call to this function was more than 60 minutes ago (client was in sleep mode) + static int64_t nTimeLastProcess = GetTime(); + if (!Params().IsMockableChain() && GetTime() - nTimeLastProcess > 60 * 60 && !m_connman.IsActiveMasternode()) { + LogPrintf("Sync Tick -- WARNING: no actions for too long, restarting sync...\n"); + m_node_sync.Reset(true); + nTimeLastProcess = GetTime(); + return; + } + + if (GetTime() - nTimeLastProcess < MASTERNODE_SYNC_TICK_SECONDS) { + // too early, nothing to do here + return; + } + + nTimeLastProcess = GetTime(); + const CConnman::NodesSnapshot snap{m_connman, /* cond = */ CConnman::FullyConnectedOnly}; + + // gradually request the rest of the votes after sync finished + if (m_node_sync.IsSynced()) { + RequestGovernanceObjectVotes(snap.Nodes()); + return; + } + + // Calculate "progress" for LOG reporting / GUI notification + int attempt = m_node_sync.GetAttempt(); + int asset_id = m_node_sync.GetAssetID(); + double nSyncProgress = double(attempt + (asset_id - 1) * 8) / (8 * 4); + LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d nTriedPeerCount %d nSyncProgress %f\n", nTick, asset_id, + attempt, nSyncProgress); + uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); + + // TODO: move switch-case out from this loop; logic & nodes code to be separated + for (auto& pnode : snap.Nodes()) { + CNetMsgMaker msgMaker(pnode->GetCommonVersion()); + + // Don't try to sync any data from outbound non-relay "masternode" connections. + // Inbound connection this early is most likely a "masternode" connection + // initiated from another node, so skip it too. + if (!pnode->CanRelay() || (m_connman.IsActiveMasternode() && pnode->IsInboundConn())) continue; + + { + if ((pnode->HasPermission(NetPermissionFlags::NoBan) || pnode->IsManualConn()) && + !m_netfulfilledman.HasFulfilledRequest(pnode->addr, strAllow)) { + m_netfulfilledman.RemoveAllFulfilledRequests(pnode->addr); + m_netfulfilledman.AddFulfilledRequest(pnode->addr, strAllow); + LogPrintf("Sync Tick -- skipping mnsync restrictions for peer=%d\n", pnode->GetId()); + } + + if (m_netfulfilledman.HasFulfilledRequest(pnode->addr, "full-sync")) { + // We already fully synced from this node recently, + // disconnect to free this connection slot for another peer. + pnode->fDisconnect = true; + LogPrintf("Sync Tick -- disconnecting from recently synced peer=%d\n", pnode->GetId()); + continue; + } + + // SPORK : ALWAYS ASK FOR SPORKS AS WE SYNC + + if (!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "spork-sync")) { + // always get sporks first, only request once from each peer + m_netfulfilledman.AddFulfilledRequest(pnode->addr, "spork-sync"); + // get current network sporks + m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS)); + LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d -- requesting sporks from peer=%d\n", nTick, + asset_id, pnode->GetId()); + } + + if (asset_id == MASTERNODE_SYNC_BLOCKCHAIN) { + int64_t nTimeSyncTimeout = snap.Nodes().size() > 3 ? MASTERNODE_SYNC_TICK_SECONDS + : MASTERNODE_SYNC_TIMEOUT_SECONDS; + if (m_node_sync.IsReachedBestHeader() && (GetTime() - m_node_sync.GetLastBump() > nTimeSyncTimeout)) { + // At this point we know that: + // a) there are peers (because we are looping on at least one of them); + // b) we waited for at least MASTERNODE_SYNC_TICK_SECONDS/MASTERNODE_SYNC_TIMEOUT_SECONDS + // (depending on the number of connected peers) since we reached the headers tip the last + // time (i.e. since fReachedBestHeader has been set to true); + // c) there were no blocks (UpdatedBlockTip, NotifyHeaderTip) or headers (AcceptedBlockHeader) + // for at least MASTERNODE_SYNC_TICK_SECONDS/MASTERNODE_SYNC_TIMEOUT_SECONDS (depending on + // the number of connected peers). + // We must be at the tip already, let's move to the next asset. + m_node_sync.SwitchToNextAsset(); + uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); + + if (gArgs.GetBoolArg("-syncmempool", DEFAULT_SYNC_MEMPOOL)) { + // Now that the blockchain is synced request the mempool from the connected outbound nodes if possible + for (auto pNodeTmp : snap.Nodes()) { + bool fRequestedEarlier = m_netfulfilledman.HasFulfilledRequest(pNodeTmp->addr, + "mempool-sync"); + if (!pNodeTmp->IsInboundConn() && !fRequestedEarlier && !pNodeTmp->IsBlockRelayOnly()) { + m_netfulfilledman.AddFulfilledRequest(pNodeTmp->addr, "mempool-sync"); + m_connman.PushMessage(pNodeTmp, msgMaker.Make(NetMsgType::MEMPOOL)); + LogPrint(BCLog::MNSYNC, /* Continued */ + "Sync Tick -- nTick %d asset_id %d -- syncing mempool from peer=%d\n", nTick, + asset_id, pNodeTmp->GetId()); + } + } + } + } + } + + // GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS + + if (asset_id == MASTERNODE_SYNC_GOVERNANCE) { + if (!m_gov_manager.IsValid()) { + m_node_sync.SwitchToNextAsset(); + return; + } + LogPrint(BCLog::GOBJECT, "Sync Tick -- nTick %d asset_id %d last_bump %lld GetTime() %lld diff %lld\n", + nTick, asset_id, m_node_sync.GetLastBump(), GetTime(), GetTime() - m_node_sync.GetLastBump()); + + // check for timeout first + if (GetTime() - m_node_sync.GetLastBump() > MASTERNODE_SYNC_TIMEOUT_SECONDS) { + LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d -- timeout\n", nTick, asset_id); + if (attempt == 0) { + LogPrintf("Sync Tick -- WARNING: failed to sync %s\n", m_node_sync.GetAssetName()); + // it's kind of ok to skip this for now, hopefully we'll catch up later? + } + m_node_sync.SwitchToNextAsset(); + return; + } + + // only request obj sync once from each peer + if (m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) { + // will request votes on per-obj basis from each node in a separate loop below + // to avoid deadlocks here + continue; + } + m_netfulfilledman.AddFulfilledRequest(pnode->addr, "governance-sync"); + + m_node_sync.BumpAttempt(); + + SendGovernanceSyncRequest(pnode); + + break; // this will cause each peer to get one request each six seconds for the various assets we need + } + } + } + + + if (asset_id != MASTERNODE_SYNC_GOVERNANCE) { + // looped through all nodes and not syncing governance yet/already, release them + return; + } + + // request votes on per-obj basis from each node + for (const auto& pnode : snap.Nodes()) { + if (!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) { + continue; // to early for this node + } + const std::vector vNodeCopy{pnode}; + int nObjsLeftToAsk = RequestGovernanceObjectVotes(vNodeCopy); + // check for data + if (nObjsLeftToAsk == 0) { + static int64_t nTimeNoObjectsLeft = 0; + static int nLastTick = 0; + static int nLastVotes = 0; + if (nTimeNoObjectsLeft == 0) { + // asked all objects for votes for the first time + nTimeNoObjectsLeft = GetTime(); + } + // make sure the condition below is checked only once per tick + if (nLastTick == nTick) continue; + if (GetTime() - nTimeNoObjectsLeft > MASTERNODE_SYNC_TIMEOUT_SECONDS && + m_gov_manager.GetVoteCount() - nLastVotes < + std::max(int(0.0001 * nLastVotes), MASTERNODE_SYNC_TICK_SECONDS)) { + // We already asked for all objects, waited for MASTERNODE_SYNC_TIMEOUT_SECONDS + // after that and less then 0.01% or MASTERNODE_SYNC_TICK_SECONDS + // (i.e. 1 per second) votes were received during the last tick. + // We can be pretty sure that we are done syncing. + LogPrintf("Sync Tick -- nTick %d asset_id %d -- asked for all objects, nothing to do\n", nTick, + MASTERNODE_SYNC_GOVERNANCE); + // reset nTimeNoObjectsLeft to be able to use the same condition on resync + nTimeNoObjectsLeft = 0; + m_node_sync.SwitchToNextAsset(); + return; + } + nLastTick = nTick; + nLastVotes = m_gov_manager.GetVoteCount(); + } + } +} + +void SyncManager::ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv) +{ + //Sync status count + if (msg_type != NetMsgType::SYNCSTATUSCOUNT) return; + + //do not care about stats if sync process finished + if (m_node_sync.IsSynced()) return; + + int nItemID; + int nCount; + vRecv >> nItemID >> nCount; + + LogPrint(BCLog::MNSYNC, "SYNCSTATUSCOUNT -- got inventory count: nItemID=%d nCount=%d peer=%d\n", nItemID, nCount, peer.GetId()); +} + +void NodeSyncNotifierImpl::SyncReset() +{ + uiInterface.NotifyAdditionalDataSyncProgressChanged(-1); +} + +void NodeSyncNotifierImpl::SyncFinished() +{ + assert(m_netfulfilledman.IsValid()); + + uiInterface.NotifyAdditionalDataSyncProgressChanged(1); + m_connman.ForEachNode(CConnman::AllNodes, [this](const CNode* pnode) { + m_netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync"); + }); +} diff --git a/src/node/sync_manager.h b/src/node/sync_manager.h new file mode 100644 index 000000000000..ac6d5fe75ec3 --- /dev/null +++ b/src/node/sync_manager.h @@ -0,0 +1,55 @@ +// Copyright (c) 2014-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_NODE_SYNC_MANAGER_H +#define BITCOIN_NODE_SYNC_MANAGER_H + +#include +#include + +class CGovernanceManager; +class CMasternodeSync; +class CNetFulfilledRequestManager; + +class SyncManager final : public NetHandler +{ +public: + SyncManager(PeerManagerInternal* peer_manager, CGovernanceManager& gov_manager, CMasternodeSync& node_sync, CConnman& connman, + CNetFulfilledRequestManager& netfulfilledman) : + NetHandler(peer_manager), + m_gov_manager(gov_manager), + m_node_sync(node_sync), + m_connman(connman), + m_netfulfilledman(netfulfilledman) + { + } + void Schedule(CScheduler& scheduler) override; + void ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv) override; + +private: + void SendGovernanceSyncRequest(CNode* pnode) const; + int RequestGovernanceObjectVotes(const std::vector& vNodesCopy) const; + void ProcessTick(); + + CGovernanceManager& m_gov_manager; + CMasternodeSync& m_node_sync; + CConnman& m_connman; + CNetFulfilledRequestManager& m_netfulfilledman; +}; + +class NodeSyncNotifierImpl : public NodeSyncNotifier +{ +public: + NodeSyncNotifierImpl(CConnman& connman, CNetFulfilledRequestManager& netfulfilledman) : + m_connman(connman), + m_netfulfilledman(netfulfilledman) + {} + + void SyncReset() override; + void SyncFinished() override; +private: + CConnman& m_connman; + CNetFulfilledRequestManager& m_netfulfilledman; +}; +#endif // BITCOIN_NODE_SYNC_MANAGER_H diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 5d657d1c4d15..821fb4a9cb48 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -273,7 +274,7 @@ ChainTestingSetup::ChainTestingSetup(const std::string& chainName, const std::ve m_node.chainman = std::make_unique(); m_node.chainman->m_blockman.m_block_tree_db = std::make_unique(m_cache_sizes.block_tree_db, true); - m_node.mn_sync = std::make_unique(*m_node.connman, *m_node.netfulfilledman); + m_node.mn_sync = std::make_unique(std::make_unique(*m_node.connman, *m_node.netfulfilledman)); m_node.govman = std::make_unique(*m_node.mn_metaman, *m_node.netfulfilledman, *m_node.chainman, m_node.dmnman, *m_node.mn_sync); // Start script-checking threads. Set g_parallel_script_checks to true so they are used. @@ -321,12 +322,9 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vectorGetDataDirNet(), fPruneMode, m_args.GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX), - !m_args.GetBoolArg("-disablegovernance", !DEFAULT_GOVERNANCE_ENABLE), m_args.GetBoolArg("-spentindex", DEFAULT_SPENTINDEX), m_args.GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX), - m_args.GetBoolArg("-txindex", DEFAULT_TXINDEX), chainparams.GetConsensus(), - chainparams.NetworkIDString(), m_args.GetBoolArg("-reindex-chainstate", false), m_cache_sizes.block_tree_db, m_cache_sizes.coins_db, diff --git a/src/validation.h b/src/validation.h index 290f721280c9..6d78bedc6207 100644 --- a/src/validation.h +++ b/src/validation.h @@ -81,8 +81,6 @@ static const bool DEFAULT_CHECKPOINTS_ENABLED = true; /** Default for -persistmempool */ static const bool DEFAULT_PERSIST_MEMPOOL = true; -/** Default for -syncmempool */ -static const bool DEFAULT_SYNC_MEMPOOL = true; /** Default for -stopatheight */ static const int DEFAULT_STOPATHEIGHT = 0; diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 87a6f11e6fcd..03cae3d37b63 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -48,9 +48,6 @@ "evo/specialtxman -> validation -> evo/specialtxman", "governance/classes -> governance/object -> governance/governance -> governance/classes", "governance/governance -> governance/signing -> governance/object -> governance/governance", - "governance/governance -> masternode/sync -> governance/governance", - "governance/governance -> net_processing -> masternode/active/context -> governance/governance", - "governance/governance -> net_processing -> governance/governance", "instantsend/instantsend -> instantsend/signing -> llmq/signing_shares -> net_processing -> instantsend/instantsend", "instantsend/instantsend -> instantsend/signing -> llmq/signing_shares -> net_processing -> llmq/context -> instantsend/instantsend", "instantsend/instantsend -> instantsend/signing -> llmq/signing_shares -> net_processing -> masternode/active/context -> instantsend/instantsend",