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 2831 : void SyncManager::Schedule(CScheduler& scheduler)
23 : {
24 5662 : scheduler.scheduleEvery(
25 98212 : [this]() -> void {
26 95381 : if (ShutdownRequested()) return;
27 94805 : ProcessTick();
28 95381 : },
29 2831 : std::chrono::seconds{1});
30 2831 : }
31 :
32 172 : void SyncManager::SendGovernanceSyncRequest(CNode* pnode) const
33 : {
34 172 : CNetMsgMaker msgMaker(pnode->GetCommonVersion());
35 172 : CBloomFilter filter;
36 172 : m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, uint256(), filter));
37 172 : }
38 :
39 624 : void SyncManager::SendGovernanceObjectSyncRequest(CNode* pnode, const uint256& nHash, bool fUseFilter) const
40 : {
41 624 : if (!pnode) return;
42 :
43 624 : LogPrint(BCLog::GOBJECT, "SyncManager::%s -- nHash %s peer=%d\n", __func__, nHash.ToString(), pnode->GetId());
44 :
45 624 : CBloomFilter filter = fUseFilter ? m_gov_manager.GetVoteBloomFilter(nHash) : CBloomFilter{};
46 :
47 624 : CNetMsgMaker msgMaker(pnode->GetCommonVersion());
48 624 : m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, nHash, filter));
49 624 : }
50 :
51 12658 : 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 12658 : if (vNodesCopy.empty()) return -1;
63 :
64 12367 : int64_t nNow = GetTime();
65 12367 : int nTimeout = 60 * 60;
66 12367 : 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 12367 : int nMaxObjRequestsPerNode = 1;
75 12367 : size_t nProjectedVotes = 2000;
76 12367 : if (Params().NetworkIDString() != CBaseChainParams::MAIN) {
77 12367 : nMaxObjRequestsPerNode =
78 24734 : std::max(1, int(nProjectedVotes /
79 12367 : std::max(1, (int)m_gov_manager.GetMNManager().GetListAtChainTip().GetCounts().enabled())));
80 12367 : }
81 :
82 12367 : static Mutex cs_recently;
83 12367 : static std::map<uint256, std::map<CService, int64_t>> mapAskedRecently GUARDED_BY(cs_recently);
84 12367 : LOCK(cs_recently);
85 :
86 37004 : auto [vTriggerObjHashes, vOtherObjHashes] = m_gov_manager.FetchGovernanceObjectVotes(nMaxObjRequestsPerNode, nNow,
87 : mapAskedRecently);
88 :
89 12367 : if (vTriggerObjHashes.empty() && vOtherObjHashes.empty()) return -2;
90 :
91 1511 : LogPrint(BCLog::GOBJECT, "%s -- start: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", __func__,
92 : vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size());
93 :
94 3022 : Shuffle(vTriggerObjHashes.begin(), vTriggerObjHashes.end(), FastRandomContext());
95 3022 : Shuffle(vOtherObjHashes.begin(), vOtherObjHashes.end(), FastRandomContext());
96 :
97 7030 : for (int i = 0; i < nMaxObjRequestsPerNode; ++i) {
98 7030 : uint256 nHashGovobj;
99 :
100 : // ask for triggers first
101 7030 : if (!vTriggerObjHashes.empty()) {
102 2233 : nHashGovobj = vTriggerObjHashes.back();
103 2233 : } else {
104 4797 : if (vOtherObjHashes.empty()) break;
105 3286 : nHashGovobj = vOtherObjHashes.back();
106 : }
107 5519 : bool fAsked = false;
108 15807 : 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 10288 : if (!pnode->CanRelay() || (m_connman.IsActiveMasternode() && pnode->IsInboundConn())) continue;
113 : // stop early to prevent setAskFor overflow
114 : {
115 9448 : LOCK(::cs_main);
116 9448 : size_t nProjectedSize = m_peer_manager->PeerGetRequestedObjectCount(pnode->GetId()) + nProjectedVotes;
117 9448 : if (nProjectedSize > MAX_INV_SZ) continue;
118 9448 : }
119 : // to early to ask the same node
120 9448 : if (mapAskedRecently[nHashGovobj].count(pnode->addr)) continue;
121 :
122 624 : SendGovernanceObjectSyncRequest(pnode, nHashGovobj, true);
123 624 : mapAskedRecently[nHashGovobj][pnode->addr] = nNow + nTimeout;
124 624 : fAsked = true;
125 : // stop loop if max number of peers per obj was asked
126 624 : if (mapAskedRecently[nHashGovobj].size() >= nPeersPerHashMax) break;
127 : }
128 : // NOTE: this should match `if` above (the one before `while`)
129 5519 : if (!vTriggerObjHashes.empty()) {
130 2233 : vTriggerObjHashes.pop_back();
131 2233 : } else {
132 3286 : vOtherObjHashes.pop_back();
133 : }
134 5519 : if (!fAsked) i--;
135 5519 : }
136 1511 : LogPrint(BCLog::GOBJECT, "%s -- end: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", __func__,
137 : vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size());
138 :
139 3022 : return int(vTriggerObjHashes.size() + vOtherObjHashes.size());
140 12658 : }
141 :
142 94805 : void SyncManager::ProcessTick()
143 : {
144 94805 : assert(m_netfulfilledman.IsValid());
145 :
146 : static int nTick = 0;
147 94805 : nTick++;
148 :
149 94805 : const static int64_t nSyncStart = TicksSinceEpoch<std::chrono::milliseconds>(SystemClock::now());
150 94805 : 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 94805 : static int64_t nTimeLastProcess = GetTime();
154 94805 : 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 94805 : if (GetTime() - nTimeLastProcess < MASTERNODE_SYNC_TICK_SECONDS) {
162 : // too early, nothing to do here
163 80687 : return;
164 : }
165 :
166 14118 : nTimeLastProcess = GetTime();
167 14118 : const CConnman::NodesSnapshot snap{m_connman, /* cond = */ CConnman::FullyConnectedOnly};
168 :
169 : // gradually request the rest of the votes after sync finished
170 14118 : if (m_node_sync.IsSynced()) {
171 12151 : RequestGovernanceObjectVotes(snap.Nodes());
172 12151 : return;
173 : }
174 :
175 : // Calculate "progress" for LOG reporting / GUI notification
176 1967 : int attempt = m_node_sync.GetAttempt();
177 1967 : int asset_id = m_node_sync.GetAssetID();
178 1967 : double nSyncProgress = double(attempt + (asset_id - 1) * 8) / (8 * 4);
179 1967 : LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d nTriedPeerCount %d nSyncProgress %f\n", nTick, asset_id,
180 : attempt, nSyncProgress);
181 1967 : uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
182 :
183 : // TODO: move switch-case out from this loop; logic & nodes code to be separated
184 5435 : for (auto& pnode : snap.Nodes()) {
185 3725 : 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 3725 : if (!pnode->CanRelay() || (m_connman.IsActiveMasternode() && pnode->IsInboundConn())) continue;
191 :
192 : {
193 5602 : if ((pnode->HasPermission(NetPermissionFlags::NoBan) || pnode->IsManualConn()) &&
194 3722 : !m_netfulfilledman.HasFulfilledRequest(pnode->addr, strAllow)) {
195 689 : m_netfulfilledman.RemoveAllFulfilledRequests(pnode->addr);
196 689 : m_netfulfilledman.AddFulfilledRequest(pnode->addr, strAllow);
197 689 : LogPrintf("Sync Tick -- skipping mnsync restrictions for peer=%d\n", pnode->GetId());
198 689 : }
199 :
200 1880 : 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 3722 : if (!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "spork-sync")) {
211 : // always get sporks first, only request once from each peer
212 1432 : m_netfulfilledman.AddFulfilledRequest(pnode->addr, "spork-sync");
213 : // get current network sporks
214 1432 : m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::GETSPORKS));
215 1432 : LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d -- requesting sporks from peer=%d\n", nTick,
216 : asset_id, pnode->GetId());
217 1432 : }
218 :
219 3722 : if (asset_id == MASTERNODE_SYNC_BLOCKCHAIN) {
220 3114 : int64_t nTimeSyncTimeout = snap.Nodes().size() > 3 ? MASTERNODE_SYNC_TICK_SECONDS
221 : : MASTERNODE_SYNC_TIMEOUT_SECONDS;
222 3114 : 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 123 : m_node_sync.SwitchToNextAsset();
233 123 : uiInterface.NotifyAdditionalDataSyncProgressChanged(nSyncProgress);
234 :
235 123 : 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 703 : for (auto pNodeTmp : snap.Nodes()) {
238 580 : bool fRequestedEarlier = m_netfulfilledman.HasFulfilledRequest(pNodeTmp->addr,
239 580 : "mempool-sync");
240 580 : if (!pNodeTmp->IsInboundConn() && !fRequestedEarlier && !pNodeTmp->IsBlockRelayOnly()) {
241 144 : m_netfulfilledman.AddFulfilledRequest(pNodeTmp->addr, "mempool-sync");
242 144 : m_connman.PushMessage(pNodeTmp, msgMaker.Make(NetMsgType::MEMPOOL));
243 144 : 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 144 : }
247 : }
248 123 : }
249 123 : }
250 3114 : }
251 :
252 : // GOVOBJ : SYNC GOVERNANCE ITEMS FROM OUR PEERS
253 :
254 3722 : if (asset_id == MASTERNODE_SYNC_GOVERNANCE) {
255 608 : if (!m_gov_manager.IsValid()) {
256 0 : m_node_sync.SwitchToNextAsset();
257 0 : return;
258 : }
259 608 : 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 608 : if (GetTime() - m_node_sync.GetLastBump() > MASTERNODE_SYNC_TIMEOUT_SECONDS) {
264 85 : LogPrint(BCLog::MNSYNC, "Sync Tick -- nTick %d asset_id %d -- timeout\n", nTick, asset_id);
265 85 : if (attempt == 0) {
266 29 : 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 29 : }
269 85 : m_node_sync.SwitchToNextAsset();
270 85 : return;
271 : }
272 :
273 : // only request obj sync once from each peer
274 523 : 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 351 : continue;
278 : }
279 172 : m_netfulfilledman.AddFulfilledRequest(pnode->addr, "governance-sync");
280 :
281 172 : m_node_sync.BumpAttempt();
282 :
283 172 : SendGovernanceSyncRequest(pnode);
284 :
285 172 : 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 1882 : if (asset_id != MASTERNODE_SYNC_GOVERNANCE) {
292 : // looped through all nodes and not syncing governance yet/already, release them
293 1576 : return;
294 : }
295 :
296 : // request votes on per-obj basis from each node
297 5065 : for (const auto& pnode : snap.Nodes()) {
298 4767 : if (!m_netfulfilledman.HasFulfilledRequest(pnode->addr, "governance-sync")) {
299 576 : continue; // to early for this node
300 : }
301 507 : const std::vector<CNode*> vNodeCopy{pnode};
302 507 : int nObjsLeftToAsk = RequestGovernanceObjectVotes(vNodeCopy);
303 : // check for data
304 507 : if (nObjsLeftToAsk == 0) {
305 : static int64_t nTimeNoObjectsLeft = 0;
306 : static int nLastTick = 0;
307 : static int nLastVotes = 0;
308 192 : if (nTimeNoObjectsLeft == 0) {
309 : // asked all objects for votes for the first time
310 24 : nTimeNoObjectsLeft = GetTime();
311 24 : }
312 : // make sure the condition below is checked only once per tick
313 192 : if (nLastTick == nTick) continue;
314 144 : if (GetTime() - nTimeNoObjectsLeft > MASTERNODE_SYNC_TIMEOUT_SECONDS &&
315 8 : m_gov_manager.GetVoteCount() - nLastVotes <
316 8 : 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 8 : 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 8 : nTimeNoObjectsLeft = 0;
325 8 : m_node_sync.SwitchToNextAsset();
326 8 : return;
327 : }
328 128 : nLastTick = nTick;
329 128 : nLastVotes = m_gov_manager.GetVoteCount();
330 128 : }
331 507 : }
332 105841 : }
333 :
334 96785 : void SyncManager::ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv)
335 : {
336 : //Sync status count
337 96785 : if (msg_type != NetMsgType::SYNCSTATUSCOUNT) return;
338 :
339 : //do not care about stats if sync process finished
340 694 : if (m_node_sync.IsSynced()) return;
341 :
342 : int nItemID;
343 : int nCount;
344 131 : vRecv >> nItemID >> nCount;
345 :
346 131 : LogPrint(BCLog::MNSYNC, "SYNCSTATUSCOUNT -- got inventory count: nItemID=%d nCount=%d peer=%d\n", nItemID, nCount, peer.GetId());
347 96785 : }
348 :
349 6466 : void NodeSyncNotifierImpl::SyncReset()
350 : {
351 6466 : uiInterface.NotifyAdditionalDataSyncProgressChanged(-1);
352 6466 : }
353 :
354 857 : void NodeSyncNotifierImpl::SyncFinished()
355 : {
356 857 : assert(m_netfulfilledman.IsValid());
357 :
358 857 : uiInterface.NotifyAdditionalDataSyncProgressChanged(1);
359 1443 : m_connman.ForEachNode(CConnman::AllNodes, [this](const CNode* pnode) {
360 586 : m_netfulfilledman.AddFulfilledRequest(pnode->addr, "full-sync");
361 586 : });
362 857 : }
|