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 : }
|