Line data Source code
1 : // Copyright (c) 2014-2025 The Dash Core developers
2 : // Distributed under the MIT/X11 software license, see the accompanying
3 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 :
5 : #include <governance/governance.h>
6 :
7 : #include <evo/deterministicmns.h>
8 : #include <flat-database.h>
9 : #include <governance/common.h>
10 : #include <governance/object.h>
11 : #include <governance/superblock.h>
12 : #include <masternode/meta.h>
13 : #include <masternode/sync.h>
14 :
15 : #include <chain.h>
16 : #include <chainparams.h>
17 : #include <common/bloom.h>
18 : #include <deploymentstatus.h>
19 : #include <net.h>
20 : #include <node/interface_ui.h>
21 : #include <protocol.h>
22 : #include <timedata.h>
23 : #include <util/check.h>
24 : #include <util/time.h>
25 : #include <validationinterface.h>
26 :
27 : #include <ranges>
28 :
29 146 : const std::string GovernanceStore::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-16";
30 :
31 : namespace {
32 : constexpr std::chrono::seconds GOVERNANCE_DELETION_DELAY{10min};
33 : constexpr std::chrono::seconds GOVERNANCE_ORPHAN_EXPIRATION_TIME{10min};
34 : constexpr std::chrono::seconds MAX_TIME_FUTURE_DEVIATION{1h};
35 : constexpr std::chrono::seconds RELIABLE_PROPAGATION_TIME{1min};
36 :
37 : class ScopedLockBool
38 : {
39 : bool& ref;
40 : bool fPrevValue;
41 :
42 : public:
43 0 : ScopedLockBool(Mutex& _cs, bool& _ref, bool _value) :
44 0 : ref(_ref)
45 0 : {
46 0 : AssertLockHeld(_cs);
47 0 : fPrevValue = ref;
48 0 : ref = _value;
49 0 : }
50 :
51 0 : ~ScopedLockBool() { ref = fPrevValue; }
52 : };
53 : } // anonymous namespace
54 :
55 178 : GovernanceStore::GovernanceStore() :
56 178 : cs_store(),
57 178 : mapObjects(),
58 178 : mapErasedGovernanceObjects(),
59 178 : cmapInvalidVotes(MAX_CACHE_SIZE),
60 178 : cmmapOrphanVotes(MAX_CACHE_SIZE),
61 178 : mapLastMasternodeObject(),
62 178 : lastMNListForVotingKeys(std::make_shared<CDeterministicMNList>())
63 0 : {
64 178 : }
65 :
66 356 : CGovernanceManager::CGovernanceManager(CMasternodeMetaMan& mn_metaman, const ChainstateManager& chainman,
67 : governance::SuperblockManager& superblocks, CDeterministicMNManager& dmnman,
68 : CMasternodeSync& mn_sync) :
69 178 : m_db{std::make_unique<db_type>("governance.dat", "magicGovernanceCache")},
70 : m_mn_metaman{mn_metaman},
71 : m_chainman{chainman},
72 : m_superblocks{superblocks},
73 : m_dmnman{dmnman},
74 : m_mn_sync{mn_sync},
75 : cmapVoteToObject{MAX_CACHE_SIZE},
76 : mapPostponedObjects{}
77 178 : {
78 178 : }
79 :
80 356 : CGovernanceManager::~CGovernanceManager()
81 178 : {
82 : // Reset the paired SuperblockManager so its loaded-flag and trigger map don't
83 : // outlive us. chain_helper (its owner) must outlive govman -- see PrepareShutdown
84 : // in init.cpp and the test teardown ordering.
85 178 : m_superblocks.Clear();
86 178 : if (!is_loaded) return;
87 0 : m_db->Store(*this);
88 356 : }
89 :
90 0 : bool CGovernanceManager::LoadCache(bool load_cache)
91 : {
92 0 : AssertLockNotHeld(cs_store);
93 0 : assert(m_db != nullptr);
94 0 : is_loaded = load_cache ? m_db->Load(*this) : m_db->Store(*this);
95 0 : if (is_loaded && load_cache) {
96 0 : CheckAndRemove();
97 0 : InitOnLoad();
98 0 : }
99 0 : m_superblocks.SetLoaded(is_loaded);
100 0 : return is_loaded;
101 : }
102 :
103 0 : void CGovernanceManager::RelayObject(const CGovernanceObject& obj)
104 : {
105 0 : AssertLockNotHeld(cs_relay);
106 0 : if (!m_mn_sync.IsSynced()) {
107 0 : LogPrint(BCLog::GOBJECT, "%s -- won't relay until fully synced\n", __func__);
108 0 : return;
109 : }
110 :
111 0 : LOCK(cs_relay);
112 0 : m_relay_invs.emplace_back(MSG_GOVERNANCE_OBJECT, obj.GetHash());
113 0 : }
114 :
115 0 : void CGovernanceManager::RelayVote(const CGovernanceVote& vote)
116 : {
117 0 : AssertLockNotHeld(cs_relay);
118 0 : if (!m_mn_sync.IsSynced()) {
119 0 : LogPrint(BCLog::GOBJECT, "%s -- won't relay until fully synced\n", __func__);
120 0 : return;
121 : }
122 :
123 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
124 0 : auto dmn = tip_mn_list.GetMNByCollateral(vote.GetMasternodeOutpoint());
125 0 : if (!dmn) {
126 0 : return;
127 : }
128 :
129 0 : LOCK(cs_relay);
130 0 : m_relay_invs.emplace_back(MSG_GOVERNANCE_OBJECT_VOTE, vote.GetHash());
131 0 : }
132 :
133 : // Accessors for thread-safe access to maps
134 0 : bool CGovernanceManager::HaveObjectForHash(const uint256& nHash) const
135 : {
136 0 : LOCK(cs_store);
137 0 : return (mapObjects.count(nHash) == 1 || mapPostponedObjects.count(nHash) == 1);
138 0 : }
139 :
140 0 : bool CGovernanceManager::SerializeObjectForHash(const uint256& nHash, CDataStream& ss) const
141 : {
142 0 : LOCK(cs_store);
143 0 : auto it = mapObjects.find(nHash);
144 0 : if (it == mapObjects.end()) {
145 0 : it = mapPostponedObjects.find(nHash);
146 0 : if (it == mapPostponedObjects.end())
147 0 : return false;
148 0 : }
149 0 : ss << it->second;
150 0 : return true;
151 0 : }
152 :
153 0 : bool CGovernanceManager::HaveVoteForHash(const uint256& nHash) const
154 : {
155 0 : LOCK(cs_store);
156 :
157 0 : std::shared_ptr<CGovernanceObject> pGovobj{nullptr};
158 0 : return cmapVoteToObject.Get(nHash, pGovobj) && WITH_LOCK(pGovobj->cs, return pGovobj->GetVoteFile().HasVote(nHash));
159 0 : }
160 :
161 0 : int CGovernanceManager::GetVoteCount() const
162 : {
163 0 : LOCK(cs_store);
164 0 : return (int)cmapVoteToObject.GetSize();
165 0 : }
166 :
167 0 : bool CGovernanceManager::SerializeVoteForHash(const uint256& nHash, CDataStream& ss) const
168 : {
169 0 : LOCK(cs_store);
170 :
171 0 : std::shared_ptr<CGovernanceObject> pGovobj{nullptr};
172 0 : return cmapVoteToObject.Get(nHash, pGovobj) && WITH_LOCK(pGovobj->cs, return pGovobj->GetVoteFile().SerializeVoteToStream(nHash, ss));
173 0 : }
174 :
175 0 : void CGovernanceManager::AddPostponedObject(const CGovernanceObject& govobj)
176 : {
177 0 : LOCK(cs_store);
178 0 : AddPostponedObjectInternal(govobj);
179 0 : }
180 :
181 0 : void CGovernanceManager::AddPostponedObjectInternal(const CGovernanceObject& govobj)
182 : {
183 0 : AssertLockHeld(cs_store);
184 0 : mapPostponedObjects.emplace(govobj.GetHash(), std::make_shared<CGovernanceObject>(govobj));
185 0 : }
186 :
187 0 : bool CGovernanceManager::ProcessObject(const std::string& peer_str, const uint256& nHash, CGovernanceObject& govobj)
188 : {
189 0 : std::string strHash = nHash.ToString();
190 :
191 0 : LOCK(cs_store);
192 :
193 0 : if (mapObjects.count(nHash) || mapPostponedObjects.count(nHash) || mapErasedGovernanceObjects.count(nHash)) {
194 : // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE?
195 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received already seen object: %s\n", strHash);
196 0 : return true;
197 : }
198 :
199 0 : bool fRateCheckBypassed = false;
200 0 : if (!MasternodeRateCheck(govobj, true, false, fRateCheckBypassed)) {
201 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n",
202 : strHash, nCachedBlockHeight);
203 0 : return true;
204 : }
205 :
206 0 : std::string strError;
207 : // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN
208 :
209 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
210 0 : bool fMissingConfirmations = false;
211 0 : bool fIsValid = govobj.IsValidLocally(tip_mn_list, m_chainman, strError, fMissingConfirmations, true);
212 :
213 : bool unused_rcb;
214 0 : if (fRateCheckBypassed && fIsValid && !MasternodeRateCheck(govobj, true, true, unused_rcb)) {
215 0 : LogPrint(BCLog::GOBJECT, /* Continued */
216 : "MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - %s - (current "
217 : "block height %d)\n",
218 : strHash, nCachedBlockHeight);
219 0 : return true;
220 : }
221 :
222 0 : if (!fIsValid) {
223 0 : if (fMissingConfirmations) {
224 0 : AddPostponedObjectInternal(govobj);
225 0 : LogPrintf("MNGOVERNANCEOBJECT -- Not enough fee confirmations for: %s, strError = %s\n", strHash, strError);
226 0 : return true;
227 : } else {
228 0 : LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Governance object is invalid - %s\n", strError);
229 0 : return false;
230 : }
231 : }
232 :
233 0 : AddGovernanceObjectInternal(govobj, peer_str);
234 0 : return true;
235 0 : }
236 :
237 0 : void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj)
238 : {
239 0 : AssertLockHeld(cs_store);
240 0 : AssertLockNotHeld(cs_relay);
241 :
242 0 : uint256 nHash = govobj.GetHash();
243 0 : std::vector<vote_time_pair_t> vecVotePairs;
244 0 : cmmapOrphanVotes.GetAll(nHash, vecVotePairs);
245 :
246 0 : ScopedLockBool guard(cs_store, fRateChecksEnabled, false);
247 :
248 0 : int64_t nNow = GetAdjustedTime();
249 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
250 0 : for (const auto& pairVote : vecVotePairs) {
251 0 : const auto& [vote, time] = pairVote;
252 0 : bool fRemove = false;
253 0 : CGovernanceException e;
254 0 : if (time < nNow) {
255 0 : fRemove = true;
256 0 : } else if (govobj.ProcessVote(m_mn_metaman, fRateChecksEnabled, tip_mn_list, vote, e)) {
257 0 : RelayVote(vote);
258 0 : fRemove = true;
259 0 : }
260 0 : if (fRemove) {
261 0 : cmmapOrphanVotes.Erase(nHash, pairVote);
262 0 : }
263 0 : }
264 0 : }
265 :
266 0 : void CGovernanceManager::AddGovernanceObjectInternal(CGovernanceObject& insert_obj, const std::string& peer_str)
267 : {
268 0 : AssertLockHeld(::cs_main);
269 0 : AssertLockHeld(cs_store);
270 0 : AssertLockNotHeld(cs_relay);
271 :
272 0 : uint256 nHash = insert_obj.GetHash();
273 0 : std::string strHash = nHash.ToString();
274 :
275 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
276 :
277 : // UPDATE CACHED VARIABLES FOR THIS OBJECT AND ADD IT TO OUR MANAGED DATA
278 :
279 0 : insert_obj.UpdateSentinelVariables(tip_mn_list); //this sets local vars in object
280 :
281 0 : std::string strError;
282 :
283 : // MAKE SURE THIS OBJECT IS OK
284 :
285 0 : if (!insert_obj.IsValidLocally(tip_mn_list, m_chainman, strError, true)) {
286 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- invalid governance object - %s - (nCachedBlockHeight %d) \n", strError, nCachedBlockHeight);
287 0 : return;
288 : }
289 :
290 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- Adding object: hash = %s, type = %d\n", nHash.ToString(),
291 : std23::to_underlying(insert_obj.GetObjectType()));
292 :
293 : // INSERT INTO OUR GOVERNANCE OBJECT MEMORY
294 : // IF WE HAVE THIS OBJECT ALREADY, WE DON'T WANT ANOTHER COPY
295 0 : auto [emplace_ret, emplace_status] = mapObjects.emplace(nHash, std::make_shared<CGovernanceObject>(insert_obj));
296 :
297 0 : if (!emplace_status) {
298 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- already have governance object %s\n", nHash.ToString());
299 0 : return;
300 : }
301 :
302 : // SHOULD WE ADD THIS OBJECT TO ANY OTHER MANAGERS?
303 :
304 0 : auto& [_, govobj] = *emplace_ret;
305 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- Before trigger block, GetDataAsPlainString = %s, nObjectType = %d\n",
306 : Assert(govobj)->GetDataAsPlainString(), std23::to_underlying(govobj->GetObjectType()));
307 :
308 0 : if (govobj->GetObjectType() == GovernanceObject::TRIGGER && !m_superblocks.AddTrigger(govobj, nCachedBlockHeight)) {
309 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- undo adding invalid trigger object: hash = %s\n", nHash.ToString());
310 0 : govobj->PrepareDeletion(GetTime<std::chrono::seconds>().count());
311 0 : return;
312 : }
313 :
314 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- %s new, received from peer %s\n", strHash, peer_str);
315 0 : RelayObject(*govobj);
316 :
317 : // Update the rate buffer
318 0 : MasternodeRateUpdate(*govobj);
319 :
320 0 : m_mn_sync.BumpAssetLastTime("CGovernanceManager::AddGovernanceObject");
321 :
322 : // WE MIGHT HAVE PENDING/ORPHAN VOTES FOR THIS OBJECT
323 :
324 0 : CheckOrphanVotes(*govobj);
325 :
326 : // SEND NOTIFICATION TO SCRIPT/ZMQ
327 0 : GetMainSignals().NotifyGovernanceObject(std::make_shared<const Governance::Object>(govobj->Object()), nHash.ToString());
328 0 : uiInterface.NotifyGovernanceChanged();
329 0 : }
330 :
331 0 : void CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj, const std::string& peer_str)
332 : {
333 0 : LOCK2(::cs_main, cs_store);
334 0 : AddGovernanceObjectInternal(govobj, peer_str);
335 0 : }
336 :
337 0 : void CGovernanceManager::CheckAndRemove()
338 : {
339 0 : AssertLockNotHeld(cs_store);
340 0 : assert(m_mn_metaman.IsValid());
341 :
342 : // Return on initial sync, spammed the debug.log and provided no use
343 0 : if (!m_mn_sync.IsBlockchainSynced()) return;
344 :
345 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean\n");
346 :
347 0 : std::vector<uint256> vecDirtyHashes = m_mn_metaman.GetAndClearDirtyGovernanceObjectHashes();
348 :
349 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
350 :
351 : {
352 0 : LOCK2(::cs_main, cs_store);
353 :
354 0 : for (const uint256& nHash : vecDirtyHashes) {
355 0 : auto it = mapObjects.find(nHash);
356 0 : if (it == mapObjects.end()) {
357 0 : continue;
358 : }
359 0 : Assert(it->second)->ClearMasternodeVotes(tip_mn_list);
360 : }
361 :
362 0 : ScopedLockBool guard(cs_store, fRateChecksEnabled, false);
363 :
364 : // Clean up any expired or invalid triggers
365 0 : m_superblocks.Clean(nCachedBlockHeight);
366 :
367 0 : const auto nNow = GetTime<std::chrono::seconds>();
368 0 : for (auto it = mapObjects.begin(); it != mapObjects.end();) {
369 0 : auto [nHash, pObj] = *it;
370 0 : std::string strHash = nHash.ToString();
371 :
372 : // IF CACHE IS NOT DIRTY, WHY DO THIS?
373 0 : if (Assert(pObj)->IsSetDirtyCache()) {
374 : // UPDATE LOCAL VALIDITY AGAINST CRYPTO DATA
375 0 : pObj->UpdateLocalValidity(tip_mn_list, m_chainman);
376 :
377 : // UPDATE SENTINEL SIGNALING VARIABLES
378 0 : pObj->UpdateSentinelVariables(tip_mn_list);
379 0 : }
380 :
381 : // IF DELETE=TRUE, THEN CLEAN THE MESS UP!
382 :
383 0 : const auto nTimeSinceDeletion = nNow - std::chrono::seconds{pObj->GetDeletionTime()};
384 :
385 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- Checking object for deletion: %s, deletion time = %d, time since deletion = %d, delete flag = %d, expired flag = %d\n",
386 : strHash, pObj->GetDeletionTime(), nTimeSinceDeletion.count(), pObj->IsSetCachedDelete(), pObj->IsSetExpired());
387 :
388 0 : if ((pObj->IsSetCachedDelete() || pObj->IsSetExpired()) &&
389 0 : (nTimeSinceDeletion >= GOVERNANCE_DELETION_DELAY)) {
390 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- erase obj %s\n", nHash.ToString());
391 0 : m_mn_metaman.RemoveGovernanceObject(pObj->GetHash());
392 :
393 : // Remove vote references
394 0 : const object_ref_cm_t::list_t& listItems = cmapVoteToObject.GetItemList();
395 0 : for (auto lit = listItems.begin(); lit != listItems.end();) {
396 0 : if (lit->value == pObj) {
397 0 : uint256 nKey = lit->key;
398 0 : ++lit;
399 0 : cmapVoteToObject.Erase(nKey);
400 0 : } else {
401 0 : ++lit;
402 : }
403 : }
404 :
405 0 : int64_t nTimeExpired{0};
406 :
407 0 : if (pObj->GetObjectType() == GovernanceObject::PROPOSAL) {
408 : // keep hashes of deleted proposals forever
409 0 : nTimeExpired = std::numeric_limits<int64_t>::max();
410 0 : } else {
411 0 : int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing;
412 0 : nTimeExpired = (std::chrono::seconds{pObj->GetCreationTime()} +
413 0 : std::chrono::seconds{2 * nSuperblockCycleSeconds} + GOVERNANCE_DELETION_DELAY)
414 0 : .count();
415 : }
416 :
417 0 : mapErasedGovernanceObjects.insert(std::make_pair(nHash, nTimeExpired));
418 0 : if (pObj->GetObjectType() == GovernanceObject::TRIGGER) {
419 0 : m_superblocks.RemoveTrigger(nHash);
420 0 : }
421 0 : mapObjects.erase(it++);
422 0 : } else {
423 0 : if (pObj->GetObjectType() == GovernanceObject::PROPOSAL) {
424 0 : std::string strValidationError;
425 0 : if (!governance::ValidateProposal(pObj->GetDataAsHexString(), strValidationError)) {
426 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- set for deletion expired obj %s\n", strHash);
427 0 : pObj->PrepareDeletion(nNow.count());
428 0 : }
429 0 : }
430 0 : ++it;
431 : }
432 0 : }
433 :
434 : // forget about expired deleted objects
435 0 : for (auto s_it = mapErasedGovernanceObjects.begin(); s_it != mapErasedGovernanceObjects.end();) {
436 0 : if (s_it->second < nNow.count()) {
437 0 : mapErasedGovernanceObjects.erase(s_it++);
438 0 : } else {
439 0 : ++s_it;
440 : }
441 : }
442 :
443 : // forget about expired requests
444 0 : for (auto r_it = m_requested_hash_time.begin(); r_it != m_requested_hash_time.end();) {
445 0 : if (r_it->second < nNow) {
446 0 : m_requested_hash_time.erase(r_it++);
447 0 : } else {
448 0 : ++r_it;
449 : }
450 : }
451 0 : }
452 :
453 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- %s, m_requested_hash_time size=%d\n",
454 : ToString(), m_requested_hash_time.size());
455 0 : }
456 :
457 0 : std::vector<CInv> CGovernanceManager::FetchRelayInventory()
458 : {
459 0 : std::vector<CInv> ret;
460 0 : LOCK(cs_relay);
461 0 : swap(ret, m_relay_invs);
462 0 : return ret;
463 0 : }
464 :
465 0 : std::shared_ptr<const CGovernanceObject> CGovernanceManager::FindConstGovernanceObject(const uint256& nHash) const
466 : {
467 0 : LOCK(cs_store);
468 0 : return FindConstGovernanceObjectInternal(nHash);
469 0 : }
470 :
471 0 : std::shared_ptr<const CGovernanceObject> CGovernanceManager::FindConstGovernanceObjectInternal(const uint256& nHash) const
472 : {
473 0 : AssertLockHeld(cs_store);
474 :
475 0 : auto it = mapObjects.find(nHash);
476 0 : if (it != mapObjects.end()) return (it->second);
477 :
478 0 : return nullptr;
479 0 : }
480 :
481 0 : std::shared_ptr<CGovernanceObject> CGovernanceManager::FindGovernanceObject(const uint256& nHash)
482 : {
483 0 : AssertLockNotHeld(cs_store);
484 0 : LOCK(cs_store);
485 0 : return FindGovernanceObjectInternal(nHash);
486 0 : }
487 :
488 0 : std::shared_ptr<CGovernanceObject> CGovernanceManager::FindGovernanceObjectInternal(const uint256& nHash)
489 : {
490 0 : AssertLockHeld(cs_store);
491 0 : if (mapObjects.count(nHash)) return mapObjects[nHash];
492 0 : return nullptr;
493 0 : }
494 :
495 0 : std::shared_ptr<CGovernanceObject> CGovernanceManager::FindGovernanceObjectByDataHash(const uint256 &nDataHash)
496 : {
497 0 : AssertLockNotHeld(cs_store);
498 0 : LOCK(cs_store);
499 0 : for (const auto& [nHash, govobj] : mapObjects) {
500 0 : if (Assert(govobj)->GetDataHash() == nDataHash) return govobj;
501 : }
502 0 : return nullptr;
503 0 : }
504 :
505 0 : std::vector<CGovernanceVote> CGovernanceManager::GetCurrentVotes(const uint256& nParentHash, const COutPoint& mnCollateralOutpointFilter) const
506 : {
507 0 : LOCK(cs_store);
508 0 : std::vector<CGovernanceVote> vecResult;
509 :
510 : // Find the governance object or short-circuit.
511 0 : auto it = mapObjects.find(nParentHash);
512 0 : if (it == mapObjects.end()) return vecResult;
513 0 : const auto& govobj = *Assert(it->second);
514 :
515 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
516 0 : std::map<COutPoint, CDeterministicMNCPtr> mapMasternodes;
517 0 : if (mnCollateralOutpointFilter.IsNull()) {
518 0 : tip_mn_list.ForEachMNShared(/*onlyValid=*/false,
519 0 : [&](const auto& dmn) { mapMasternodes.emplace(dmn->collateralOutpoint, dmn); });
520 0 : } else {
521 0 : auto dmn = tip_mn_list.GetMNByCollateral(mnCollateralOutpointFilter);
522 0 : if (dmn) {
523 0 : mapMasternodes.emplace(dmn->collateralOutpoint, dmn);
524 0 : }
525 0 : }
526 :
527 : // Loop through each MN collateral outpoint and get the votes for the `nParentHash` governance object
528 0 : for (const auto& [outpoint, _] : mapMasternodes) {
529 : // get a vote_rec_t from the govobj
530 0 : vote_rec_t voteRecord;
531 0 : if (!govobj.GetCurrentMNVotes(outpoint, voteRecord)) continue;
532 :
533 0 : for (const auto& [signal, vote_instance] : voteRecord.mapInstances) {
534 0 : CGovernanceVote vote = CGovernanceVote(outpoint, nParentHash, (vote_signal_enum_t)signal,
535 0 : vote_instance.eOutcome);
536 0 : vote.SetTime(vote_instance.nCreationTime);
537 0 : vecResult.push_back(vote);
538 0 : }
539 0 : }
540 :
541 0 : return vecResult;
542 0 : }
543 :
544 0 : void CGovernanceManager::GetAllNewerThan(std::vector<CGovernanceObject>& objs, int64_t nMoreThanTime,
545 : bool include_postponed) const
546 : {
547 0 : LOCK(cs_store);
548 :
549 0 : for (const auto& [_, govobj] : mapObjects) {
550 0 : if (Assert(govobj)->GetCreationTime() < nMoreThanTime) {
551 0 : continue;
552 : }
553 0 : objs.push_back(*govobj);
554 : }
555 :
556 0 : if (include_postponed) {
557 0 : for (const auto& [_, govobj] : mapPostponedObjects) {
558 0 : if (Assert(govobj)->GetCreationTime() < nMoreThanTime) {
559 0 : continue;
560 : }
561 0 : objs.push_back(*govobj);
562 : }
563 0 : }
564 0 : }
565 :
566 0 : bool CGovernanceManager::ConfirmInventoryRequest(const CInv& inv)
567 : {
568 0 : AssertLockNotHeld(cs_store);
569 :
570 : // do not request objects until it's time to sync
571 0 : if (!m_mn_sync.IsBlockchainSynced()) return false;
572 :
573 0 : LOCK(cs_store);
574 :
575 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest inv = %s\n", inv.ToString());
576 :
577 : // First check if we've already recorded this object
578 0 : switch (inv.type) {
579 : case MSG_GOVERNANCE_OBJECT: {
580 0 : if (mapObjects.count(inv.hash) == 1 || mapPostponedObjects.count(inv.hash) == 1) {
581 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest already have governance object, returning false\n");
582 0 : return false;
583 : }
584 0 : break;
585 : }
586 : case MSG_GOVERNANCE_OBJECT_VOTE: {
587 0 : if (cmapVoteToObject.HasKey(inv.hash)) {
588 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest already have governance vote, returning false\n");
589 0 : return false;
590 : }
591 0 : break;
592 : }
593 : default:
594 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest unknown type, returning false\n");
595 0 : return false;
596 : }
597 :
598 0 : const auto valid_until = GetTime<std::chrono::seconds>() + RELIABLE_PROPAGATION_TIME;
599 0 : const auto& [_itr, inserted] = m_requested_hash_time.emplace(inv.hash, valid_until);
600 :
601 0 : if (inserted) {
602 0 : LogPrint(BCLog::GOBJECT, /* Continued */
603 : "CGovernanceManager::ConfirmInventoryRequest added %s inv hash to m_requested_hash_time, size=%d\n",
604 : inv.type == MSG_GOVERNANCE_OBJECT ? "object" : "vote", m_requested_hash_time.size());
605 0 : }
606 :
607 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest reached end, returning true\n");
608 0 : return true;
609 0 : }
610 :
611 0 : std::vector<CInv> CGovernanceManager::GetSyncableVoteInvs(const uint256& nProp, const CBloomFilter& filter) const
612 : {
613 0 : LOCK(cs_store);
614 :
615 0 : auto it = mapObjects.find(nProp);
616 0 : if (it == mapObjects.end()) {
617 0 : return {};
618 : }
619 :
620 0 : const auto& govobj = *Assert(it->second);
621 0 : if (govobj.IsSetCachedDelete() || govobj.IsSetExpired()) {
622 0 : return {};
623 : }
624 :
625 0 : std::vector<CInv> invs;
626 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
627 :
628 0 : LOCK(govobj.cs);
629 0 : const auto& fileVotes = govobj.GetVoteFile();
630 0 : for (const auto& vote : fileVotes.GetVotes()) {
631 0 : uint256 nVoteHash = vote.GetHash();
632 :
633 0 : bool onlyVotingKeyAllowed = govobj.GetObjectType() == GovernanceObject::PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING;
634 :
635 0 : if (filter.contains(nVoteHash) || !vote.IsValid(tip_mn_list, onlyVotingKeyAllowed)) {
636 0 : continue;
637 : }
638 0 : invs.emplace_back(MSG_GOVERNANCE_OBJECT_VOTE, nVoteHash);
639 : }
640 :
641 0 : return invs;
642 0 : }
643 :
644 0 : std::vector<CInv> CGovernanceManager::GetSyncableObjectInvs() const
645 : {
646 0 : LOCK(cs_store);
647 :
648 0 : std::vector<CInv> invs;
649 0 : invs.reserve(mapObjects.size());
650 :
651 0 : for (const auto& [nHash, govobj] : mapObjects) {
652 0 : if (Assert(govobj)->IsSetCachedDelete() || govobj->IsSetExpired()) {
653 0 : continue;
654 : }
655 0 : invs.emplace_back(MSG_GOVERNANCE_OBJECT, nHash);
656 : }
657 :
658 0 : return invs;
659 0 : }
660 :
661 0 : void CGovernanceManager::MasternodeRateUpdate(const CGovernanceObject& govobj)
662 : {
663 0 : AssertLockHeld(cs_store);
664 :
665 0 : if (govobj.GetObjectType() != GovernanceObject::TRIGGER) return;
666 :
667 0 : const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint();
668 0 : auto it = mapLastMasternodeObject.find(masternodeOutpoint);
669 :
670 0 : if (it == mapLastMasternodeObject.end()) {
671 0 : it = mapLastMasternodeObject.insert(txout_m_t::value_type(masternodeOutpoint, last_object_rec(true))).first;
672 0 : }
673 :
674 0 : int64_t nTimestamp = govobj.GetCreationTime();
675 0 : it->second.triggerBuffer.AddTimestamp(nTimestamp);
676 :
677 0 : if (nTimestamp > GetTime() + count_seconds(MAX_TIME_FUTURE_DEVIATION) - count_seconds(RELIABLE_PROPAGATION_TIME)) {
678 : // schedule additional relay for the object
679 0 : setAdditionalRelayObjects.insert(govobj.GetHash());
680 0 : }
681 :
682 0 : it->second.fStatusOK = true;
683 0 : }
684 :
685 0 : bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateFailStatus)
686 : {
687 0 : LOCK(cs_store);
688 : bool fRateCheckBypassed;
689 0 : return MasternodeRateCheck(govobj, fUpdateFailStatus, true, fRateCheckBypassed);
690 0 : }
691 :
692 0 : bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateFailStatus, bool fForce, bool& fRateCheckBypassed)
693 : {
694 0 : AssertLockHeld(cs_store);
695 :
696 0 : fRateCheckBypassed = false;
697 :
698 0 : if (!m_mn_sync.IsSynced() || !fRateChecksEnabled) {
699 0 : return true;
700 : }
701 :
702 0 : if (govobj.GetObjectType() != GovernanceObject::TRIGGER) {
703 0 : return true;
704 : }
705 :
706 0 : const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint();
707 0 : int64_t nTimestamp = govobj.GetCreationTime();
708 0 : int64_t nNow = GetAdjustedTime();
709 0 : int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing;
710 :
711 0 : std::string strHash = govobj.GetHash().ToString();
712 :
713 0 : if (nTimestamp < nNow - 2 * nSuperblockCycleSeconds) {
714 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too old timestamp, masternode = %s, timestamp = %d, current time = %d\n",
715 : strHash, masternodeOutpoint.ToStringShort(), nTimestamp, nNow);
716 0 : return false;
717 : }
718 :
719 0 : if (nTimestamp > nNow + count_seconds(MAX_TIME_FUTURE_DEVIATION)) {
720 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too new (future) timestamp, masternode = %s, timestamp = %d, current time = %d\n",
721 : strHash, masternodeOutpoint.ToStringShort(), nTimestamp, nNow);
722 0 : return false;
723 : }
724 :
725 0 : auto it = mapLastMasternodeObject.find(masternodeOutpoint);
726 0 : if (it == mapLastMasternodeObject.end()) return true;
727 :
728 0 : if (it->second.fStatusOK && !fForce) {
729 0 : fRateCheckBypassed = true;
730 0 : return true;
731 : }
732 :
733 : // Allow 1 trigger per mn per cycle, with a small fudge factor
734 0 : double dMaxRate = 2 * 1.1 / double(nSuperblockCycleSeconds);
735 :
736 : // Temporary copy to check rate after new timestamp is added
737 0 : CRateCheckBuffer buffer = it->second.triggerBuffer;
738 :
739 0 : buffer.AddTimestamp(nTimestamp);
740 0 : double dRate = buffer.GetRate();
741 :
742 0 : if (dRate < dMaxRate) {
743 0 : return true;
744 : }
745 :
746 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::MasternodeRateCheck -- Rate too high: object hash = %s, masternode = %s, object timestamp = %d, rate = %f, max rate = %f\n",
747 : strHash, masternodeOutpoint.ToStringShort(), nTimestamp, dRate, dMaxRate);
748 :
749 0 : if (fUpdateFailStatus) {
750 0 : it->second.fStatusOK = false;
751 0 : }
752 :
753 0 : return false;
754 0 : }
755 :
756 0 : bool CGovernanceManager::ProcessVoteAndRelay(const CGovernanceVote& vote, CGovernanceException& exception, CConnman& connman)
757 : {
758 0 : AssertLockNotHeld(cs_store);
759 0 : AssertLockNotHeld(cs_relay);
760 0 : uint256 hashToRequest; // Ignored for local votes (no peer to request from)
761 0 : bool fOK = ProcessVote(vote, exception, hashToRequest);
762 0 : if (fOK) {
763 0 : RelayVote(vote);
764 0 : }
765 0 : return fOK;
766 : }
767 :
768 0 : bool CGovernanceManager::ProcessVote(const CGovernanceVote& vote, CGovernanceException& exception,
769 : uint256& hashToRequest)
770 : {
771 0 : AssertLockNotHeld(cs_store);
772 0 : hashToRequest = uint256{};
773 :
774 0 : LOCK(cs_store);
775 0 : uint256 nHashVote = vote.GetHash();
776 0 : uint256 nHashGovobj = vote.GetParentHash();
777 :
778 0 : if (cmapVoteToObject.HasKey(nHashVote)) {
779 0 : LogPrint(BCLog::GOBJECT, "CGovernanceObject::%s -- skipping known valid vote %s for object %s\n", __func__,
780 : nHashVote.ToString(), nHashGovobj.ToString());
781 0 : return false;
782 : }
783 :
784 0 : if (cmapInvalidVotes.HasKey(nHashVote)) {
785 0 : std::string msg{strprintf("CGovernanceManager::%s -- Old invalid vote, MN outpoint = %s, governance object hash = %s",
786 0 : __func__, vote.GetMasternodeOutpoint().ToStringShort(), nHashGovobj.ToString())};
787 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
788 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20);
789 0 : return false;
790 0 : }
791 :
792 0 : auto it = mapObjects.find(nHashGovobj);
793 0 : if (it == mapObjects.end()) {
794 0 : std::string msg{strprintf("CGovernanceManager::%s -- Unknown parent object %s, MN outpoint = %s", __func__,
795 0 : nHashGovobj.ToString(), vote.GetMasternodeOutpoint().ToStringShort())};
796 0 : exception = CGovernanceException(msg, GOVERNANCE_EXCEPTION_WARNING);
797 0 : if (cmmapOrphanVotes.Insert(nHashGovobj, vote_time_pair_t(vote, count_seconds(GetTime<std::chrono::seconds>() +
798 : GOVERNANCE_ORPHAN_EXPIRATION_TIME)))) {
799 0 : hashToRequest = nHashGovobj; // Caller should request this object
800 0 : }
801 0 : LogPrint(BCLog::GOBJECT, "%s\n", msg);
802 0 : return false;
803 0 : }
804 :
805 0 : auto& govobj = *Assert(it->second);
806 :
807 0 : if (govobj.IsSetCachedDelete() || govobj.IsSetExpired()) {
808 0 : LogPrint(BCLog::GOBJECT, "CGovernanceObject::%s -- ignoring vote for expired or deleted object, hash = %s\n",
809 : __func__, nHashGovobj.ToString());
810 0 : return false;
811 : }
812 :
813 0 : bool fOk = govobj.ProcessVote(m_mn_metaman, fRateChecksEnabled, m_dmnman.GetListAtChainTip(), vote, exception);
814 0 : if (fOk) {
815 0 : fOk = cmapVoteToObject.Insert(nHashVote, it->second);
816 0 : } else if (exception.GetType() == GOVERNANCE_EXCEPTION_PERMANENT_ERROR && exception.GetNodePenalty() == 20) {
817 0 : cmapInvalidVotes.Insert(nHashVote, vote);
818 0 : }
819 0 : return fOk;
820 0 : }
821 :
822 0 : void CGovernanceManager::CheckPostponedObjects()
823 : {
824 0 : AssertLockHeld(::cs_main);
825 0 : AssertLockHeld(cs_store);
826 0 : AssertLockNotHeld(cs_relay);
827 0 : if (!m_mn_sync.IsSynced()) return;
828 :
829 : // Check postponed proposals
830 0 : for (auto it = mapPostponedObjects.begin(); it != mapPostponedObjects.end();) {
831 0 : const uint256& nHash = it->first;
832 0 : auto& govobj = *Assert(it->second);
833 :
834 0 : assert(govobj.GetObjectType() != GovernanceObject::TRIGGER);
835 :
836 0 : std::string strError;
837 : bool fMissingConfirmations;
838 0 : if (govobj.IsCollateralValid(m_chainman, strError, fMissingConfirmations)) {
839 0 : if (govobj.IsValidLocally(m_dmnman.GetListAtChainTip(), m_chainman, strError, false)) {
840 0 : AddGovernanceObjectInternal(govobj, "<postponed>");
841 0 : } else {
842 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::CheckPostponedObjects -- %s invalid\n", nHash.ToString());
843 : }
844 :
845 0 : } else if (fMissingConfirmations) {
846 : // wait for more confirmations
847 0 : ++it;
848 0 : continue;
849 : }
850 :
851 : // remove processed or invalid object from the queue
852 0 : mapPostponedObjects.erase(it++);
853 0 : }
854 :
855 :
856 : // Perform additional relays for triggers
857 0 : int64_t nNow = GetAdjustedTime();
858 0 : int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing;
859 :
860 0 : for (auto it = setAdditionalRelayObjects.begin(); it != setAdditionalRelayObjects.end();) {
861 0 : auto itObject = mapObjects.find(*it);
862 0 : if (itObject != mapObjects.end()) {
863 0 : const auto& govobj = *Assert(itObject->second);
864 :
865 0 : int64_t nTimestamp = govobj.GetCreationTime();
866 :
867 0 : bool fValid = (nTimestamp <= nNow + count_seconds(MAX_TIME_FUTURE_DEVIATION)) &&
868 0 : (nTimestamp >= nNow - 2 * nSuperblockCycleSeconds);
869 0 : bool fReady = (nTimestamp <=
870 0 : nNow + count_seconds(MAX_TIME_FUTURE_DEVIATION) - count_seconds(RELIABLE_PROPAGATION_TIME));
871 :
872 0 : if (fValid) {
873 0 : if (fReady) {
874 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::CheckPostponedObjects -- additional relay: hash = %s\n", govobj.GetHash().ToString());
875 0 : RelayObject(govobj);
876 0 : } else {
877 0 : it++;
878 0 : continue;
879 : }
880 0 : }
881 :
882 0 : } else {
883 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::CheckPostponedObjects -- additional relay of unknown object: %s\n", it->ToString());
884 : }
885 :
886 0 : setAdditionalRelayObjects.erase(it++);
887 : }
888 0 : }
889 :
890 0 : CBloomFilter CGovernanceManager::GetVoteBloomFilter(const uint256& nHash) const
891 : {
892 0 : LOCK(cs_store);
893 :
894 0 : auto pObj = FindConstGovernanceObjectInternal(nHash);
895 0 : if (!pObj) {
896 0 : return CBloomFilter{};
897 : }
898 :
899 0 : CBloomFilter filter(Params().GetConsensus().nGovernanceFilterElements, GOVERNANCE_FILTER_FP_RATE,
900 0 : GetRand<int>(/*nMax=*/999999), BLOOM_UPDATE_ALL);
901 :
902 0 : std::vector<CGovernanceVote> vecVotes = WITH_LOCK(pObj->cs, return pObj->GetVoteFile().GetVotes());
903 0 : for (const auto& vote : vecVotes) {
904 0 : filter.insert(vote.GetHash());
905 : }
906 :
907 0 : return filter;
908 0 : }
909 :
910 0 : CDeterministicMNManager& CGovernanceManager::GetMNManager() { return m_dmnman; }
911 :
912 0 : std::pair<std::vector<uint256>, std::vector<uint256>> CGovernanceManager::FetchGovernanceObjectVotes(
913 : size_t nPeersPerHashMax, int64_t nNow, std::map<uint256, std::map<CService, int64_t>>& mapAskedRecently) const
914 : {
915 0 : std::vector<uint256> vTriggerObjHashes;
916 0 : std::vector<uint256> vOtherObjHashes;
917 : {
918 0 : LOCK(cs_store);
919 :
920 0 : for (const auto& [nHash, govobj] : mapObjects) {
921 0 : if (Assert(govobj)->IsSetCachedDelete()) continue;
922 0 : if (mapAskedRecently.count(nHash)) {
923 0 : for (auto it = mapAskedRecently[nHash].begin(); it != mapAskedRecently[nHash].end();) {
924 0 : if (it->second < nNow) {
925 0 : mapAskedRecently[nHash].erase(it++);
926 0 : } else {
927 0 : ++it;
928 : }
929 : }
930 0 : if (mapAskedRecently[nHash].size() >= nPeersPerHashMax) continue;
931 0 : }
932 :
933 0 : if (govobj->GetObjectType() == GovernanceObject::TRIGGER) {
934 0 : vTriggerObjHashes.push_back(nHash);
935 0 : } else {
936 0 : vOtherObjHashes.push_back(nHash);
937 : }
938 : }
939 0 : }
940 0 : return {vTriggerObjHashes, vOtherObjHashes};
941 0 : }
942 :
943 0 : bool CGovernanceManager::AcceptMessage(const uint256& nHash)
944 : {
945 0 : AssertLockNotHeld(cs_store);
946 0 : LOCK(cs_store);
947 0 : auto it = m_requested_hash_time.find(nHash);
948 0 : if (it == m_requested_hash_time.end()) {
949 : // We never requested this
950 0 : return false;
951 : }
952 : // Only accept one response
953 0 : m_requested_hash_time.erase(it);
954 0 : return true;
955 0 : }
956 :
957 0 : void CGovernanceManager::RebuildIndexes()
958 : {
959 0 : AssertLockHeld(cs_store);
960 :
961 0 : cmapVoteToObject.Clear();
962 0 : for (auto& [_, govobj] : mapObjects) {
963 0 : assert(govobj);
964 0 : std::vector<CGovernanceVote> vecVotes = WITH_LOCK(govobj->cs, return govobj->GetVoteFile().GetVotes());
965 0 : for (const auto& vecVote : vecVotes) {
966 0 : cmapVoteToObject.Insert(vecVote.GetHash(), govobj);
967 : }
968 0 : }
969 0 : }
970 :
971 0 : void CGovernanceManager::InitOnLoad()
972 : {
973 : {
974 0 : LOCK(cs_store);
975 0 : const auto start{SteadyClock::now()};
976 0 : LogPrintf("Preparing masternode indexes and governance triggers...\n");
977 0 : RebuildIndexes();
978 :
979 0 : const int64_t nNow = GetTime<std::chrono::seconds>().count();
980 0 : for (auto& [_, govobj] : mapObjects) {
981 0 : if (Assert(govobj)->GetObjectType() != GovernanceObject::TRIGGER) continue;
982 0 : if (!m_superblocks.AddTrigger(govobj, nCachedBlockHeight)) {
983 0 : govobj->PrepareDeletion(nNow);
984 0 : }
985 : }
986 0 : LogPrintf("Masternode indexes and governance triggers prepared %dms\n",
987 : Ticks<std::chrono::milliseconds>(SteadyClock::now() - start));
988 0 : }
989 0 : LogPrintf(" %s\n", ToString());
990 0 : }
991 :
992 0 : void GovernanceStore::Clear()
993 : {
994 0 : LOCK(cs_store);
995 0 : mapObjects.clear();
996 0 : mapErasedGovernanceObjects.clear();
997 0 : cmapInvalidVotes.Clear();
998 0 : cmmapOrphanVotes.Clear();
999 0 : mapLastMasternodeObject.clear();
1000 0 : lastMNListForVotingKeys = std::make_shared<CDeterministicMNList>();
1001 0 : }
1002 :
1003 0 : void CGovernanceManager::Clear()
1004 : {
1005 0 : AssertLockNotHeld(cs_store);
1006 0 : LogPrint(BCLog::GOBJECT, "Governance object manager was cleared\n");
1007 0 : GovernanceStore::Clear();
1008 0 : nTimeLastDiff = 0;
1009 0 : nCachedBlockHeight = 0;
1010 0 : cmapVoteToObject.Clear();
1011 0 : mapPostponedObjects.clear();
1012 0 : setAdditionalRelayObjects.clear();
1013 0 : m_requested_hash_time.clear();
1014 0 : fRateChecksEnabled = true;
1015 0 : m_superblocks.Clear();
1016 0 : }
1017 :
1018 0 : std::string GovernanceStore::ToString() const
1019 : {
1020 0 : LOCK(cs_store);
1021 :
1022 0 : int nProposalCount = 0;
1023 0 : int nTriggerCount = 0;
1024 0 : int nOtherCount = 0;
1025 :
1026 0 : for (const auto& [_, govobj] : mapObjects) {
1027 0 : switch (Assert(govobj)->GetObjectType()) {
1028 : case GovernanceObject::PROPOSAL:
1029 0 : nProposalCount++;
1030 0 : break;
1031 : case GovernanceObject::TRIGGER:
1032 0 : nTriggerCount++;
1033 0 : break;
1034 : default:
1035 0 : nOtherCount++;
1036 0 : break;
1037 : }
1038 : }
1039 :
1040 0 : return strprintf("Governance Objects: %d (Proposals: %d, Triggers: %d, Other: %d; Erased: %d)",
1041 0 : (int)mapObjects.size(),
1042 0 : nProposalCount, nTriggerCount, nOtherCount, (int)mapErasedGovernanceObjects.size());
1043 0 : }
1044 :
1045 0 : std::string CGovernanceManager::ToString() const
1046 : {
1047 0 : AssertLockNotHeld(cs_store);
1048 0 : return strprintf("%s, Votes: %d", GovernanceStore::ToString(), (int)cmapVoteToObject.GetSize());
1049 0 : }
1050 :
1051 0 : void CGovernanceManager::UpdatedBlockTip(const CBlockIndex* pindex)
1052 : {
1053 0 : AssertLockNotHeld(cs_store);
1054 0 : AssertLockNotHeld(cs_relay);
1055 : // Note this gets called from ActivateBestChain without cs_main being held
1056 : // so it should be safe to lock our mutex here without risking a deadlock
1057 : // On the other hand it should be safe for us to access pindex without holding a lock
1058 : // on cs_main because the CBlockIndex objects are dynamically allocated and
1059 : // presumably never deleted.
1060 0 : if (!pindex) {
1061 0 : return;
1062 : }
1063 :
1064 0 : nCachedBlockHeight = pindex->nHeight;
1065 0 : LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight);
1066 :
1067 0 : LOCK2(::cs_main, cs_store);
1068 0 : if (DeploymentDIP0003Enforced(pindex->nHeight, Params().GetConsensus())) {
1069 0 : RemoveInvalidVotes();
1070 0 : }
1071 :
1072 0 : CheckPostponedObjects();
1073 :
1074 0 : m_superblocks.ExecuteBestSuperblock(m_dmnman.GetListAtChainTip(), pindex->nHeight);
1075 0 : }
1076 :
1077 0 : std::vector<uint256> CGovernanceManager::GetOrphanVoteObjectHashes()
1078 : {
1079 0 : LOCK(cs_store);
1080 :
1081 0 : int64_t nNow = GetTime<std::chrono::seconds>().count();
1082 :
1083 : // Clean up expired orphan votes
1084 0 : const vote_cmm_t::list_t& items = cmmapOrphanVotes.GetItemList();
1085 0 : for (auto it = items.begin(); it != items.end();) {
1086 0 : auto prevIt = it;
1087 0 : ++it;
1088 0 : const auto& [_, time] = prevIt->value;
1089 0 : if (time < nNow) {
1090 0 : cmmapOrphanVotes.Erase(prevIt->key, prevIt->value);
1091 0 : }
1092 : }
1093 :
1094 : // Get hashes of objects we don't have yet
1095 0 : std::vector<uint256> vecHashesFiltered;
1096 0 : std::vector<uint256> vecHashes;
1097 0 : cmmapOrphanVotes.GetKeys(vecHashes);
1098 0 : for (const uint256& nHash : vecHashes) {
1099 0 : if (mapObjects.find(nHash) == mapObjects.end()) {
1100 0 : vecHashesFiltered.push_back(nHash);
1101 0 : }
1102 : }
1103 :
1104 0 : return vecHashesFiltered;
1105 0 : }
1106 :
1107 0 : void CGovernanceManager::RemoveInvalidVotes()
1108 : {
1109 0 : AssertLockHeld(cs_store);
1110 :
1111 0 : if (!m_mn_sync.IsSynced()) {
1112 0 : return;
1113 : }
1114 :
1115 0 : const auto tip_mn_list = m_dmnman.GetListAtChainTip();
1116 0 : auto diff = lastMNListForVotingKeys->BuildDiff(tip_mn_list);
1117 :
1118 0 : std::vector<COutPoint> changedKeyMNs;
1119 0 : for (const auto& [internalId, pdmnState] : diff.updatedMNs) {
1120 0 : auto oldDmn = lastMNListForVotingKeys->GetMNByInternalId(internalId);
1121 : // BuildDiff will construct itself with MNs that we already have knowledge
1122 : // of, meaning that fetch operations should never fail.
1123 0 : assert(oldDmn);
1124 0 : if ((pdmnState.fields & CDeterministicMNStateDiff::Field_keyIDVoting) && pdmnState.state.keyIDVoting != oldDmn->pdmnState->keyIDVoting) {
1125 0 : changedKeyMNs.emplace_back(oldDmn->collateralOutpoint);
1126 0 : } else if ((pdmnState.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) && pdmnState.state.pubKeyOperator != oldDmn->pdmnState->pubKeyOperator) {
1127 0 : changedKeyMNs.emplace_back(oldDmn->collateralOutpoint);
1128 0 : }
1129 0 : }
1130 0 : for (const auto& id : diff.removedMns) {
1131 0 : auto oldDmn = lastMNListForVotingKeys->GetMNByInternalId(id);
1132 : // BuildDiff will construct itself with MNs that we already have knowledge
1133 : // of, meaning that fetch operations should never fail.
1134 0 : assert(oldDmn);
1135 0 : changedKeyMNs.emplace_back(oldDmn->collateralOutpoint);
1136 0 : }
1137 :
1138 0 : for (const auto& outpoint : changedKeyMNs) {
1139 0 : for (auto& [_, govobj] : mapObjects) {
1140 0 : auto removed = Assert(govobj)->RemoveInvalidVotes(tip_mn_list, outpoint);
1141 0 : if (removed.empty()) {
1142 0 : continue;
1143 : }
1144 0 : for (auto& voteHash : removed) {
1145 0 : cmapVoteToObject.Erase(voteHash);
1146 0 : cmapInvalidVotes.Erase(voteHash);
1147 0 : cmmapOrphanVotes.Erase(voteHash);
1148 0 : m_requested_hash_time.erase(voteHash);
1149 : }
1150 0 : }
1151 : }
1152 :
1153 : // store current MN list for the next run so that we can determine which keys changed
1154 0 : lastMNListForVotingKeys = std::make_shared<CDeterministicMNList>(tip_mn_list);
1155 0 : }
1156 :
1157 0 : std::vector<std::shared_ptr<const CGovernanceObject>> CGovernanceManager::GetApprovedProposals(
1158 : const CDeterministicMNList& tip_mn_list)
1159 : {
1160 0 : AssertLockNotHeld(cs_store);
1161 :
1162 : // Use std::vector of std::shared_ptr<const CGovernanceObject> because CGovernanceObject doesn't support move operations (needed for sorting the vector later)
1163 0 : std::vector<std::shared_ptr<const CGovernanceObject>> ret{};
1164 :
1165 : // A proposal is considered passing if (YES votes) >= (Total Weight of Masternodes / 10),
1166 : // count total valid (ENABLED) masternodes to determine passing threshold.
1167 0 : const int nWeightedMnCount = tip_mn_list.GetCounts().m_valid_weighted;
1168 0 : const int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nWeightedMnCount / 10);
1169 :
1170 0 : LOCK(cs_store);
1171 0 : for (const auto& [_, govobj] : mapObjects) {
1172 : // Skip all non-proposals objects
1173 0 : if (Assert(govobj)->GetObjectType() != GovernanceObject::PROPOSAL) continue;
1174 : // Skip non-passing proposals
1175 0 : const int absYesCount = govobj->GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING);
1176 0 : if (absYesCount < nAbsVoteReq) continue;
1177 0 : ret.emplace_back(govobj);
1178 : }
1179 :
1180 : // Sort approved proposals by absolute Yes votes descending
1181 0 : std::sort(ret.begin(), ret.end(), [&tip_mn_list](auto& lhs, auto& rhs) {
1182 0 : const auto lhs_yes = lhs->GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING);
1183 0 : const auto rhs_yes = rhs->GetAbsoluteYesCount(tip_mn_list, VOTE_SIGNAL_FUNDING);
1184 0 : return lhs_yes == rhs_yes ? UintToArith256(lhs->GetHash()) > UintToArith256(rhs->GetHash()) : lhs_yes > rhs_yes;
1185 : });
1186 :
1187 0 : return ret;
1188 0 : }
|