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 <governance/net_governance.h>
6 :
7 : #include <chainparams.h>
8 : #include <common/bloom.h>
9 : #include <evo/deterministicmns.h>
10 : #include <governance/governance.h>
11 : #include <governance/object.h>
12 : #include <logging.h>
13 : #include <masternode/sync.h>
14 : #include <net.h>
15 : #include <netfulfilledman.h>
16 : #include <netmessagemaker.h>
17 : #include <scheduler.h>
18 :
19 : class CConnman;
20 :
21 0 : void NetGovernance::Schedule(CScheduler& scheduler)
22 : {
23 : // Code below is meant to be running only if governance validation is enabled
24 : //
25 0 : if (!m_gov_manager.IsValid()) return;
26 0 : scheduler.scheduleEvery(
27 0 : [this]() -> void {
28 0 : if (!m_node_sync.IsSynced()) return;
29 :
30 : // Request governance objects for orphan votes
31 0 : auto vecOrphanHashes = m_gov_manager.GetOrphanVoteObjectHashes();
32 0 : if (!vecOrphanHashes.empty()) {
33 0 : LogPrint(BCLog::GOBJECT, "NetGovernance::Schedule -- requesting %d orphan objects\n",
34 : vecOrphanHashes.size());
35 0 : const CConnman::NodesSnapshot snap{m_connman, CConnman::FullyConnectedOnly};
36 0 : for (const uint256& nHash : vecOrphanHashes) {
37 0 : for (CNode* pnode : snap.Nodes()) {
38 0 : if (!pnode->CanRelay()) continue;
39 0 : CNetMsgMaker msgMaker(pnode->GetCommonVersion());
40 0 : CBloomFilter filter; // Empty filter - we want the object, not votes
41 0 : m_connman.PushMessage(pnode, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, nHash, filter));
42 0 : }
43 : }
44 0 : }
45 :
46 : // CHECK AND REMOVE - REPROCESS GOVERNANCE OBJECTS
47 0 : m_gov_manager.CheckAndRemove();
48 0 : },
49 0 : std::chrono::minutes{5});
50 :
51 0 : scheduler.scheduleEvery(
52 0 : [this]() -> void {
53 0 : auto relay_invs = m_gov_manager.FetchRelayInventory();
54 0 : for (const auto& inv : relay_invs) {
55 0 : m_peer_manager->PeerRelayInv(inv);
56 : }
57 0 : },
58 : // Tests need tighter timings to avoid timeouts, use more relaxed pacing otherwise
59 0 : Params().IsMockableChain() ? std::chrono::seconds{1} : std::chrono::seconds{5});
60 0 : }
61 :
62 0 : void NetGovernance::ProcessMessage(CNode& peer, const std::string& msg_type, CDataStream& vRecv)
63 : {
64 0 : if (!m_gov_manager.IsValid()) return;
65 0 : if (!m_node_sync.IsBlockchainSynced()) return;
66 :
67 : // ANOTHER USER IS ASKING US TO HELP THEM SYNC GOVERNANCE OBJECT DATA
68 0 : if (msg_type == NetMsgType::MNGOVERNANCESYNC) {
69 : // Ignore such requests until we are fully synced.
70 : // We could start processing this after masternode list is synced
71 : // but this is a heavy one so it's better to finish sync first.
72 0 : if (!m_node_sync.IsSynced()) return;
73 :
74 0 : uint256 nProp;
75 0 : CBloomFilter filter;
76 0 : vRecv >> nProp;
77 0 : vRecv >> filter;
78 :
79 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- syncing governance objects to our peer %s\n", peer.GetLogString());
80 0 : if (nProp == uint256()) {
81 : // Full sync of all governance objects
82 0 : assert(m_netfulfilledman.IsValid());
83 0 : if (m_netfulfilledman.HasFulfilledRequest(peer.addr, NetMsgType::MNGOVERNANCESYNC)) {
84 : // Asking for the whole list multiple times in a short period of time is no good
85 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- peer already asked me for the list\n");
86 0 : m_peer_manager->PeerMisbehaving(peer.GetId(), 20);
87 0 : return;
88 : }
89 0 : m_netfulfilledman.AddFulfilledRequest(peer.addr, NetMsgType::MNGOVERNANCESYNC);
90 :
91 0 : auto invs = m_gov_manager.GetSyncableObjectInvs();
92 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- syncing %d objects to peer=%d\n", invs.size(), peer.GetId());
93 :
94 0 : CNetMsgMaker msgMaker(peer.GetCommonVersion());
95 0 : m_connman.PushMessage(&peer, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ,
96 0 : static_cast<int>(invs.size())));
97 0 : for (const auto& inv : invs) {
98 0 : m_peer_manager->PeerRelayInv(inv);
99 : }
100 0 : } else {
101 : // Sync votes for a specific governance object
102 0 : auto invs = m_gov_manager.GetSyncableVoteInvs(nProp, filter);
103 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- syncing %d votes for %s to peer=%d\n", invs.size(),
104 : nProp.ToString(), peer.GetId());
105 :
106 0 : CNetMsgMaker msgMaker(peer.GetCommonVersion());
107 0 : m_connman.PushMessage(&peer, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ_VOTE,
108 0 : static_cast<int>(invs.size())));
109 0 : for (const auto& inv : invs) {
110 0 : m_peer_manager->PeerRelayInv(inv);
111 : }
112 0 : }
113 0 : }
114 : // A NEW GOVERNANCE OBJECT HAS ARRIVED
115 0 : else if (msg_type == NetMsgType::MNGOVERNANCEOBJECT) {
116 : // MAKE SURE WE HAVE A VALID REFERENCE TO THE TIP BEFORE CONTINUING
117 0 : CGovernanceObject govobj;
118 0 : vRecv >> govobj;
119 :
120 0 : uint256 nHash = govobj.GetHash();
121 :
122 0 : WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(peer.GetId(), CInv{MSG_GOVERNANCE_OBJECT, nHash}));
123 :
124 0 : if (!m_node_sync.IsBlockchainSynced()) {
125 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode list not synced\n");
126 0 : return;
127 : }
128 :
129 0 : std::string strHash = nHash.ToString();
130 :
131 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received object: %s\n", strHash);
132 :
133 0 : if (!m_gov_manager.AcceptMessage(nHash)) {
134 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received unrequested object: %s\n", strHash);
135 0 : return;
136 : }
137 :
138 0 : if (!WITH_LOCK(::cs_main, return m_gov_manager.ProcessObject(peer.GetLogString(), nHash, govobj))) {
139 : // apply node's ban score
140 0 : m_peer_manager->PeerMisbehaving(peer.GetId(), 20);
141 0 : }
142 0 : }
143 :
144 : // A NEW GOVERNANCE OBJECT VOTE HAS ARRIVED
145 0 : else if (msg_type == NetMsgType::MNGOVERNANCEOBJECTVOTE) {
146 0 : CGovernanceVote vote;
147 0 : vRecv >> vote;
148 :
149 0 : uint256 nHash = vote.GetHash();
150 :
151 0 : WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(peer.GetId(), CInv{MSG_GOVERNANCE_OBJECT_VOTE, nHash}));
152 :
153 : // Ignore such messages until masternode list is synced
154 0 : if (!m_node_sync.IsBlockchainSynced()) {
155 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- masternode list not synced\n");
156 0 : return;
157 : }
158 :
159 0 : const auto tip_mn_list = m_gov_manager.GetMNManager().GetListAtChainTip();
160 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Received vote: %s\n", vote.ToString(tip_mn_list));
161 :
162 0 : std::string strHash = nHash.ToString();
163 :
164 0 : if (!m_gov_manager.AcceptMessage(nHash)) {
165 0 : LogPrint(BCLog::GOBJECT, /* Continued */
166 : "MNGOVERNANCEOBJECTVOTE -- Received unrequested vote object: %s, hash: %s, peer = %d\n",
167 : vote.ToString(tip_mn_list), strHash, peer.GetId());
168 0 : return;
169 : }
170 :
171 0 : CGovernanceException exception;
172 0 : uint256 hashToRequest;
173 0 : if (m_gov_manager.ProcessVote(vote, exception, hashToRequest)) {
174 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- %s new\n", strHash);
175 0 : m_node_sync.BumpAssetLastTime("MNGOVERNANCEOBJECTVOTE");
176 :
177 0 : if (!m_node_sync.IsSynced()) {
178 0 : LogPrint(BCLog::GOBJECT, "%s -- won't relay until fully synced\n", __func__);
179 0 : return;
180 : }
181 0 : auto dmn = tip_mn_list.GetMNByCollateral(vote.GetMasternodeOutpoint());
182 0 : if (!dmn) {
183 0 : return;
184 : }
185 0 : m_gov_manager.RelayVote(vote);
186 : // TODO: figure out why immediate sending of inventory doesn't work here!
187 : // m_peer_manager->PeerRelayInv(CInv{MSG_GOVERNANCE_OBJECT_VOTE, nHash});
188 0 : } else {
189 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Rejected vote, error = %s\n", exception.what());
190 0 : if (hashToRequest != uint256()) {
191 : // Orphan vote - request the missing governance object
192 0 : CNetMsgMaker msgMaker(peer.GetCommonVersion());
193 0 : CBloomFilter filter; // Empty filter - we just want the object, not votes
194 0 : m_connman.PushMessage(&peer, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, hashToRequest, filter));
195 0 : }
196 0 : if ((exception.GetNodePenalty() != 0) && m_node_sync.IsSynced()) {
197 0 : m_peer_manager->PeerMisbehaving(peer.GetId(), exception.GetNodePenalty());
198 0 : }
199 : }
200 0 : }
201 0 : }
202 :
203 0 : bool NetGovernance::AlreadyHave(const CInv& inv)
204 : {
205 0 : if (inv.type != MSG_GOVERNANCE_OBJECT && inv.type != MSG_GOVERNANCE_OBJECT_VOTE) {
206 0 : return false;
207 : }
208 : // When governance isn't loaded (e.g. -disablegovernance), claim we already have
209 : // the item so we don't fetch or track it. ConfirmInventoryRequest would otherwise
210 : // grow m_requested_hash_time unbounded since CheckAndRemove never runs in that mode.
211 0 : if (!m_gov_manager.IsValid()) return true;
212 0 : return !m_gov_manager.ConfirmInventoryRequest(inv);
213 0 : }
214 :
215 0 : bool NetGovernance::ProcessGetData(CNode& pfrom, const CInv& inv, CConnman& connman, const CNetMsgMaker& msgMaker)
216 : {
217 0 : if (inv.type == MSG_GOVERNANCE_OBJECT) {
218 0 : if (!m_gov_manager.HaveObjectForHash(inv.hash)) return false;
219 0 : CDataStream ss(SER_NETWORK, pfrom.GetCommonVersion());
220 0 : ss.reserve(1000);
221 0 : if (!m_gov_manager.SerializeObjectForHash(inv.hash, ss)) return false;
222 0 : connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCEOBJECT, ss));
223 0 : return true;
224 0 : }
225 0 : if (inv.type == MSG_GOVERNANCE_OBJECT_VOTE) {
226 0 : if (!m_gov_manager.HaveVoteForHash(inv.hash)) return false;
227 0 : CDataStream ss(SER_NETWORK, pfrom.GetCommonVersion());
228 0 : ss.reserve(1000);
229 0 : if (!m_gov_manager.SerializeVoteForHash(inv.hash, ss)) return false;
230 0 : connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCEOBJECTVOTE, ss));
231 0 : return true;
232 0 : }
233 0 : return false;
234 0 : }
|