// Copyright (c) 2014-2017 The Dash Core developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "governance.h" #include "governance-object.h" #include "governance-vote.h" #include "governance-classes.h" #include "net_processing.h" #include "netmessagemaker.h" #include "masternode.h" #include "masternode-sync.h" #include "masternodeman.h" #include "messagesigner.h" #include "netfulfilledman.h" #include "util.h" CGovernanceManager governance; int nSubmittedFinalBudget; const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-12"; const int CGovernanceManager::MAX_TIME_FUTURE_DEVIATION = 60*60; const int CGovernanceManager::RELIABLE_PROPAGATION_TIME = 60; CGovernanceManager::CGovernanceManager() : nTimeLastDiff(0), nCachedBlockHeight(0), mapObjects(), mapErasedGovernanceObjects(), mapMasternodeOrphanObjects(), mapWatchdogObjects(), nHashWatchdogCurrent(), nTimeWatchdogCurrent(0), cmapVoteToObject(MAX_CACHE_SIZE), cmapInvalidVotes(MAX_CACHE_SIZE), cmmapOrphanVotes(MAX_CACHE_SIZE), mapLastMasternodeObject(), setRequestedObjects(), fRateChecksEnabled(true), cs() {} // Accessors for thread-safe access to maps bool CGovernanceManager::HaveObjectForHash(uint256 nHash) { LOCK(cs); return (mapObjects.count(nHash) == 1 || mapPostponedObjects.count(nHash) == 1); } bool CGovernanceManager::SerializeObjectForHash(uint256 nHash, CDataStream& ss) { LOCK(cs); object_m_it it = mapObjects.find(nHash); if (it == mapObjects.end()) { it = mapPostponedObjects.find(nHash); if (it == mapPostponedObjects.end()) return false; } ss << it->second; return true; } bool CGovernanceManager::HaveVoteForHash(uint256 nHash) { LOCK(cs); CGovernanceObject* pGovobj = NULL; if(!cmapVoteToObject.Get(nHash,pGovobj)) { return false; } if(!pGovobj->GetVoteFile().HasVote(nHash)) { return false; } return true; } int CGovernanceManager::GetVoteCount() const { LOCK(cs); return (int)cmapVoteToObject.GetSize(); } bool CGovernanceManager::SerializeVoteForHash(uint256 nHash, CDataStream& ss) { LOCK(cs); CGovernanceObject* pGovobj = NULL; if(!cmapVoteToObject.Get(nHash,pGovobj)) { return false; } if(!pGovobj->GetVoteFile().SerializeVoteToStream(nHash, ss)) { return false; } return true; } void CGovernanceManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman) { // lite mode is not supported if(fLiteMode) return; if(!masternodeSync.IsBlockchainSynced()) return; if(pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) return; // ANOTHER USER IS ASKING US TO HELP THEM SYNC GOVERNANCE OBJECT DATA if (strCommand == NetMsgType::MNGOVERNANCESYNC) { // Ignore such requests until we are fully synced. // We could start processing this after masternode list is synced // but this is a heavy one so it's better to finish sync first. if (!masternodeSync.IsSynced()) return; uint256 nProp; CBloomFilter filter; vRecv >> nProp; if(pfrom->nVersion >= GOVERNANCE_FILTER_PROTO_VERSION) { vRecv >> filter; filter.UpdateEmptyFull(); } else { filter.clear(); } if(nProp == uint256()) { SyncAll(pfrom, connman); } else { SyncSingleObjAndItsVotes(pfrom, nProp, filter, connman); } LogPrint("gobject", "MNGOVERNANCESYNC -- syncing governance objects to our peer at %s\n", pfrom->addr.ToString()); } // A NEW GOVERNANCE OBJECT HAS ARRIVED else if (strCommand == NetMsgType::MNGOVERNANCEOBJECT) { // MAKE SURE WE HAVE A VALID REFERENCE TO THE TIP BEFORE CONTINUING CGovernanceObject govobj; vRecv >> govobj; uint256 nHash = govobj.GetHash(); pfrom->setAskFor.erase(nHash); if(!masternodeSync.IsMasternodeListSynced()) { LogPrint("gobject", "MNGOVERNANCEOBJECT -- masternode list not synced\n"); return; } std::string strHash = nHash.ToString(); LogPrint("gobject", "MNGOVERNANCEOBJECT -- Received object: %s\n", strHash); if(!AcceptObjectMessage(nHash)) { LogPrintf("MNGOVERNANCEOBJECT -- Received unrequested object: %s\n", strHash); return; } LOCK2(cs_main, cs); if(mapObjects.count(nHash) || mapPostponedObjects.count(nHash) || mapErasedGovernanceObjects.count(nHash) || mapMasternodeOrphanObjects.count(nHash)) { // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE? LogPrint("gobject", "MNGOVERNANCEOBJECT -- Received already seen object: %s\n", strHash); return; } bool fRateCheckBypassed = false; if(!MasternodeRateCheck(govobj, true, false, fRateCheckBypassed)) { LogPrintf("MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", strHash, nCachedBlockHeight); return; } std::string strError = ""; // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN bool fMasternodeMissing = false; bool fMissingConfirmations = false; bool fIsValid = govobj.IsValidLocally(strError, fMasternodeMissing, fMissingConfirmations, true); if(fRateCheckBypassed && (fIsValid || fMasternodeMissing)) { if(!MasternodeRateCheck(govobj, true)) { LogPrintf("MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - %s - (current block height %d) \n", strHash, nCachedBlockHeight); return; } } if(!fIsValid) { if(fMasternodeMissing) { int& count = mapMasternodeOrphanCounter[govobj.GetMasternodeOutpoint()]; if (count >= 10) { LogPrint("gobject", "MNGOVERNANCEOBJECT -- Too many orphan objects, missing masternode=%s\n", govobj.GetMasternodeOutpoint().ToStringShort()); // ask for this object again in 2 minutes CInv inv(MSG_GOVERNANCE_OBJECT, govobj.GetHash()); pfrom->AskFor(inv); return; } count++; ExpirationInfo info(pfrom->GetId(), GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME); mapMasternodeOrphanObjects.insert(std::make_pair(nHash, object_info_pair_t(govobj, info))); LogPrintf("MNGOVERNANCEOBJECT -- Missing masternode for: %s, strError = %s\n", strHash, strError); } else if(fMissingConfirmations) { AddPostponedObject(govobj); LogPrintf("MNGOVERNANCEOBJECT -- Not enough fee confirmations for: %s, strError = %s\n", strHash, strError); } else { LogPrintf("MNGOVERNANCEOBJECT -- Governance object is invalid - %s\n", strError); // apply node's ban score Misbehaving(pfrom->GetId(), 20); } return; } AddGovernanceObject(govobj, connman, pfrom); } // A NEW GOVERNANCE OBJECT VOTE HAS ARRIVED else if (strCommand == NetMsgType::MNGOVERNANCEOBJECTVOTE) { CGovernanceVote vote; vRecv >> vote; uint256 nHash = vote.GetHash(); pfrom->setAskFor.erase(nHash); // Ignore such messages until masternode list is synced if(!masternodeSync.IsMasternodeListSynced()) { LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- masternode list not synced\n"); return; } LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- Received vote: %s\n", vote.ToString()); std::string strHash = nHash.ToString(); if(!AcceptVoteMessage(nHash)) { LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- Received unrequested vote object: %s, hash: %s, peer = %d\n", vote.ToString(), strHash, pfrom->GetId()); return; } CGovernanceException exception; if(ProcessVote(pfrom, vote, exception, connman)) { LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- %s new\n", strHash); masternodeSync.BumpAssetLastTime("MNGOVERNANCEOBJECTVOTE"); vote.Relay(connman); } else { LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- Rejected vote, error = %s\n", exception.what()); if((exception.GetNodePenalty() != 0) && masternodeSync.IsSynced()) { Misbehaving(pfrom->GetId(), exception.GetNodePenalty()); } return; } } } void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj, CGovernanceException& exception, CConnman& connman) { uint256 nHash = govobj.GetHash(); std::vector vecVotePairs; cmmapOrphanVotes.GetAll(nHash, vecVotePairs); ScopedLockBool guard(cs, fRateChecksEnabled, false); int64_t nNow = GetAdjustedTime(); for(size_t i = 0; i < vecVotePairs.size(); ++i) { bool fRemove = false; vote_time_pair_t& pairVote = vecVotePairs[i]; CGovernanceVote& vote = pairVote.first; CGovernanceException exception; if(pairVote.second < nNow) { fRemove = true; } else if(govobj.ProcessVote(NULL, vote, exception, connman)) { vote.Relay(connman); fRemove = true; } if(fRemove) { cmmapOrphanVotes.Erase(nHash, pairVote); } } } void CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj, CConnman& connman, CNode* pfrom) { DBG( cout << "CGovernanceManager::AddGovernanceObject START" << endl; ); uint256 nHash = govobj.GetHash(); std::string strHash = nHash.ToString(); // UPDATE CACHED VARIABLES FOR THIS OBJECT AND ADD IT TO OUR MANANGED DATA govobj.UpdateSentinelVariables(); //this sets local vars in object LOCK2(cs_main, cs); std::string strError = ""; // MAKE SURE THIS OBJECT IS OK if(!govobj.IsValidLocally(strError, true)) { LogPrintf("CGovernanceManager::AddGovernanceObject -- invalid governance object - %s - (nCachedBlockHeight %d) \n", strError, nCachedBlockHeight); return; } // IF WE HAVE THIS OBJECT ALREADY, WE DON'T WANT ANOTHER COPY if(mapObjects.count(nHash)) { LogPrintf("CGovernanceManager::AddGovernanceObject -- already have governance object %s\n", nHash.ToString()); return; } LogPrint("gobject", "CGovernanceManager::AddGovernanceObject -- Adding object: hash = %s, type = %d\n", nHash.ToString(), govobj.GetObjectType()); if(govobj.nObjectType == GOVERNANCE_OBJECT_WATCHDOG) { // If it's a watchdog, make sure it fits required time bounds if((govobj.GetCreationTime() < GetAdjustedTime() - GOVERNANCE_WATCHDOG_EXPIRATION_TIME || govobj.GetCreationTime() > GetAdjustedTime() + GOVERNANCE_WATCHDOG_EXPIRATION_TIME) ) { // drop it LogPrint("gobject", "CGovernanceManager::AddGovernanceObject -- CreationTime is out of bounds: hash = %s\n", nHash.ToString()); return; } if(!UpdateCurrentWatchdog(govobj)) { // Allow wd's which are not current to be reprocessed if(pfrom && (nHashWatchdogCurrent != uint256())) { pfrom->PushInventory(CInv(MSG_GOVERNANCE_OBJECT, nHashWatchdogCurrent)); } LogPrint("gobject", "CGovernanceManager::AddGovernanceObject -- Watchdog not better than current: hash = %s\n", nHash.ToString()); return; } } // INSERT INTO OUR GOVERNANCE OBJECT MEMORY mapObjects.insert(std::make_pair(nHash, govobj)); // SHOULD WE ADD THIS OBJECT TO ANY OTHER MANANGERS? DBG( cout << "CGovernanceManager::AddGovernanceObject Before trigger block, GetDataAsPlainString = " << govobj.GetDataAsPlainString() << ", nObjectType = " << govobj.nObjectType << endl; ); switch(govobj.nObjectType) { case GOVERNANCE_OBJECT_TRIGGER: DBG( cout << "CGovernanceManager::AddGovernanceObject Before AddNewTrigger" << endl; ); triggerman.AddNewTrigger(nHash); DBG( cout << "CGovernanceManager::AddGovernanceObject After AddNewTrigger" << endl; ); break; case GOVERNANCE_OBJECT_WATCHDOG: mapWatchdogObjects[nHash] = govobj.GetCreationTime() + GOVERNANCE_WATCHDOG_EXPIRATION_TIME; LogPrint("gobject", "CGovernanceManager::AddGovernanceObject -- Added watchdog to map: hash = %s\n", nHash.ToString()); break; default: break; } LogPrintf("AddGovernanceObject -- %s new, received form %s\n", strHash, pfrom? pfrom->GetAddrName() : "NULL"); govobj.Relay(connman); // Update the rate buffer MasternodeRateUpdate(govobj); masternodeSync.BumpAssetLastTime("CGovernanceManager::AddGovernanceObject"); // WE MIGHT HAVE PENDING/ORPHAN VOTES FOR THIS OBJECT CGovernanceException exception; CheckOrphanVotes(govobj, exception, connman); DBG( cout << "CGovernanceManager::AddGovernanceObject END" << endl; ); } bool CGovernanceManager::UpdateCurrentWatchdog(CGovernanceObject& watchdogNew) { bool fAccept = false; arith_uint256 nHashNew = UintToArith256(watchdogNew.GetHash()); arith_uint256 nHashCurrent = UintToArith256(nHashWatchdogCurrent); int64_t nExpirationDelay = GOVERNANCE_WATCHDOG_EXPIRATION_TIME / 2; int64_t nNow = GetAdjustedTime(); if(nHashWatchdogCurrent == uint256() || // no known current OR ((nNow - watchdogNew.GetCreationTime() < nExpirationDelay) && // (new one is NOT expired AND ((nNow - nTimeWatchdogCurrent > nExpirationDelay) || (nHashNew > nHashCurrent)))// (current is expired OR // its hash is lower)) ) { LOCK(cs); object_m_it it = mapObjects.find(nHashWatchdogCurrent); if(it != mapObjects.end()) { LogPrint("gobject", "CGovernanceManager::UpdateCurrentWatchdog -- Expiring previous current watchdog, hash = %s\n", nHashWatchdogCurrent.ToString()); it->second.fExpired = true; if(it->second.nDeletionTime == 0) { it->second.nDeletionTime = nNow; } } nHashWatchdogCurrent = watchdogNew.GetHash(); nTimeWatchdogCurrent = watchdogNew.GetCreationTime(); fAccept = true; LogPrint("gobject", "CGovernanceManager::UpdateCurrentWatchdog -- Current watchdog updated to: hash = %s\n", ArithToUint256(nHashNew).ToString()); } return fAccept; } void CGovernanceManager::UpdateCachesAndClean() { LogPrint("gobject", "CGovernanceManager::UpdateCachesAndClean\n"); std::vector vecDirtyHashes = mnodeman.GetAndClearDirtyGovernanceObjectHashes(); LOCK2(cs_main, cs); // Flag expired watchdogs for removal int64_t nNow = GetAdjustedTime(); LogPrint("gobject", "CGovernanceManager::UpdateCachesAndClean -- Number watchdogs in map: %d, current time = %d\n", mapWatchdogObjects.size(), nNow); if(mapWatchdogObjects.size() > 1) { hash_time_m_it it = mapWatchdogObjects.begin(); while(it != mapWatchdogObjects.end()) { LogPrint("gobject", "CGovernanceManager::UpdateCachesAndClean -- Checking watchdog: %s, expiration time = %d\n", it->first.ToString(), it->second); if(it->second < nNow) { LogPrint("gobject", "CGovernanceManager::UpdateCachesAndClean -- Attempting to expire watchdog: %s, expiration time = %d\n", it->first.ToString(), it->second); object_m_it it2 = mapObjects.find(it->first); if(it2 != mapObjects.end()) { LogPrint("gobject", "CGovernanceManager::UpdateCachesAndClean -- Expiring watchdog: %s, expiration time = %d\n", it->first.ToString(), it->second); it2->second.fExpired = true; if(it2->second.nDeletionTime == 0) { it2->second.nDeletionTime = nNow; } } if(it->first == nHashWatchdogCurrent) { nHashWatchdogCurrent = uint256(); } mapWatchdogObjects.erase(it++); } else { ++it; } } } for(size_t i = 0; i < vecDirtyHashes.size(); ++i) { object_m_it it = mapObjects.find(vecDirtyHashes[i]); if(it == mapObjects.end()) { continue; } it->second.ClearMasternodeVotes(); it->second.fDirtyCache = true; } ScopedLockBool guard(cs, fRateChecksEnabled, false); // UPDATE CACHE FOR EACH OBJECT THAT IS FLAGGED DIRTYCACHE=TRUE object_m_it it = mapObjects.begin(); // Clean up any expired or invalid triggers triggerman.CleanAndRemove(); while(it != mapObjects.end()) { CGovernanceObject* pObj = &((*it).second); if(!pObj) { ++it; continue; } uint256 nHash = it->first; std::string strHash = nHash.ToString(); // IF CACHE IS NOT DIRTY, WHY DO THIS? if(pObj->IsSetDirtyCache()) { // UPDATE LOCAL VALIDITY AGAINST CRYPTO DATA pObj->UpdateLocalValidity(); // UPDATE SENTINEL SIGNALING VARIABLES pObj->UpdateSentinelVariables(); } if(pObj->IsSetCachedDelete() && (nHash == nHashWatchdogCurrent)) { nHashWatchdogCurrent = uint256(); } // IF DELETE=TRUE, THEN CLEAN THE MESS UP! int64_t nTimeSinceDeletion = GetAdjustedTime() - pObj->GetDeletionTime(); LogPrint("gobject", "CGovernanceManager::UpdateCachesAndClean -- Checking object for deletion: %s, deletion time = %d, time since deletion = %d, delete flag = %d, expired flag = %d\n", strHash, pObj->GetDeletionTime(), nTimeSinceDeletion, pObj->IsSetCachedDelete(), pObj->IsSetExpired()); if((pObj->IsSetCachedDelete() || pObj->IsSetExpired()) && (nTimeSinceDeletion >= GOVERNANCE_DELETION_DELAY)) { LogPrintf("CGovernanceManager::UpdateCachesAndClean -- erase obj %s\n", (*it).first.ToString()); mnodeman.RemoveGovernanceObject(pObj->GetHash()); // Remove vote references const object_ref_cm_t::list_t& listItems = cmapVoteToObject.GetItemList(); object_ref_cm_t::list_cit lit = listItems.begin(); while(lit != listItems.end()) { if(lit->value == pObj) { uint256 nKey = lit->key; ++lit; cmapVoteToObject.Erase(nKey); } else { ++lit; } } int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; int64_t nTimeExpired = pObj->GetCreationTime() + 2 * nSuperblockCycleSeconds + GOVERNANCE_DELETION_DELAY; if(pObj->GetObjectType() == GOVERNANCE_OBJECT_WATCHDOG) { mapWatchdogObjects.erase(nHash); } else if(pObj->GetObjectType() != GOVERNANCE_OBJECT_TRIGGER) { // keep hashes of deleted proposals forever nTimeExpired = std::numeric_limits::max(); } mapErasedGovernanceObjects.insert(std::make_pair(nHash, nTimeExpired)); mapObjects.erase(it++); } else { ++it; } } // forget about expired deleted objects hash_time_m_it s_it = mapErasedGovernanceObjects.begin(); while(s_it != mapErasedGovernanceObjects.end()) { if(s_it->second < nNow) mapErasedGovernanceObjects.erase(s_it++); else ++s_it; } LogPrintf("CGovernanceManager::UpdateCachesAndClean -- %s\n", ToString()); } CGovernanceObject *CGovernanceManager::FindGovernanceObject(const uint256& nHash) { LOCK(cs); if(mapObjects.count(nHash)) return &mapObjects[nHash]; return NULL; } std::vector CGovernanceManager::GetMatchingVotes(const uint256& nParentHash) { LOCK(cs); std::vector vecResult; object_m_it it = mapObjects.find(nParentHash); if(it == mapObjects.end()) { return vecResult; } CGovernanceObject& govobj = it->second; return govobj.GetVoteFile().GetVotes(); } std::vector CGovernanceManager::GetCurrentVotes(const uint256& nParentHash, const COutPoint& mnCollateralOutpointFilter) { LOCK(cs); std::vector vecResult; // Find the governance object or short-circuit. object_m_it it = mapObjects.find(nParentHash); if(it == mapObjects.end()) return vecResult; CGovernanceObject& govobj = it->second; CMasternode mn; std::map mapMasternodes; if(mnCollateralOutpointFilter.IsNull()) { mapMasternodes = mnodeman.GetFullMasternodeMap(); } else if (mnodeman.Get(mnCollateralOutpointFilter, mn)) { mapMasternodes[mnCollateralOutpointFilter] = mn; } // Loop thru each MN collateral outpoint and get the votes for the `nParentHash` governance object for (const auto& mnpair : mapMasternodes) { // get a vote_rec_t from the govobj vote_rec_t voteRecord; if (!govobj.GetCurrentMNVotes(mnpair.first, voteRecord)) continue; for (vote_instance_m_it it3 = voteRecord.mapInstances.begin(); it3 != voteRecord.mapInstances.end(); ++it3) { int signal = (it3->first); int outcome = ((it3->second).eOutcome); int64_t nCreationTime = ((it3->second).nCreationTime); CGovernanceVote vote = CGovernanceVote(mnpair.first, nParentHash, (vote_signal_enum_t)signal, (vote_outcome_enum_t)outcome); vote.SetTime(nCreationTime); vecResult.push_back(vote); } } return vecResult; } std::vector CGovernanceManager::GetAllNewerThan(int64_t nMoreThanTime) { LOCK(cs); std::vector vGovObjs; object_m_it it = mapObjects.begin(); while(it != mapObjects.end()) { // IF THIS OBJECT IS OLDER THAN TIME, CONTINUE if((*it).second.GetCreationTime() < nMoreThanTime) { ++it; continue; } // ADD GOVERNANCE OBJECT TO LIST CGovernanceObject* pGovObj = &((*it).second); vGovObjs.push_back(pGovObj); // NEXT ++it; } return vGovObjs; } // // Sort by votes, if there's a tie sort by their feeHash TX // struct sortProposalsByVotes { bool operator()(const std::pair &left, const std::pair &right) { if (left.second != right.second) return (left.second > right.second); return (UintToArith256(left.first->GetCollateralHash()) > UintToArith256(right.first->GetCollateralHash())); } }; void CGovernanceManager::DoMaintenance(CConnman& connman) { if(fLiteMode || !masternodeSync.IsSynced()) return; // CHECK OBJECTS WE'VE ASKED FOR, REMOVE OLD ENTRIES CleanOrphanObjects(); RequestOrphanObjects(connman); // CHECK AND REMOVE - REPROCESS GOVERNANCE OBJECTS UpdateCachesAndClean(); } bool CGovernanceManager::ConfirmInventoryRequest(const CInv& inv) { // do not request objects until it's time to sync if(!masternodeSync.IsWinnersListSynced()) return false; LOCK(cs); LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest inv = %s\n", inv.ToString()); // First check if we've already recorded this object switch(inv.type) { case MSG_GOVERNANCE_OBJECT: { if(mapObjects.count(inv.hash) == 1 || mapPostponedObjects.count(inv.hash) == 1) { LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest already have governance object, returning false\n"); return false; } } break; case MSG_GOVERNANCE_OBJECT_VOTE: { if(cmapVoteToObject.HasKey(inv.hash)) { LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest already have governance vote, returning false\n"); return false; } } break; default: LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest unknown type, returning false\n"); return false; } hash_s_t* setHash = NULL; switch(inv.type) { case MSG_GOVERNANCE_OBJECT: setHash = &setRequestedObjects; break; case MSG_GOVERNANCE_OBJECT_VOTE: setHash = &setRequestedVotes; break; default: return false; } hash_s_cit it = setHash->find(inv.hash); if(it == setHash->end()) { setHash->insert(inv.hash); LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest added inv to requested set\n"); } LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest reached end, returning true\n"); return true; } void CGovernanceManager::SyncSingleObjAndItsVotes(CNode* pnode, const uint256& nProp, const CBloomFilter& filter, CConnman& connman) { // do not provide any data until our node is synced if(!masternodeSync.IsSynced()) return; int nVoteCount = 0; // SYNC GOVERNANCE OBJECTS WITH OTHER CLIENT LogPrint("gobject", "CGovernanceManager::%s -- syncing single object to peer=%d, nProp = %s\n", __func__, pnode->id, nProp.ToString()); LOCK2(cs_main, cs); // single valid object and its valid votes object_m_it it = mapObjects.find(nProp); if(it == mapObjects.end()) { LogPrint("gobject", "CGovernanceManager::%s -- no matching object for hash %s, peer=%d\n", __func__, nProp.ToString(), pnode->id); return; } CGovernanceObject& govobj = it->second; std::string strHash = it->first.ToString(); LogPrint("gobject", "CGovernanceManager::%s -- attempting to sync govobj: %s, peer=%d\n", __func__, strHash, pnode->id); if(govobj.IsSetCachedDelete() || govobj.IsSetExpired()) { LogPrintf("CGovernanceManager::%s -- not syncing deleted/expired govobj: %s, peer=%d\n", __func__, strHash, pnode->id); return; } // Push the govobj inventory message over to the other client LogPrint("gobject", "CGovernanceManager::%s -- syncing govobj: %s, peer=%d\n", __func__, strHash, pnode->id); pnode->PushInventory(CInv(MSG_GOVERNANCE_OBJECT, it->first)); std::vector vecVotes = govobj.GetVoteFile().GetVotes(); for(size_t i = 0; i < vecVotes.size(); ++i) { if(filter.contains(vecVotes[i].GetHash())) { continue; } if(!vecVotes[i].IsValid(true)) { continue; } pnode->PushInventory(CInv(MSG_GOVERNANCE_OBJECT_VOTE, vecVotes[i].GetHash())); ++nVoteCount; } CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ, 1)); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ_VOTE, nVoteCount)); LogPrintf("CGovernanceManager::%s -- sent 1 object and %d votes to peer=%d\n", __func__, nVoteCount, pnode->id); } void CGovernanceManager::SyncAll(CNode* pnode, CConnman& connman) { // do not provide any data until our node is synced if(!masternodeSync.IsSynced()) return; if(netfulfilledman.HasFulfilledRequest(pnode->addr, NetMsgType::MNGOVERNANCESYNC)) { // Asking for the whole list multiple times in a short period of time is no good LogPrint("gobject", "CGovernanceManager::%s -- peer already asked me for the list\n", __func__); Misbehaving(pnode->GetId(), 20); return; } netfulfilledman.AddFulfilledRequest(pnode->addr, NetMsgType::MNGOVERNANCESYNC); int nObjCount = 0; int nVoteCount = 0; // SYNC GOVERNANCE OBJECTS WITH OTHER CLIENT LogPrint("gobject", "CGovernanceManager::%s -- syncing all objects to peer=%d\n", __func__, pnode->id); LOCK2(cs_main, cs); // all valid objects, no votes for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { CGovernanceObject& govobj = it->second; std::string strHash = it->first.ToString(); LogPrint("gobject", "CGovernanceManager::%s -- attempting to sync govobj: %s, peer=%d\n", __func__, strHash, pnode->id); if(govobj.IsSetCachedDelete() || govobj.IsSetExpired()) { LogPrintf("CGovernanceManager::%s -- not syncing deleted/expired govobj: %s, peer=%d\n", __func__, strHash, pnode->id); continue; } // Push the inventory budget proposal message over to the other client LogPrint("gobject", "CGovernanceManager::%s -- syncing govobj: %s, peer=%d\n", __func__, strHash, pnode->id); pnode->PushInventory(CInv(MSG_GOVERNANCE_OBJECT, it->first)); ++nObjCount; } CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ, nObjCount)); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ_VOTE, nVoteCount)); LogPrintf("CGovernanceManager::%s -- sent %d objects and %d votes to peer=%d\n", __func__, nObjCount, nVoteCount, pnode->id); } void CGovernanceManager::MasternodeRateUpdate(const CGovernanceObject& govobj) { int nObjectType = govobj.GetObjectType(); if((nObjectType != GOVERNANCE_OBJECT_TRIGGER) && (nObjectType != GOVERNANCE_OBJECT_WATCHDOG)) return; const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint(); txout_m_it it = mapLastMasternodeObject.find(masternodeOutpoint); if(it == mapLastMasternodeObject.end()) it = mapLastMasternodeObject.insert(txout_m_t::value_type(masternodeOutpoint, last_object_rec(true))).first; int64_t nTimestamp = govobj.GetCreationTime(); if (GOVERNANCE_OBJECT_TRIGGER == nObjectType) it->second.triggerBuffer.AddTimestamp(nTimestamp); else if (GOVERNANCE_OBJECT_WATCHDOG == nObjectType) it->second.watchdogBuffer.AddTimestamp(nTimestamp); if (nTimestamp > GetTime() + MAX_TIME_FUTURE_DEVIATION - RELIABLE_PROPAGATION_TIME) { // schedule additional relay for the object setAdditionalRelayObjects.insert(govobj.GetHash()); } it->second.fStatusOK = true; } bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateFailStatus) { bool fRateCheckBypassed; return MasternodeRateCheck(govobj, fUpdateFailStatus, true, fRateCheckBypassed); } bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateFailStatus, bool fForce, bool& fRateCheckBypassed) { LOCK(cs); fRateCheckBypassed = false; if(!masternodeSync.IsSynced()) { return true; } if(!fRateChecksEnabled) { return true; } int nObjectType = govobj.GetObjectType(); if((nObjectType != GOVERNANCE_OBJECT_TRIGGER) && (nObjectType != GOVERNANCE_OBJECT_WATCHDOG)) { return true; } const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint(); int64_t nTimestamp = govobj.GetCreationTime(); int64_t nNow = GetAdjustedTime(); int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; std::string strHash = govobj.GetHash().ToString(); if(nTimestamp < nNow - 2 * nSuperblockCycleSeconds) { LogPrintf("CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too old timestamp, masternode = %s, timestamp = %d, current time = %d\n", strHash, masternodeOutpoint.ToStringShort(), nTimestamp, nNow); return false; } if(nTimestamp > nNow + MAX_TIME_FUTURE_DEVIATION) { LogPrintf("CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too new (future) timestamp, masternode = %s, timestamp = %d, current time = %d\n", strHash, masternodeOutpoint.ToStringShort(), nTimestamp, nNow); return false; } txout_m_it it = mapLastMasternodeObject.find(masternodeOutpoint); if(it == mapLastMasternodeObject.end()) return true; if(it->second.fStatusOK && !fForce) { fRateCheckBypassed = true; return true; } double dMaxRate = 1.1 / nSuperblockCycleSeconds; double dRate = 0.0; CRateCheckBuffer buffer; switch(nObjectType) { case GOVERNANCE_OBJECT_TRIGGER: // Allow 1 trigger per mn per cycle, with a small fudge factor buffer = it->second.triggerBuffer; dMaxRate = 2 * 1.1 / double(nSuperblockCycleSeconds); break; case GOVERNANCE_OBJECT_WATCHDOG: buffer = it->second.watchdogBuffer; dMaxRate = 2 * 1.1 / 3600.; break; default: break; } buffer.AddTimestamp(nTimestamp); dRate = buffer.GetRate(); bool fRateOK = ( dRate < dMaxRate ); if(!fRateOK) { LogPrintf("CGovernanceManager::MasternodeRateCheck -- Rate too high: object hash = %s, masternode = %s, object timestamp = %d, rate = %f, max rate = %f\n", strHash, masternodeOutpoint.ToStringShort(), nTimestamp, dRate, dMaxRate); if (fUpdateFailStatus) it->second.fStatusOK = false; } return fRateOK; } bool CGovernanceManager::ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception, CConnman& connman) { ENTER_CRITICAL_SECTION(cs); uint256 nHashVote = vote.GetHash(); uint256 nHashGovobj = vote.GetParentHash(); if(cmapVoteToObject.HasKey(nHashVote)) { LogPrint("gobject", "CGovernanceObject::ProcessVote -- skipping known valid vote %s for object %s\n", nHashVote.ToString(), nHashGovobj.ToString()); LEAVE_CRITICAL_SECTION(cs); return false; } if(cmapInvalidVotes.HasKey(nHashVote)) { std::ostringstream ostr; ostr << "CGovernanceManager::ProcessVote -- Old invalid vote " << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort() << ", governance object hash = " << nHashGovobj.ToString(); LogPrintf("%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); LEAVE_CRITICAL_SECTION(cs); return false; } object_m_it it = mapObjects.find(nHashGovobj); if(it == mapObjects.end()) { std::ostringstream ostr; ostr << "CGovernanceManager::ProcessVote -- Unknown parent object " << nHashGovobj.ToString() << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort(); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); if(cmmapOrphanVotes.Insert(nHashGovobj, vote_time_pair_t(vote, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))) { LEAVE_CRITICAL_SECTION(cs); RequestGovernanceObject(pfrom, nHashGovobj, connman); LogPrintf("%s\n", ostr.str()); return false; } LogPrint("gobject", "%s\n", ostr.str()); LEAVE_CRITICAL_SECTION(cs); return false; } CGovernanceObject& govobj = it->second; if(govobj.IsSetCachedDelete() || govobj.IsSetExpired()) { LogPrint("gobject", "CGovernanceObject::ProcessVote -- ignoring vote for expired or deleted object, hash = %s\n", nHashGovobj.ToString()); LEAVE_CRITICAL_SECTION(cs); return false; } bool fOk = govobj.ProcessVote(pfrom, vote, exception, connman) && cmapVoteToObject.Insert(nHashVote, &govobj); if(fOk && govobj.GetObjectType() == GOVERNANCE_OBJECT_WATCHDOG) { mnodeman.UpdateWatchdogVoteTime(vote.GetMasternodeOutpoint()); LogPrint("gobject", "CGovernanceObject::ProcessVote -- GOVERNANCE_OBJECT_WATCHDOG vote %s for %s\n", nHashVote.ToString(), nHashGovobj.ToString()); } LEAVE_CRITICAL_SECTION(cs); return fOk; } void CGovernanceManager::CheckMasternodeOrphanVotes(CConnman& connman) { LOCK2(cs_main, cs); ScopedLockBool guard(cs, fRateChecksEnabled, false); for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { it->second.CheckOrphanVotes(connman); } } void CGovernanceManager::CheckMasternodeOrphanObjects(CConnman& connman) { LOCK2(cs_main, cs); int64_t nNow = GetAdjustedTime(); ScopedLockBool guard(cs, fRateChecksEnabled, false); object_info_m_it it = mapMasternodeOrphanObjects.begin(); while(it != mapMasternodeOrphanObjects.end()) { object_info_pair_t& pair = it->second; CGovernanceObject& govobj = pair.first; if(pair.second.nExpirationTime >= nNow) { string strError; bool fMasternodeMissing = false; bool fConfirmationsMissing = false; bool fIsValid = govobj.IsValidLocally(strError, fMasternodeMissing, fConfirmationsMissing, true); if(fIsValid) { AddGovernanceObject(govobj, connman); } else if(fMasternodeMissing) { ++it; continue; } } else { // apply node's ban score Misbehaving(pair.second.idFrom, 20); } auto it_count = mapMasternodeOrphanCounter.find(govobj.GetMasternodeOutpoint()); if(--it_count->second == 0) mapMasternodeOrphanCounter.erase(it_count); mapMasternodeOrphanObjects.erase(it++); } } void CGovernanceManager::CheckPostponedObjects(CConnman& connman) { if(!masternodeSync.IsSynced()) return; LOCK2(cs_main, cs); // Check postponed proposals for(object_m_it it = mapPostponedObjects.begin(); it != mapPostponedObjects.end();) { const uint256& nHash = it->first; CGovernanceObject& govobj = it->second; assert(govobj.GetObjectType() != GOVERNANCE_OBJECT_WATCHDOG && govobj.GetObjectType() != GOVERNANCE_OBJECT_TRIGGER); std::string strError; bool fMissingConfirmations; if (govobj.IsCollateralValid(strError, fMissingConfirmations)) { if(govobj.IsValidLocally(strError, false)) AddGovernanceObject(govobj, connman); else LogPrintf("CGovernanceManager::CheckPostponedObjects -- %s invalid\n", nHash.ToString()); } else if(fMissingConfirmations) { // wait for more confirmations ++it; continue; } // remove processed or invalid object from the queue mapPostponedObjects.erase(it++); } // Perform additional relays for triggers/watchdogs int64_t nNow = GetAdjustedTime(); int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; for(hash_s_it it = setAdditionalRelayObjects.begin(); it != setAdditionalRelayObjects.end();) { object_m_it itObject = mapObjects.find(*it); if(itObject != mapObjects.end()) { CGovernanceObject& govobj = itObject->second; int64_t nTimestamp = govobj.GetCreationTime(); bool fValid = (nTimestamp <= nNow + MAX_TIME_FUTURE_DEVIATION) && (nTimestamp >= nNow - 2 * nSuperblockCycleSeconds); bool fReady = (nTimestamp <= nNow + MAX_TIME_FUTURE_DEVIATION - RELIABLE_PROPAGATION_TIME); if(fValid) { if(fReady) { LogPrintf("CGovernanceManager::CheckPostponedObjects -- additional relay: hash = %s\n", govobj.GetHash().ToString()); govobj.Relay(connman); } else { it++; continue; } } } else { LogPrintf("CGovernanceManager::CheckPostponedObjects -- additional relay of unknown object: %s\n", it->ToString()); } setAdditionalRelayObjects.erase(it++); } } void CGovernanceManager::RequestGovernanceObject(CNode* pfrom, const uint256& nHash, CConnman& connman, bool fUseFilter) { if(!pfrom) { return; } LogPrint("gobject", "CGovernanceObject::RequestGovernanceObject -- hash = %s (peer=%d)\n", nHash.ToString(), pfrom->GetId()); CNetMsgMaker msgMaker(pfrom->GetSendVersion()); if(pfrom->nVersion < GOVERNANCE_FILTER_PROTO_VERSION) { connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, nHash)); return; } CBloomFilter filter; filter.clear(); int nVoteCount = 0; if(fUseFilter) { LOCK(cs); CGovernanceObject* pObj = FindGovernanceObject(nHash); if(pObj) { filter = CBloomFilter(Params().GetConsensus().nGovernanceFilterElements, GOVERNANCE_FILTER_FP_RATE, GetRandInt(999999), BLOOM_UPDATE_ALL); std::vector vecVotes = pObj->GetVoteFile().GetVotes(); nVoteCount = vecVotes.size(); for(size_t i = 0; i < vecVotes.size(); ++i) { filter.insert(vecVotes[i].GetHash()); } } } LogPrint("gobject", "CGovernanceManager::RequestGovernanceObject -- nHash %s nVoteCount %d peer=%d\n", nHash.ToString(), nVoteCount, pfrom->id); connman.PushMessage(pfrom, msgMaker.Make(NetMsgType::MNGOVERNANCESYNC, nHash, filter)); } int CGovernanceManager::RequestGovernanceObjectVotes(CNode* pnode, CConnman& connman) { if(pnode->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) return -3; std::vector vNodesCopy; vNodesCopy.push_back(pnode); return RequestGovernanceObjectVotes(vNodesCopy, connman); } int CGovernanceManager::RequestGovernanceObjectVotes(const std::vector& vNodesCopy, CConnman& connman) { static std::map > mapAskedRecently; if(vNodesCopy.empty()) return -1; int64_t nNow = GetTime(); int nTimeout = 60 * 60; size_t nPeersPerHashMax = 3; std::vector vpGovObjsTmp; std::vector vpGovObjsTriggersTmp; // This should help us to get some idea about an impact this can bring once deployed on mainnet. // Testnet is ~40 times smaller in masternode count, but only ~1000 masternodes usually vote, // so 1 obj on mainnet == ~10 objs or ~1000 votes on testnet. However we want to test a higher // number of votes to make sure it's robust enough, so aim at 2000 votes per masternode per request. // On mainnet nMaxObjRequestsPerNode is always set to 1. int nMaxObjRequestsPerNode = 1; size_t nProjectedVotes = 2000; if(Params().NetworkIDString() != CBaseChainParams::MAIN) { nMaxObjRequestsPerNode = std::max(1, int(nProjectedVotes / std::max(1, mnodeman.size()))); } { LOCK2(cs_main, cs); if(mapObjects.empty()) return -2; for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { if(mapAskedRecently.count(it->first)) { std::map::iterator it1 = mapAskedRecently[it->first].begin(); while(it1 != mapAskedRecently[it->first].end()) { if(it1->second < nNow) { mapAskedRecently[it->first].erase(it1++); } else { ++it1; } } if(mapAskedRecently[it->first].size() >= nPeersPerHashMax) continue; } if(it->second.nObjectType == GOVERNANCE_OBJECT_TRIGGER) { vpGovObjsTriggersTmp.push_back(&(it->second)); } else { vpGovObjsTmp.push_back(&(it->second)); } } } LogPrint("gobject", "CGovernanceManager::RequestGovernanceObjectVotes -- start: vpGovObjsTriggersTmp %d vpGovObjsTmp %d mapAskedRecently %d\n", vpGovObjsTriggersTmp.size(), vpGovObjsTmp.size(), mapAskedRecently.size()); FastRandomContext insecure_rand; // shuffle pointers std::random_shuffle(vpGovObjsTriggersTmp.begin(), vpGovObjsTriggersTmp.end(), insecure_rand); std::random_shuffle(vpGovObjsTmp.begin(), vpGovObjsTmp.end(), insecure_rand); for (int i = 0; i < nMaxObjRequestsPerNode; ++i) { uint256 nHashGovobj; // ask for triggers first if(vpGovObjsTriggersTmp.size()) { nHashGovobj = vpGovObjsTriggersTmp.back()->GetHash(); } else { if(vpGovObjsTmp.empty()) break; nHashGovobj = vpGovObjsTmp.back()->GetHash(); } bool fAsked = false; for (const auto& pnode : vNodesCopy) { // Only use regular peers, don't try to ask from outbound "masternode" connections - // they stay connected for a short period of time and it's possible that we won't get everything we should. // Only use outbound connections - inbound connection could be a "masternode" connection // initiated from another node, so skip it too. if(pnode->fMasternode || (fMasternodeMode && pnode->fInbound)) continue; // only use up to date peers if(pnode->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) continue; // stop early to prevent setAskFor overflow size_t nProjectedSize = pnode->setAskFor.size() + nProjectedVotes; if(nProjectedSize > SETASKFOR_MAX_SZ/2) continue; // to early to ask the same node if(mapAskedRecently[nHashGovobj].count(pnode->addr)) continue; RequestGovernanceObject(pnode, nHashGovobj, connman, true); mapAskedRecently[nHashGovobj][pnode->addr] = nNow + nTimeout; fAsked = true; // stop loop if max number of peers per obj was asked if(mapAskedRecently[nHashGovobj].size() >= nPeersPerHashMax) break; } // NOTE: this should match `if` above (the one before `while`) if(vpGovObjsTriggersTmp.size()) { vpGovObjsTriggersTmp.pop_back(); } else { vpGovObjsTmp.pop_back(); } if(!fAsked) i--; } LogPrint("gobject", "CGovernanceManager::RequestGovernanceObjectVotes -- end: vpGovObjsTriggersTmp %d vpGovObjsTmp %d mapAskedRecently %d\n", vpGovObjsTriggersTmp.size(), vpGovObjsTmp.size(), mapAskedRecently.size()); return int(vpGovObjsTriggersTmp.size() + vpGovObjsTmp.size()); } bool CGovernanceManager::AcceptObjectMessage(const uint256& nHash) { LOCK(cs); return AcceptMessage(nHash, setRequestedObjects); } bool CGovernanceManager::AcceptVoteMessage(const uint256& nHash) { LOCK(cs); return AcceptMessage(nHash, setRequestedVotes); } bool CGovernanceManager::AcceptMessage(const uint256& nHash, hash_s_t& setHash) { hash_s_it it = setHash.find(nHash); if(it == setHash.end()) { // We never requested this return false; } // Only accept one response setHash.erase(it); return true; } void CGovernanceManager::RebuildIndexes() { cmapVoteToObject.Clear(); for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { CGovernanceObject& govobj = it->second; std::vector vecVotes = govobj.GetVoteFile().GetVotes(); for(size_t i = 0; i < vecVotes.size(); ++i) { cmapVoteToObject.Insert(vecVotes[i].GetHash(), &govobj); } } } void CGovernanceManager::AddCachedTriggers() { LOCK(cs); for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { CGovernanceObject& govobj = it->second; if(govobj.nObjectType != GOVERNANCE_OBJECT_TRIGGER) { continue; } triggerman.AddNewTrigger(govobj.GetHash()); } } void CGovernanceManager::InitOnLoad() { LOCK(cs); int64_t nStart = GetTimeMillis(); LogPrintf("Preparing masternode indexes and governance triggers...\n"); RebuildIndexes(); AddCachedTriggers(); LogPrintf("Masternode indexes and governance triggers prepared %dms\n", GetTimeMillis() - nStart); LogPrintf(" %s\n", ToString()); } std::string CGovernanceManager::ToString() const { LOCK(cs); int nProposalCount = 0; int nTriggerCount = 0; int nWatchdogCount = 0; int nOtherCount = 0; object_m_cit it = mapObjects.begin(); while(it != mapObjects.end()) { switch(it->second.GetObjectType()) { case GOVERNANCE_OBJECT_PROPOSAL: nProposalCount++; break; case GOVERNANCE_OBJECT_TRIGGER: nTriggerCount++; break; case GOVERNANCE_OBJECT_WATCHDOG: nWatchdogCount++; break; default: nOtherCount++; break; } ++it; } return strprintf("Governance Objects: %d (Proposals: %d, Triggers: %d, Watchdogs: %d/%d, Other: %d; Erased: %d), Votes: %d", (int)mapObjects.size(), nProposalCount, nTriggerCount, nWatchdogCount, mapWatchdogObjects.size(), nOtherCount, (int)mapErasedGovernanceObjects.size(), (int)cmapVoteToObject.GetSize()); } void CGovernanceManager::UpdatedBlockTip(const CBlockIndex *pindex, CConnman& connman) { // Note this gets called from ActivateBestChain without cs_main being held // so it should be safe to lock our mutex here without risking a deadlock // On the other hand it should be safe for us to access pindex without holding a lock // on cs_main because the CBlockIndex objects are dynamically allocated and // presumably never deleted. if(!pindex) { return; } nCachedBlockHeight = pindex->nHeight; LogPrint("gobject", "CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight); CheckPostponedObjects(connman); CSuperblockManager::ExecuteBestSuperblock(pindex->nHeight); } void CGovernanceManager::RequestOrphanObjects(CConnman& connman) { std::vector vNodesCopy = connman.CopyNodeVector(CConnman::FullyConnectedOnly); std::vector vecHashesFiltered; { std::vector vecHashes; LOCK(cs); cmmapOrphanVotes.GetKeys(vecHashes); for(size_t i = 0; i < vecHashes.size(); ++i) { const uint256& nHash = vecHashes[i]; if(mapObjects.find(nHash) == mapObjects.end()) { vecHashesFiltered.push_back(nHash); } } } LogPrint("gobject", "CGovernanceObject::RequestOrphanObjects -- number objects = %d\n", vecHashesFiltered.size()); for(size_t i = 0; i < vecHashesFiltered.size(); ++i) { const uint256& nHash = vecHashesFiltered[i]; for(size_t j = 0; j < vNodesCopy.size(); ++j) { CNode* pnode = vNodesCopy[j]; if(pnode->fMasternode) { continue; } RequestGovernanceObject(pnode, nHash, connman); } } connman.ReleaseNodeVector(vNodesCopy); } void CGovernanceManager::CleanOrphanObjects() { LOCK(cs); const vote_cmm_t::list_t& items = cmmapOrphanVotes.GetItemList(); int64_t nNow = GetAdjustedTime(); vote_cmm_t::list_cit it = items.begin(); while(it != items.end()) { vote_cmm_t::list_cit prevIt = it; ++it; const vote_time_pair_t& pairVote = prevIt->value; if(pairVote.second < nNow) { cmmapOrphanVotes.Erase(prevIt->key, prevIt->value); } } }