// Copyright (c) 2014-2021 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include CGovernanceManager governance; int nSubmittedFinalBudget; const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-15"; const int CGovernanceManager::MAX_TIME_FUTURE_DEVIATION = 60 * 60; const int CGovernanceManager::RELIABLE_PROPAGATION_TIME = 60; CGovernanceManager::CGovernanceManager() : nTimeLastDiff(0), nCachedBlockHeight(0), mapObjects(), mapErasedGovernanceObjects(), cmapVoteToObject(MAX_CACHE_SIZE), cmapInvalidVotes(MAX_CACHE_SIZE), cmmapOrphanVotes(MAX_CACHE_SIZE), mapLastMasternodeObject(), setRequestedObjects(), fRateChecksEnabled(true), lastMNListForVotingKeys(std::make_shared()), cs() { } // Accessors for thread-safe access to maps bool CGovernanceManager::HaveObjectForHash(const uint256& nHash) const { LOCK(cs); return (mapObjects.count(nHash) == 1 || mapPostponedObjects.count(nHash) == 1); } bool CGovernanceManager::SerializeObjectForHash(const uint256& nHash, CDataStream& ss) const { LOCK(cs); auto 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(const uint256& nHash) const { LOCK(cs); CGovernanceObject* pGovobj = nullptr; return cmapVoteToObject.Get(nHash, pGovobj) && pGovobj->GetVoteFile().HasVote(nHash); } int CGovernanceManager::GetVoteCount() const { LOCK(cs); return (int)cmapVoteToObject.GetSize(); } bool CGovernanceManager::SerializeVoteForHash(const uint256& nHash, CDataStream& ss) const { LOCK(cs); CGovernanceObject* pGovobj = nullptr; return cmapVoteToObject.Get(nHash, pGovobj) && pGovobj->GetVoteFile().SerializeVoteToStream(nHash, ss); } void CGovernanceManager::ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStream& vRecv, CConnman& connman, bool enable_bip61) { if (fDisableGovernance) return; if (!masternodeSync.IsBlockchainSynced()) return; // ANOTHER USER IS ASKING US TO HELP THEM SYNC GOVERNANCE OBJECT DATA if (strCommand == NetMsgType::MNGOVERNANCESYNC) { if (pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); if (enable_bip61) { connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf( "Version must be %d or greater", MIN_GOVERNANCE_PEER_PROTO_VERSION))); } return; } // 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; vRecv >> filter; filter.UpdateEmptyFull(); if (nProp == uint256()) { SyncObjects(pfrom, connman); } else { SyncSingleObjVotes(pfrom, nProp, filter, connman); } LogPrint(BCLog::GOBJECT, "MNGOVERNANCESYNC -- syncing governance objects to our peer %s\n", pfrom->GetLogString()); } // 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(); { LOCK(cs_main); EraseObjectRequest(pfrom->GetId(), CInv(MSG_GOVERNANCE_OBJECT, nHash)); } if (pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); if (enable_bip61) { connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf( "Version must be %d or greater", MIN_GOVERNANCE_PEER_PROTO_VERSION))); } return; } if (!masternodeSync.IsBlockchainSynced()) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode list not synced\n"); return; } std::string strHash = nHash.ToString(); LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received object: %s\n", strHash); if (!AcceptObjectMessage(nHash)) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received unrequested object: %s\n", strHash); return; } LOCK2(cs_main, cs); if (mapObjects.count(nHash) || mapPostponedObjects.count(nHash) || mapErasedGovernanceObjects.count(nHash)) { // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE? LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- Received already seen object: %s\n", strHash); return; } bool fRateCheckBypassed = false; if (!MasternodeRateCheck(govobj, true, false, fRateCheckBypassed)) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", strHash, nCachedBlockHeight); return; } std::string strError; // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN bool fMissingConfirmations = false; bool fIsValid = govobj.IsValidLocally(strError, fMissingConfirmations, true); if (fRateCheckBypassed && fIsValid && !MasternodeRateCheck(govobj, true)) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - %s - (current block height %d)\n", strHash, nCachedBlockHeight); return; } if (!fIsValid) { if (fMissingConfirmations) { AddPostponedObject(govobj); LogPrintf("MNGOVERNANCEOBJECT -- Not enough fee confirmations for: %s, strError = %s\n", strHash, strError); } else { LogPrint(BCLog::GOBJECT, "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(); { LOCK(cs_main); EraseObjectRequest(pfrom->GetId(), CInv(MSG_GOVERNANCE_OBJECT_VOTE, nHash)); } if (pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- peer=%d using obsolete version %i\n", pfrom->GetId(), pfrom->nVersion); if (enable_bip61) { connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::REJECT, strCommand, REJECT_OBSOLETE, strprintf( "Version must be %d or greater", MIN_GOVERNANCE_PEER_PROTO_VERSION))); } } // Ignore such messages until masternode list is synced if (!masternodeSync.IsBlockchainSynced()) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- masternode list not synced\n"); return; } LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Received vote: %s\n", vote.ToString()); std::string strHash = nHash.ToString(); if (!AcceptVoteMessage(nHash)) { LogPrint(BCLog::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(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- %s new\n", strHash); masternodeSync.BumpAssetLastTime("MNGOVERNANCEOBJECTVOTE"); vote.Relay(connman); } else { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- Rejected vote, error = %s\n", exception.what()); if ((exception.GetNodePenalty() != 0) && masternodeSync.IsSynced()) { LOCK(cs_main); Misbehaving(pfrom->GetId(), exception.GetNodePenalty()); } return; } } } void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj, CConnman& connman) { uint256 nHash = govobj.GetHash(); std::vector vecVotePairs; cmmapOrphanVotes.GetAll(nHash, vecVotePairs); ScopedLockBool guard(cs, fRateChecksEnabled, false); int64_t nNow = GetAdjustedTime(); for (const auto& pairVote : vecVotePairs) { bool fRemove = false; const CGovernanceVote& vote = pairVote.first; CGovernanceException e; if (pairVote.second < nNow) { fRemove = true; } else if (govobj.ProcessVote(vote, e)) { vote.Relay(connman); fRemove = true; } if (fRemove) { cmmapOrphanVotes.Erase(nHash, pairVote); } } } void CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj, CConnman& connman, const CNode* pfrom) { uint256 nHash = govobj.GetHash(); std::string strHash = nHash.ToString(); // UPDATE CACHED VARIABLES FOR THIS OBJECT AND ADD IT TO OUR MANAGED 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)) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- invalid governance object - %s - (nCachedBlockHeight %d) \n", strError, nCachedBlockHeight); return; } LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- Adding object: hash = %s, type = %d\n", nHash.ToString(), govobj.GetObjectType()); // INSERT INTO OUR GOVERNANCE OBJECT MEMORY // IF WE HAVE THIS OBJECT ALREADY, WE DON'T WANT ANOTHER COPY auto objpair = mapObjects.emplace(nHash, govobj); if (!objpair.second) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- already have governance object %s\n", nHash.ToString()); return; } // SHOULD WE ADD THIS OBJECT TO ANY OTHER MANAGERS? LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- Before trigger block, GetDataAsPlainString = %s, nObjectType = %d\n", govobj.GetDataAsPlainString(), govobj.GetObjectType()); if (govobj.GetObjectType() == GOVERNANCE_OBJECT_TRIGGER && !triggerman.AddNewTrigger(nHash)) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- undo adding invalid trigger object: hash = %s\n", nHash.ToString()); objpair.first->second.PrepareDeletion(GetAdjustedTime()); return; } LogPrint(BCLog::GOBJECT, "CGovernanceManager::AddGovernanceObject -- %s new, received from peer %s\n", strHash, pfrom ? pfrom->GetLogString() : "nullptr"); govobj.Relay(connman); // Update the rate buffer MasternodeRateUpdate(govobj); masternodeSync.BumpAssetLastTime("CGovernanceManager::AddGovernanceObject"); // WE MIGHT HAVE PENDING/ORPHAN VOTES FOR THIS OBJECT CheckOrphanVotes(govobj, connman); // SEND NOTIFICATION TO SCRIPT/ZMQ GetMainSignals().NotifyGovernanceObject(std::make_shared(govobj)); } void CGovernanceManager::UpdateCachesAndClean() { // Return on initial sync, spammed the debug.log and provided no use if (!masternodeSync.IsBlockchainSynced()) { return; } LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean\n"); std::vector vecDirtyHashes = mmetaman.GetAndClearDirtyGovernanceObjectHashes(); LOCK2(cs_main, cs); for (const uint256& nHash : vecDirtyHashes) { auto it = mapObjects.find(nHash); if (it == mapObjects.end()) { continue; } it->second.ClearMasternodeVotes(); } ScopedLockBool guard(cs, fRateChecksEnabled, false); // Clean up any expired or invalid triggers triggerman.CleanAndRemove(); auto it = mapObjects.begin(); int64_t nNow = GetAdjustedTime(); 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 DELETE=TRUE, THEN CLEAN THE MESS UP! int64_t nTimeSinceDeletion = nNow - pObj->GetDeletionTime(); LogPrint(BCLog::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)) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- erase obj %s\n", (*it).first.ToString()); mmetaman.RemoveGovernanceObject(pObj->GetHash()); // Remove vote references const object_ref_cm_t::list_t& listItems = cmapVoteToObject.GetItemList(); auto lit = listItems.begin(); while (lit != listItems.end()) { if (lit->value == pObj) { uint256 nKey = lit->key; ++lit; cmapVoteToObject.Erase(nKey); } else { ++lit; } } int64_t nTimeExpired{0}; if (pObj->GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { // keep hashes of deleted proposals forever nTimeExpired = std::numeric_limits::max(); } else { int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; nTimeExpired = pObj->GetCreationTime() + 2 * nSuperblockCycleSeconds + GOVERNANCE_DELETION_DELAY; } mapErasedGovernanceObjects.insert(std::make_pair(nHash, nTimeExpired)); mapObjects.erase(it++); } else { // NOTE: triggers are handled via triggerman if (pObj->GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { CProposalValidator validator(pObj->GetDataAsHexString(), true); if (!validator.Validate()) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- set for deletion expired obj %s\n", strHash); pObj->PrepareDeletion(nNow); } } ++it; } } // forget about expired deleted objects auto s_it = mapErasedGovernanceObjects.begin(); while (s_it != mapErasedGovernanceObjects.end()) { if (s_it->second < nNow) { mapErasedGovernanceObjects.erase(s_it++); } else { ++s_it; } } LogPrint(BCLog::GOBJECT, "CGovernanceManager::UpdateCachesAndClean -- %s\n", ToString()); } CGovernanceObject* CGovernanceManager::FindGovernanceObject(const uint256& nHash) { LOCK(cs); if (mapObjects.count(nHash)) return &mapObjects[nHash]; return nullptr; } std::vector CGovernanceManager::GetCurrentVotes(const uint256& nParentHash, const COutPoint& mnCollateralOutpointFilter) const { LOCK(cs); std::vector vecResult; // Find the governance object or short-circuit. auto it = mapObjects.find(nParentHash); if (it == mapObjects.end()) return vecResult; const CGovernanceObject& govobj = it->second; auto mnList = deterministicMNManager->GetListAtChainTip(); std::map mapMasternodes; if (mnCollateralOutpointFilter.IsNull()) { mnList.ForEachMN(false, [&](const CDeterministicMNCPtr& dmn) { mapMasternodes.emplace(dmn->collateralOutpoint, dmn); }); } else { auto dmn = mnList.GetMNByCollateral(mnCollateralOutpointFilter); if (dmn) { mapMasternodes.emplace(dmn->collateralOutpoint, dmn); } } // Loop through 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 (const auto& voteInstancePair : voteRecord.mapInstances) { int signal = voteInstancePair.first; int outcome = voteInstancePair.second.eOutcome; int64_t nCreationTime = voteInstancePair.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) const { LOCK(cs); std::vector vGovObjs; for (const auto& objPair : mapObjects) { // IF THIS OBJECT IS OLDER THAN TIME, CONTINUE if (objPair.second.GetCreationTime() < nMoreThanTime) { continue; } // ADD GOVERNANCE OBJECT TO LIST const CGovernanceObject* pGovObj = &(objPair.second); vGovObjs.push_back(pGovObj); } 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) const { 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 (fDisableGovernance || !masternodeSync.IsSynced() || ShutdownRequested()) 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.IsBlockchainSynced()) return false; LOCK(cs); LogPrint(BCLog::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(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest already have governance object, returning false\n"); return false; } break; } case MSG_GOVERNANCE_OBJECT_VOTE: { if (cmapVoteToObject.HasKey(inv.hash)) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest already have governance vote, returning false\n"); return false; } break; } default: LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest unknown type, returning false\n"); return false; } hash_s_t* setHash = nullptr; switch (inv.type) { case MSG_GOVERNANCE_OBJECT: setHash = &setRequestedObjects; break; case MSG_GOVERNANCE_OBJECT_VOTE: setHash = &setRequestedVotes; break; default: return false; } auto it = setHash->find(inv.hash); if (it == setHash->end()) { setHash->insert(inv.hash); LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest added inv to requested set\n"); } LogPrint(BCLog::GOBJECT, "CGovernanceManager::ConfirmInventoryRequest reached end, returning true\n"); return true; } void CGovernanceManager::SyncSingleObjVotes(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(BCLog::GOBJECT, "CGovernanceManager::%s -- syncing single object to peer=%d, nProp = %s\n", __func__, pnode->GetId(), nProp.ToString()); LOCK(cs); // single valid object and its valid votes auto it = mapObjects.find(nProp); if (it == mapObjects.end()) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- no matching object for hash %s, peer=%d\n", __func__, nProp.ToString(), pnode->GetId()); return; } const CGovernanceObject& govobj = it->second; std::string strHash = it->first.ToString(); LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- attempting to sync govobj: %s, peer=%d\n", __func__, strHash, pnode->GetId()); if (govobj.IsSetCachedDelete() || govobj.IsSetExpired()) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- not syncing deleted/expired govobj: %s, peer=%d\n", __func__, strHash, pnode->GetId()); return; } const auto& fileVotes = govobj.GetVoteFile(); for (const auto& vote : fileVotes.GetVotes()) { uint256 nVoteHash = vote.GetHash(); bool onlyVotingKeyAllowed = govobj.GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL && vote.GetSignal() == VOTE_SIGNAL_FUNDING; if (filter.contains(nVoteHash) || !vote.IsValid(onlyVotingKeyAllowed)) { continue; } pnode->PushInventory(CInv(MSG_GOVERNANCE_OBJECT_VOTE, nVoteHash)); ++nVoteCount; } CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ_VOTE, nVoteCount)); LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- sent %d votes to peer=%d\n", __func__, nVoteCount, pnode->GetId()); } void CGovernanceManager::SyncObjects(CNode* pnode, CConnman& connman) const { // do not provide any data until our node is synced if (!masternodeSync.IsSynced()) return; if (netfulfilledman.HasFulfilledRequest(pnode->addr, NetMsgType::MNGOVERNANCESYNC)) { LOCK(cs_main); // Asking for the whole list multiple times in a short period of time is no good LogPrint(BCLog::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; // SYNC GOVERNANCE OBJECTS WITH OTHER CLIENT LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- syncing all objects to peer=%d\n", __func__, pnode->GetId()); LOCK(cs); // all valid objects, no votes for (const auto& objPair : mapObjects) { uint256 nHash = objPair.first; const CGovernanceObject& govobj = objPair.second; std::string strHash = nHash.ToString(); LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- attempting to sync govobj: %s, peer=%d\n", __func__, strHash, pnode->GetId()); if (govobj.IsSetCachedDelete() || govobj.IsSetExpired()) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- not syncing deleted/expired govobj: %s, peer=%d\n", __func__, strHash, pnode->GetId()); continue; } // Push the inventory budget proposal message over to the other client LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- syncing govobj: %s, peer=%d\n", __func__, strHash, pnode->GetId()); pnode->PushInventory(CInv(MSG_GOVERNANCE_OBJECT, nHash)); ++nObjCount; } CNetMsgMaker msgMaker(pnode->GetSendVersion()); connman.PushMessage(pnode, msgMaker.Make(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ, nObjCount)); LogPrint(BCLog::GOBJECT, "CGovernanceManager::%s -- sent %d objects to peer=%d\n", __func__, nObjCount, pnode->GetId()); } void CGovernanceManager::MasternodeRateUpdate(const CGovernanceObject& govobj) { if (govobj.GetObjectType() != GOVERNANCE_OBJECT_TRIGGER) return; const COutPoint& masternodeOutpoint = govobj.GetMasternodeOutpoint(); auto 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(); it->second.triggerBuffer.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() || !fRateChecksEnabled) { return true; } if (govobj.GetObjectType() != GOVERNANCE_OBJECT_TRIGGER) { 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) { LogPrint(BCLog::GOBJECT, "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) { LogPrint(BCLog::GOBJECT, "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; } auto it = mapLastMasternodeObject.find(masternodeOutpoint); if (it == mapLastMasternodeObject.end()) return true; if (it->second.fStatusOK && !fForce) { fRateCheckBypassed = true; return true; } // Allow 1 trigger per mn per cycle, with a small fudge factor double dMaxRate = 2 * 1.1 / double(nSuperblockCycleSeconds); // Temporary copy to check rate after new timestamp is added CRateCheckBuffer buffer = it->second.triggerBuffer; buffer.AddTimestamp(nTimestamp); double dRate = buffer.GetRate(); if (dRate < dMaxRate) { return true; } LogPrint(BCLog::GOBJECT, "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 false; } 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(BCLog::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(); LogPrint(BCLog::GOBJECT, "%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); LEAVE_CRITICAL_SECTION(cs) return false; } auto 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); LogPrint(BCLog::GOBJECT, "%s\n", ostr.str()); return false; } LogPrint(BCLog::GOBJECT, "%s\n", ostr.str()); LEAVE_CRITICAL_SECTION(cs) return false; } CGovernanceObject& govobj = it->second; if (govobj.IsSetCachedDelete() || govobj.IsSetExpired()) { LogPrint(BCLog::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(vote, exception) && cmapVoteToObject.Insert(nHashVote, &govobj); LEAVE_CRITICAL_SECTION(cs) return fOk; } void CGovernanceManager::CheckPostponedObjects(CConnman& connman) { if (!masternodeSync.IsSynced()) return; LOCK2(cs_main, cs); // Check postponed proposals for (auto it = mapPostponedObjects.begin(); it != mapPostponedObjects.end();) { const uint256& nHash = it->first; CGovernanceObject& govobj = it->second; assert(govobj.GetObjectType() != GOVERNANCE_OBJECT_TRIGGER); std::string strError; bool fMissingConfirmations; if (govobj.IsCollateralValid(strError, fMissingConfirmations)) { if (govobj.IsValidLocally(strError, false)) { AddGovernanceObject(govobj, connman); } else { LogPrint(BCLog::GOBJECT, "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 int64_t nNow = GetAdjustedTime(); int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing; for (auto it = setAdditionalRelayObjects.begin(); it != setAdditionalRelayObjects.end();) { auto itObject = mapObjects.find(*it); if (itObject != mapObjects.end()) { const 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) { LogPrint(BCLog::GOBJECT, "CGovernanceManager::CheckPostponedObjects -- additional relay: hash = %s\n", govobj.GetHash().ToString()); govobj.Relay(connman); } else { it++; continue; } } } else { LogPrint(BCLog::GOBJECT, "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(BCLog::GOBJECT, "CGovernanceManager::RequestGovernanceObject -- nHash %s peer=%d\n", nHash.ToString(), pfrom->GetId()); CNetMsgMaker msgMaker(pfrom->GetSendVersion()); CBloomFilter filter; filter.clear(); size_t nVoteCount = 0; if (fUseFilter) { LOCK(cs); const 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 (const auto& vote : vecVotes) { filter.insert(vote.GetHash()); } } } LogPrint(BCLog::GOBJECT, "CGovernanceManager::RequestGovernanceObject -- nHash %s nVoteCount %d peer=%d\n", nHash.ToString(), nVoteCount, pfrom->GetId()); 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 vTriggerObjHashes; std::vector vOtherObjHashes; // 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, (int)deterministicMNManager->GetListAtChainTip().GetValidMNsCount()))); } { LOCK(cs); if (mapObjects.empty()) return -2; for (const auto& objPair : mapObjects) { uint256 nHash = objPair.first; if (mapAskedRecently.count(nHash)) { auto it = mapAskedRecently[nHash].begin(); while (it != mapAskedRecently[nHash].end()) { if (it->second < nNow) { mapAskedRecently[nHash].erase(it++); } else { ++it; } } if (mapAskedRecently[nHash].size() >= nPeersPerHashMax) continue; } if (objPair.second.GetObjectType() == GOVERNANCE_OBJECT_TRIGGER) { vTriggerObjHashes.push_back(nHash); } else { vOtherObjHashes.push_back(nHash); } } } LogPrint(BCLog::GOBJECT, "CGovernanceManager::RequestGovernanceObjectVotes -- start: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size()); Shuffle(vTriggerObjHashes.begin(), vTriggerObjHashes.end(), FastRandomContext()); Shuffle(vOtherObjHashes.begin(), vOtherObjHashes.end(), FastRandomContext()); for (int i = 0; i < nMaxObjRequestsPerNode; ++i) { uint256 nHashGovobj; // ask for triggers first if (!vTriggerObjHashes.empty()) { nHashGovobj = vTriggerObjHashes.back(); } else { if (vOtherObjHashes.empty()) break; nHashGovobj = vOtherObjHashes.back(); } bool fAsked = false; for (const auto& pnode : vNodesCopy) { // Don't try to sync any data from outbound non-relay "masternode" connections. // Inbound connection this early is most likely a "masternode" connection // initiated from another node, so skip it too. if (!pnode->CanRelay() || (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 { LOCK(cs_main); size_t nProjectedSize = GetRequestedObjectCount(pnode->GetId()) + nProjectedVotes; if (nProjectedSize > MAX_INV_SZ) 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 (!vTriggerObjHashes.empty()) { vTriggerObjHashes.pop_back(); } else { vOtherObjHashes.pop_back(); } if (!fAsked) i--; } LogPrint(BCLog::GOBJECT, "CGovernanceManager::RequestGovernanceObjectVotes -- end: vTriggerObjHashes %d vOtherObjHashes %d mapAskedRecently %d\n", vTriggerObjHashes.size(), vOtherObjHashes.size(), mapAskedRecently.size()); return int(vTriggerObjHashes.size() + vOtherObjHashes.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) { auto 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() { LOCK(cs); cmapVoteToObject.Clear(); for (auto& objPair : mapObjects) { CGovernanceObject& govobj = objPair.second; std::vector vecVotes = govobj.GetVoteFile().GetVotes(); for (const auto& vecVote : vecVotes) { cmapVoteToObject.Insert(vecVote.GetHash(), &govobj); } } } void CGovernanceManager::AddCachedTriggers() { LOCK(cs); for (auto& objpair : mapObjects) { CGovernanceObject& govobj = objpair.second; if (govobj.GetObjectType() != GOVERNANCE_OBJECT_TRIGGER) { continue; } if (!triggerman.AddNewTrigger(govobj.GetHash())) { govobj.PrepareDeletion(GetAdjustedTime()); } } } 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 nOtherCount = 0; for (const auto& objPair : mapObjects) { switch (objPair.second.GetObjectType()) { case GOVERNANCE_OBJECT_PROPOSAL: nProposalCount++; break; case GOVERNANCE_OBJECT_TRIGGER: nTriggerCount++; break; default: nOtherCount++; break; } } return strprintf("Governance Objects: %d (Proposals: %d, Triggers: %d, Other: %d; Erased: %d), Votes: %d", (int)mapObjects.size(), nProposalCount, nTriggerCount, nOtherCount, (int)mapErasedGovernanceObjects.size(), (int)cmapVoteToObject.GetSize()); } UniValue CGovernanceManager::ToJson() const { LOCK(cs); int nProposalCount = 0; int nTriggerCount = 0; int nOtherCount = 0; for (const auto& objpair : mapObjects) { switch (objpair.second.GetObjectType()) { case GOVERNANCE_OBJECT_PROPOSAL: nProposalCount++; break; case GOVERNANCE_OBJECT_TRIGGER: nTriggerCount++; break; default: nOtherCount++; break; } } UniValue jsonObj(UniValue::VOBJ); jsonObj.pushKV("objects_total", (int)mapObjects.size()); jsonObj.pushKV("proposals", nProposalCount); jsonObj.pushKV("triggers", nTriggerCount); jsonObj.pushKV("other", nOtherCount); jsonObj.pushKV("erased", (int)mapErasedGovernanceObjects.size()); jsonObj.pushKV("votes", (int)cmapVoteToObject.GetSize()); return jsonObj; } 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(BCLog::GOBJECT, "CGovernanceManager::UpdatedBlockTip -- nCachedBlockHeight: %d\n", nCachedBlockHeight); if (deterministicMNManager->IsDIP3Enforced(pindex->nHeight)) { RemoveInvalidVotes(); } 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 (const uint256& nHash : vecHashes) { if (mapObjects.find(nHash) == mapObjects.end()) { vecHashesFiltered.push_back(nHash); } } } LogPrint(BCLog::GOBJECT, "CGovernanceObject::RequestOrphanObjects -- number objects = %d\n", vecHashesFiltered.size()); for (const uint256& nHash : vecHashesFiltered) { for (CNode* pnode : vNodesCopy) { if (!pnode->CanRelay()) { 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(); auto it = items.begin(); while (it != items.end()) { auto prevIt = it; ++it; const vote_time_pair_t& pairVote = prevIt->value; if (pairVote.second < nNow) { cmmapOrphanVotes.Erase(prevIt->key, prevIt->value); } } } void CGovernanceManager::RemoveInvalidVotes() { if (!masternodeSync.IsSynced()) { return; } LOCK(cs); auto curMNList = deterministicMNManager->GetListAtChainTip(); auto diff = lastMNListForVotingKeys->BuildDiff(curMNList); std::vector changedKeyMNs; for (const auto& p : diff.updatedMNs) { auto oldDmn = lastMNListForVotingKeys->GetMNByInternalId(p.first); if ((p.second.fields & CDeterministicMNStateDiff::Field_keyIDVoting) && p.second.state.keyIDVoting != oldDmn->pdmnState->keyIDVoting) { changedKeyMNs.emplace_back(oldDmn->collateralOutpoint); } else if ((p.second.fields & CDeterministicMNStateDiff::Field_pubKeyOperator) && p.second.state.pubKeyOperator != oldDmn->pdmnState->pubKeyOperator) { changedKeyMNs.emplace_back(oldDmn->collateralOutpoint); } } for (const auto& id : diff.removedMns) { auto oldDmn = lastMNListForVotingKeys->GetMNByInternalId(id); changedKeyMNs.emplace_back(oldDmn->collateralOutpoint); } for (const auto& outpoint : changedKeyMNs) { for (auto& p : mapObjects) { auto removed = p.second.RemoveInvalidVotes(outpoint); if (removed.empty()) { continue; } for (auto& voteHash : removed) { cmapVoteToObject.Erase(voteHash); cmapInvalidVotes.Erase(voteHash); cmmapOrphanVotes.Erase(voteHash); setRequestedVotes.erase(voteHash); } } } // store current MN list for the next run so that we can determine which keys changed lastMNListForVotingKeys = std::make_shared(curMNList); } bool AreSuperblocksEnabled() { return sporkManager.IsSporkActive(SPORK_9_SUPERBLOCKS_ENABLED); }