LCOV - code coverage report
Current view: top level - src/node - sync_manager.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 3 184 1.6 %
Date: 2026-06-25 07:23:51 Functions: 1 10 10.0 %

          Line data    Source code
       1             : // Copyright (c) 2024-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <node/sync_manager.h>
       6             : 
       7             : #include <chainparams.h>
       8             : #include <evo/deterministicmns.h>
       9             : #include <governance/governance.h>
      10             : #include <logging.h>
      11             : #include <masternode/sync.h>
      12             : #include <net.h>
      13             : #include <netfulfilledman.h>
      14             : #include <netmessagemaker.h>
      15             : #include <node/interface_ui.h>
      16             : #include <random.h>
      17             : #include <scheduler.h>
      18             : #include <shutdown.h>
      19             : 
      20             : class CConnman;
      21             : 
      22           0 : void SyncManager::Schedule(CScheduler& scheduler)
      23             : {
      24           0 :     scheduler.scheduleEvery(
      25           0 :         [this]() -> void {
      26           0 :             if (ShutdownRequested()) return;
      27           0 :             ProcessTick();
      28           0 :         },
      29           0 :         std::chrono::seconds{1});
      30           0 : }
      31             : 
      32           0 : void SyncManager::SendGovernanceSyncRequest(CNode* pnode) const
      33             : {
      34           0 :     CNetMsgMaker msgMaker(pnode->GetCommonVersion());
      35           0 :     CBloomFilter filter;
      36           0 :     m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256(), filter));
      37           0 : }
      38             : 
      39           0 : void SyncManager::SendGovernanceObjectSyncRequest(CNode* pnode, const uint256& nHash, bool fUseFilter) const
      40             : {
      41           0 :     if (!pnode) return;
      42             : 
      43           0 :     LogPrint(BCLog::GOBJECT, "SyncManager::%s -- nHash %s peer=%d\n", __func__, nHash.ToString(), pnode->GetId());
      44             : 
      45           0 :     CBloomFilter filter = fUseFilter ? m_gov_manager.GetVoteBloomFilter(nHash) : CBloomFilter{};
      46             : 
      47           0 :     CNetMsgMaker msgMaker(pnode->GetCommonVersion());
      48           0 :     m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, nHash, filter));
      49           0 : }
      50             : 
      51           0 : int SyncManager::RequestGovernanceObjectVotes(const std::vector<CNode*>& vNodesCopy) const
      52             : {
      53             :     // Maximum number of nodes to request votes from for the same object hash on real networks
      54             :     // (mainnet, testnet, devnets). Keep this low to avoid unnecessary bandwidth usage.
      55             :     static constexpr size_t REALNET_PEERS_PER_HASH{3};
      56             :     // Maximum number of nodes to request votes from for the same object hash on regtest.
      57             :     // During testing, nodes are isolated to create conflicting triggers. Using the real
      58             :     // networks limit of 3 nodes often results in querying only "non-isolated" nodes, missing the
      59             :     // isolated ones we need to test. This high limit ensures all available nodes are queried.
      60             :     static constexpr size_t REGTEST_PEERS_PER_HASH{std::numeric_limits<size_t>::max()};
      61             : 
      62           0 :     if (vNodesCopy.empty()) return -1;
      63             : 
      64           0 :     int64_t nNow = GetTime();
      65           0 :     int nTimeout = 60 * 60;
      66           0 :     size_t nPeersPerHashMax = Params().IsMockableChain() ? REGTEST_PEERS_PER_HASH : REALNET_PEERS_PER_HASH;
      67             : 
      68             : 
      69             :     // This should help us to get some idea about an impact this can bring once deployed on mainnet.
      70             :     // Testnet is ~40 times smaller in masternode count, but only ~1000 masternodes usually vote,
      71             :     // so 1 obj on mainnet == ~10 objs or ~1000 votes on testnet. However we want to test a higher
      72             :     // number of votes to make sure it's robust enough, so aim at 2000 votes per masternode per request.
      73             :     // On mainnet nMaxObjRequestsPerNode is always set to 1.
      74           0 :     int nMaxObjRequestsPerNode = 1;
      75           0 :     size_t nProjectedVotes = 2000;
      76           0 :     if (Params().NetworkIDString() != CBaseChainParams::MAIN) {
      77           0 :         nMaxObjRequestsPerNode =
      78           0 :             std::max(1, int(nProjectedVotes /
      79           0 :                             std::max(1, (int)m_gov_manager.GetMNManager().GetListAtChainTip().GetCounts().enabled())));
      80           0 :     }
      81             : 
      82           0 :     static Mutex cs_recently;
      83           0 :     static std::map<uint256, std::map<CService, int64_t>> mapAskedRecently GUARDED_BY(cs_recently);
      84           0 :     LOCK(cs_recently);
      85             : 
      86           0 :     auto [vTriggerObjHashes, vOtherObjHashes] = m_gov_manager.FetchGovernanceObjectVotes(nMaxObjRequestsPerNode, nNow,
      87             :                                                                                          mapAskedRecently);
      88             : 
      89           0 :     if (vTriggerObjHashes.empty() && vOtherObjHashes.empty()) return -2;
      90             : 
      91           0 :     LogPrint(BCLog::GOBJECT, "%s -- start: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", __func__,
      92             :              vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size());
      93             : 
      94           0 :     Shuffle(vTriggerObjHashes.begin(), vTriggerObjHashes.end(), FastRandomContext());
      95           0 :     Shuffle(vOtherObjHashes.begin(), vOtherObjHashes.end(), FastRandomContext());
      96             : 
      97           0 :     for (int i = 0; i < nMaxObjRequestsPerNode; ++i) {
      98           0 :         uint256 nHashGovobj;
      99             : 
     100             :         // ask for triggers first
     101           0 :         if (!vTriggerObjHashes.empty()) {
     102           0 :             nHashGovobj = vTriggerObjHashes.back();
     103           0 :         } else {
     104           0 :             if (vOtherObjHashes.empty()) break;
     105           0 :             nHashGovobj = vOtherObjHashes.back();
     106             :         }
     107           0 :         bool fAsked = false;
     108           0 :         for (const auto& pnode : vNodesCopy) {
     109             :             // Don't try to sync any data from outbound non-relay "masternode" connections.
     110             :             // Inbound connection this early is most likely a "masternode" connection
     111             :             // initiated from another node, so skip it too.
     112           0 :             if (!pnode->CanRelay() || (m_connman.IsActiveMasternode() && pnode->IsInboundConn())) continue;
     113             :             // stop early to prevent setAskFor overflow
     114             :             {
     115           0 :                 LOCK(::cs_main);
     116           0 :                 size_t nProjectedSize = m_peer_manager->PeerGetRequestedObjectCount(pnode->GetId()) + nProjectedVotes;
     117           0 :                 if (nProjectedSize > MAX_INV_SZ) continue;
     118           0 :             }
     119             :             // to early to ask the same node
     120           0 :             if (mapAskedRecently[nHashGovobj].count(pnode->addr)) continue;
     121             : 
     122           0 :             SendGovernanceObjectSyncRequest(pnode, nHashGovobj, true);
     123           0 :             mapAskedRecently[nHashGovobj][pnode->addr] = nNow + nTimeout;
     124           0 :             fAsked = true;
     125             :             // stop loop if max number of peers per obj was asked
     126           0 :             if (mapAskedRecently[nHashGovobj].size() >= nPeersPerHashMax) break;
     127             :         }
     128             :         // NOTE: this should match `if` above (the one before `while`)
     129           0 :         if (!vTriggerObjHashes.empty()) {
     130           0 :             vTriggerObjHashes.pop_back();
     131           0 :         } else {
     132           0 :             vOtherObjHashes.pop_back();
     133             :         }
     134           0 :         if (!fAsked) i--;
     135           0 :     }
     136           0 :     LogPrint(BCLog::GOBJECT, "%s -- end: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", __func__,
     137             :              vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size());
     138             : 
     139           0 :     return int(vTriggerObjHashes.size() + vOtherObjHashes.size());
     140           0 : }
     141             : 
     142           0 : void SyncManager::ProcessTick()
     143             : {
     144           0 :     assert(m_netfulfilledman.IsValid());
     145             : 
     146             :     static int nTick = 0;
     147           0 :     nTick++;
     148             : 
     149           0 :     const static int64_t nSyncStart = TicksSinceEpoch<std::chrono::milliseconds>(SystemClock::now());
     150           0 :     const static std::string strAllow = strprintf("allow-sync-%lld", nSyncStart);
     151             : 
     152             :     // reset the sync process if the last call to this function was more than 60 minutes ago (client was in sleep mode)
     153           0 :     static int64_t nTimeLastProcess = GetTime();
     154           0 :     if (!Params().IsMockableChain() && GetTime() - nTimeLastProcess > 60 * 60 && !m_connman.IsActiveMasternode()) {
     155           0 :         LogPrintf("Sync Tick -- WARNING: no actions for too long, restarting sync...\n");
     156           0 :         m_node_sync.Reset(true);
     157           0 :         nTimeLastProcess = GetTime();
     158           0 :         return;
     159             :     }
     160             : 
     161           0 :     if (GetTime() - nTimeLastProcess < MASTERNODE_SYNC_TICK_SECONDS) {
     162             :         // too early, nothing to do here
     163           0 :         return;
     164             :     }
     165             : 
     166           0 :     nTimeLastProcess = GetTime();
     167           0 :     const CConnman::NodesSnapshot snap{m_connman, /* cond = */ CConnman::FullyConnectedOnly};
     168             : 
     169             :     // gradually request the rest of the votes after sync finished
     170           0 :     if (m_node_sync.IsSynced()) {
     171           0 :         RequestGovernanceObjectVotes(snap.Nodes());
     172           0 :         return;
     173             :     }
     174             : 
     175             :     // Calculate "progress" for LOG reporting / GUI notification
     176           0 :     int attempt = m_node_sync.GetAttempt();
     177           0 :     int asset_id = m_node_sync.GetAssetID();
     178           0 :     double nSyncProgress = double(attempt + (asset_id - 1) * 8) / (8 * 4);
     179           0 :     LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d nTriedPeerCount %d nSyncProgress %f\n", nTick, asset_id,
     180             :              attempt, nSyncProgress);
     181           0 :     uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
     182             : 
     183             :     // TODO: move switch-case out from this loop; logic & nodes code to be separated
     184           0 :     for (auto& pnode : snap.Nodes()) {
     185           0 :         CNetMsgMaker msgMaker(pnode->GetCommonVersion());
     186             : 
     187             :         // Don't try to sync any data from outbound non-relay "masternode" connections.
     188             :         // Inbound connection this early is most likely a "masternode" connection
     189             :         // initiated from another node, so skip it too.
     190           0 :         if (!pnode->CanRelay() || (m_connman.IsActiveMasternode() && pnode->IsInboundConn())) continue;
     191             : 
     192             :         {
     193           0 :             if ((pnode->HasPermission(NetPermissionFlags::NoBan) || pnode->IsManualConn()) &&
     194           0 :                 !m_netfulfilledman.HasFulfilledRequest(pnode->addr, strAllow)) {
     195           0 :                 m_netfulfilledman.RemoveAllFulfilledRequests(pnode->addr);
     196           0 :                 m_netfulfilledman.AddFulfilledRequest(pnode->addr, strAllow);
     197           0 :                 LogPrintf("Sync Tick -- skipping mnsync restrictions for peer=%d\n", pnode->GetId());
     198           0 :             }
     199             : 
     200           0 :             if (m_netfulfilledman.HasFulfilledRequest(pnode->addr, "full-sync")) {
     201             :                 // We already fully synced from this node recently,
     202             :                 // disconnect to free this connection slot for another peer.
     203           0 :                 pnode->fDisconnect = true;
     204           0 :                 LogPrintf("Sync Tick -- disconnecting from recently synced peer=%d\n", pnode->GetId());
     205           0 :                 continue;
     206             :             }
     207             : 
     208             :             // SPORK : ALWAYS ASK FOR SPORKS AS WE SYNC
     209             : 
     210           0 :             if (!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "spork-sync")) {
     211             :                 // always get sporks first, only request once from each peer
     212           0 :                 m_netfulfilledman.AddFulfilledRequest(pnode->addr, "spork-sync");
     213             :                 // get current network sporks
     214           0 :                 m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS));
     215           0 :                 LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d -- requesting sporks from peer=%d\n", nTick,
     216             :                          asset_id, pnode->GetId());
     217           0 :             }
     218             : 
     219           0 :             if (asset_id == MASTERNODE_SYNC_BLOCKCHAIN) {
     220           0 :                 int64_t nTimeSyncTimeout = snap.Nodes().size() > 3 ? MASTERNODE_SYNC_TICK_SECONDS
     221             :                                                                    : MASTERNODE_SYNC_TIMEOUT_SECONDS;
     222           0 :                 if (m_node_sync.IsReachedBestHeader() && (GetTime() - m_node_sync.GetLastBump() > nTimeSyncTimeout)) {
     223             :                     // At this point we know that:
     224             :                     // a) there are peers (because we are looping on at least one of them);
     225             :                     // b) we waited for at least MASTERNODE_SYNC_TICK_SECONDS/MASTERNODE_SYNC_TIMEOUT_SECONDS
     226             :                     //    (depending on the number of connected peers) since we reached the headers tip the last
     227             :                     //    time (i.e. since fReachedBestHeader has been set to true);
     228             :                     // c) there were no blocks (UpdatedBlockTip, NotifyHeaderTip) or headers (AcceptedBlockHeader)
     229             :                     //    for at least MASTERNODE_SYNC_TICK_SECONDS/MASTERNODE_SYNC_TIMEOUT_SECONDS (depending on
     230             :                     //    the number of connected peers).
     231             :                     // We must be at the tip already, let's move to the next asset.
     232           0 :                     m_node_sync.SwitchToNextAsset();
     233           0 :                     uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
     234             : 
     235           0 :                     if (gArgs.GetBoolArg("-syncmempool", DEFAULT_SYNC_MEMPOOL)) {
     236             :                         // Now that the blockchain is synced request the mempool from the connected outbound nodes if possible
     237           0 :                         for (auto pNodeTmp : snap.Nodes()) {
     238           0 :                             bool fRequestedEarlier = m_netfulfilledman.HasFulfilledRequest(pNodeTmp->addr,
     239           0 :                                                                                            "mempool-sync");
     240           0 :                             if (!pNodeTmp->IsInboundConn() && !fRequestedEarlier && !pNodeTmp->IsBlockRelayOnly()) {
     241           0 :                                 m_netfulfilledman.AddFulfilledRequest(pNodeTmp->addr, "mempool-sync");
     242           0 :                                 m_connman.PushMessage(pNodeTmp, msgMaker.Make(NetMsgType::MEMPOOL));
     243           0 :                                 LogPrint(BCLog::MNSYNC, /* Continued */
     244             :                                          "Sync Tick -- nTick %d asset_id %d -- syncing mempool from peer=%d\n", nTick,
     245             :                                          asset_id, pNodeTmp->GetId());
     246           0 :                             }
     247             :                         }
     248           0 :                     }
     249           0 :                 }
     250           0 :             }
     251             : 
     252             :             // GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS
     253             : 
     254           0 :             if (asset_id == MASTERNODE_SYNC_GOVERNANCE) {
     255           0 :                 if (!m_gov_manager.IsValid()) {
     256           0 :                     m_node_sync.SwitchToNextAsset();
     257           0 :                     return;
     258             :                 }
     259           0 :                 LogPrint(BCLog::GOBJECT, "Sync Tick -- nTick %d asset_id %d last_bump %lld GetTime() %lld diff %lld\n",
     260             :                          nTick, asset_id, m_node_sync.GetLastBump(), GetTime(), GetTime() - m_node_sync.GetLastBump());
     261             : 
     262             :                 // check for timeout first
     263           0 :                 if (GetTime() - m_node_sync.GetLastBump() > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
     264           0 :                     LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d -- timeout\n", nTick, asset_id);
     265           0 :                     if (attempt == 0) {
     266           0 :                         LogPrintf("Sync Tick -- WARNING: failed to sync %s\n", m_node_sync.GetAssetName());
     267             :                         // it's kind of ok to skip this for now, hopefully we'll catch up later?
     268           0 :                     }
     269           0 :                     m_node_sync.SwitchToNextAsset();
     270           0 :                     return;
     271             :                 }
     272             : 
     273             :                 // only request obj sync once from each peer
     274           0 :                 if (m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) {
     275             :                     // will request votes on per-obj basis from each node in a separate loop below
     276             :                     // to avoid deadlocks here
     277           0 :                     continue;
     278             :                 }
     279           0 :                 m_netfulfilledman.AddFulfilledRequest(pnode->addr, "governance-sync");
     280             : 
     281           0 :                 m_node_sync.BumpAttempt();
     282             : 
     283           0 :                 SendGovernanceSyncRequest(pnode);
     284             : 
     285           0 :                 break; // this will cause each peer to get one request each six seconds for the various assets we need
     286             :             }
     287             :         }
     288             :     }
     289             : 
     290             : 
     291           0 :     if (asset_id != MASTERNODE_SYNC_GOVERNANCE) {
     292             :         // looped through all nodes and not syncing governance yet/already, release them
     293           0 :         return;
     294             :     }
     295             : 
     296             :     // request votes on per-obj basis from each node
     297           0 :     for (const auto& pnode : snap.Nodes()) {
     298           0 :         if (!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) {
     299           0 :             continue; // to early for this node
     300             :         }
     301           0 :         const std::vector<CNode*> vNodeCopy{pnode};
     302           0 :         int nObjsLeftToAsk = RequestGovernanceObjectVotes(vNodeCopy);
     303             :         // check for data
     304           0 :         if (nObjsLeftToAsk == 0) {
     305             :             static int64_t nTimeNoObjectsLeft = 0;
     306             :             static int nLastTick = 0;
     307             :             static int nLastVotes = 0;
     308           0 :             if (nTimeNoObjectsLeft == 0) {
     309             :                 // asked all objects for votes for the first time
     310           0 :                 nTimeNoObjectsLeft = GetTime();
     311           0 :             }
     312             :             // make sure the condition below is checked only once per tick
     313           0 :             if (nLastTick == nTick) continue;
     314           0 :             if (GetTime() - nTimeNoObjectsLeft > MASTERNODE_SYNC_TIMEOUT_SECONDS &&
     315           0 :                 m_gov_manager.GetVoteCount() - nLastVotes <
     316           0 :                     std::max(int(0.0001 * nLastVotes), MASTERNODE_SYNC_TICK_SECONDS)) {
     317             :                 // We already asked for all objects, waited for MASTERNODE_SYNC_TIMEOUT_SECONDS
     318             :                 // after that and less then 0.01% or MASTERNODE_SYNC_TICK_SECONDS
     319             :                 // (i.e. 1 per second) votes were received during the last tick.
     320             :                 // We can be pretty sure that we are done syncing.
     321           0 :                 LogPrintf("Sync Tick -- nTick %d asset_id %d -- asked for all objects, nothing to do\n", nTick,
     322             :                           MASTERNODE_SYNC_GOVERNANCE);
     323             :                 // reset nTimeNoObjectsLeft to be able to use the same condition on resync
     324           0 :                 nTimeNoObjectsLeft = 0;
     325           0 :                 m_node_sync.SwitchToNextAsset();
     326           0 :                 return;
     327             :             }
     328           0 :             nLastTick = nTick;
     329           0 :             nLastVotes = m_gov_manager.GetVoteCount();
     330           0 :         }
     331           0 :     }
     332           0 : }
     333             : 
     334           0 : void SyncManager::ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv)
     335             : {
     336             :     //Sync status count
     337           0 :     if (msg_type != NetMsgType::SYNCSTATUSCOUNT) return;
     338             : 
     339             :     //do not care about stats if sync process finished
     340           0 :     if (m_node_sync.IsSynced()) return;
     341             : 
     342             :     int nItemID;
     343             :     int nCount;
     344           0 :     vRecv >> nItemID >> nCount;
     345             : 
     346           0 :     LogPrint(BCLog::MNSYNC, "SYNCSTATUSCOUNT -- got inventory count: nItemID=%d  nCount=%d  peer=%d\n", nItemID, nCount, peer.GetId());
     347           0 : }
     348             : 
     349           2 : void NodeSyncNotifierImpl::SyncReset()
     350             : {
     351           2 :     uiInterface.NotifyAdditionalDataSyncProgressChanged(-1);
     352           2 : }
     353             : 
     354           0 : void NodeSyncNotifierImpl::SyncFinished()
     355             : {
     356           0 :     assert(m_netfulfilledman.IsValid());
     357             : 
     358           0 :     uiInterface.NotifyAdditionalDataSyncProgressChanged(1);
     359           0 :     m_connman.ForEachNode(CConnman::AllNodes, [this](const CNode* pnode) {
     360           0 :         m_netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync");
     361           0 :     });
     362           0 : }

Generated by: LCOV version 1.16