// 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 "core_io.h" #include "governance.h" #include "governance-classes.h" #include "governance-object.h" #include "governance-vote.h" #include "instantx.h" #include "masternode-sync.h" #include "masternodeman.h" #include "messagesigner.h" #include "util.h" #include CGovernanceObject::CGovernanceObject() : cs(), nObjectType(GOVERNANCE_OBJECT_UNKNOWN), nHashParent(), nRevision(0), nTime(0), nDeletionTime(0), nCollateralHash(), strData(), vinMasternode(), vchSig(), fCachedLocalValidity(false), strLocalValidityError(), fCachedFunding(false), fCachedValid(true), fCachedDelete(false), fCachedEndorsed(false), fDirtyCache(true), fExpired(false), fUnparsable(false), mapCurrentMNVotes(), mapOrphanVotes(), fileVotes() { // PARSE JSON DATA STORAGE (STRDATA) LoadData(); } CGovernanceObject::CGovernanceObject(uint256 nHashParentIn, int nRevisionIn, int64_t nTimeIn, uint256 nCollateralHashIn, std::string strDataIn) : cs(), nObjectType(GOVERNANCE_OBJECT_UNKNOWN), nHashParent(nHashParentIn), nRevision(nRevisionIn), nTime(nTimeIn), nDeletionTime(0), nCollateralHash(nCollateralHashIn), strData(strDataIn), vinMasternode(), vchSig(), fCachedLocalValidity(false), strLocalValidityError(), fCachedFunding(false), fCachedValid(true), fCachedDelete(false), fCachedEndorsed(false), fDirtyCache(true), fExpired(false), fUnparsable(false), mapCurrentMNVotes(), mapOrphanVotes(), fileVotes() { // PARSE JSON DATA STORAGE (STRDATA) LoadData(); } CGovernanceObject::CGovernanceObject(const CGovernanceObject& other) : cs(), nObjectType(other.nObjectType), nHashParent(other.nHashParent), nRevision(other.nRevision), nTime(other.nTime), nDeletionTime(other.nDeletionTime), nCollateralHash(other.nCollateralHash), strData(other.strData), vinMasternode(other.vinMasternode), vchSig(other.vchSig), fCachedLocalValidity(other.fCachedLocalValidity), strLocalValidityError(other.strLocalValidityError), fCachedFunding(other.fCachedFunding), fCachedValid(other.fCachedValid), fCachedDelete(other.fCachedDelete), fCachedEndorsed(other.fCachedEndorsed), fDirtyCache(other.fDirtyCache), fExpired(other.fExpired), fUnparsable(other.fUnparsable), mapCurrentMNVotes(other.mapCurrentMNVotes), mapOrphanVotes(other.mapOrphanVotes), fileVotes(other.fileVotes) {} bool CGovernanceObject::ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception, CConnman& connman) { if(!mnodeman.Has(vote.GetMasternodeOutpoint())) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Masternode index not found"; exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); if(mapOrphanVotes.Insert(vote.GetMasternodeOutpoint(), vote_time_pair_t(vote, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))) { if(pfrom) { mnodeman.AskForMN(pfrom, vote.GetMasternodeOutpoint(), connman); } LogPrintf("%s\n", ostr.str()); } else { LogPrint("gobject", "%s\n", ostr.str()); } return false; } vote_m_it it = mapCurrentMNVotes.find(vote.GetMasternodeOutpoint()); if(it == mapCurrentMNVotes.end()) { it = mapCurrentMNVotes.insert(vote_m_t::value_type(vote.GetMasternodeOutpoint(), vote_rec_t())).first; } vote_rec_t& recVote = it->second; vote_signal_enum_t eSignal = vote.GetSignal(); if(eSignal == VOTE_SIGNAL_NONE) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Vote signal: none"; LogPrint("gobject", "%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); return false; } if(eSignal > MAX_SUPPORTED_VOTE_SIGNAL) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Unsupported vote signal: " << CGovernanceVoting::ConvertSignalToString(vote.GetSignal()); LogPrintf("%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); return false; } vote_instance_m_it it2 = recVote.mapInstances.find(int(eSignal)); if(it2 == recVote.mapInstances.end()) { it2 = recVote.mapInstances.insert(vote_instance_m_t::value_type(int(eSignal), vote_instance_t())).first; } vote_instance_t& voteInstance = it2->second; // Reject obsolete votes if(vote.GetTimestamp() < voteInstance.nCreationTime) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Obsolete vote"; LogPrint("gobject", "%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_NONE); return false; } int64_t nNow = GetAdjustedTime(); int64_t nVoteTimeUpdate = voteInstance.nTime; if(governance.AreRateChecksEnabled()) { int64_t nTimeDelta = nNow - voteInstance.nTime; if(nTimeDelta < GOVERNANCE_UPDATE_MIN) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Masternode voting too often" << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort() << ", governance object hash = " << GetHash().ToString() << ", time delta = " << nTimeDelta; LogPrint("gobject", "%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_TEMPORARY_ERROR); nVoteTimeUpdate = nNow; return false; } } // Finally check that the vote is actually valid (done last because of cost of signature verification) if(!vote.IsValid(true)) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Invalid vote" << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort() << ", governance object hash = " << GetHash().ToString() << ", vote hash = " << vote.GetHash().ToString(); LogPrintf("%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); governance.AddInvalidVote(vote); return false; } if(!mnodeman.AddGovernanceVote(vote.GetMasternodeOutpoint(), vote.GetParentHash())) { std::ostringstream ostr; ostr << "CGovernanceObject::ProcessVote -- Unable to add governance vote" << ", MN outpoint = " << vote.GetMasternodeOutpoint().ToStringShort() << ", governance object hash = " << GetHash().ToString(); LogPrint("gobject", "%s\n", ostr.str()); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR); return false; } voteInstance = vote_instance_t(vote.GetOutcome(), nVoteTimeUpdate, vote.GetTimestamp()); if(!fileVotes.HasVote(vote.GetHash())) { fileVotes.AddVote(vote); } fDirtyCache = true; return true; } void CGovernanceObject::ClearMasternodeVotes() { vote_m_it it = mapCurrentMNVotes.begin(); while(it != mapCurrentMNVotes.end()) { if(!mnodeman.Has(it->first)) { fileVotes.RemoveVotesFromMasternode(it->first); mapCurrentMNVotes.erase(it++); } else { ++it; } } } std::string CGovernanceObject::GetSignatureMessage() const { LOCK(cs); std::string strMessage = nHashParent.ToString() + "|" + boost::lexical_cast(nRevision) + "|" + boost::lexical_cast(nTime) + "|" + strData + "|" + vinMasternode.prevout.ToStringShort() + "|" + nCollateralHash.ToString(); return strMessage; } void CGovernanceObject::SetMasternodeVin(const COutPoint& outpoint) { vinMasternode = CTxIn(outpoint); } bool CGovernanceObject::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode) { std::string strError; std::string strMessage = GetSignatureMessage(); LOCK(cs); if(!CMessageSigner::SignMessage(strMessage, vchSig, keyMasternode)) { LogPrintf("CGovernanceObject::Sign -- SignMessage() failed\n"); return false; } if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) { LogPrintf("CGovernanceObject::Sign -- VerifyMessage() failed, error: %s\n", strError); return false; } LogPrint("gobject", "CGovernanceObject::Sign -- pubkey id = %s, vin = %s\n", pubKeyMasternode.GetID().ToString(), vinMasternode.prevout.ToStringShort()); return true; } bool CGovernanceObject::CheckSignature(CPubKey& pubKeyMasternode) { std::string strError; std::string strMessage = GetSignatureMessage(); LOCK(cs); if(!CMessageSigner::VerifyMessage(pubKeyMasternode, vchSig, strMessage, strError)) { LogPrintf("CGovernance::CheckSignature -- VerifyMessage() failed, error: %s\n", strError); return false; } return true; } int CGovernanceObject::GetObjectSubtype() { // todo - 12.1 // - detect subtype from strData json, obj["subtype"] if(nObjectType == GOVERNANCE_OBJECT_TRIGGER) return TRIGGER_SUPERBLOCK; return -1; } uint256 CGovernanceObject::GetHash() const { // CREATE HASH OF ALL IMPORTANT PIECES OF DATA CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); ss << nHashParent; ss << nRevision; ss << nTime; ss << strData; ss << vinMasternode; ss << vchSig; // fee_tx is left out on purpose uint256 h1 = ss.GetHash(); DBG( printf("CGovernanceObject::GetHash %i %li %s\n", nRevision, nTime, strData.c_str()); ); return h1; } /** Return the actual object from the strData JSON structure. Returns an empty object on error. */ UniValue CGovernanceObject::GetJSONObject() { UniValue obj(UniValue::VOBJ); if(strData.empty()) { return obj; } UniValue objResult(UniValue::VOBJ); GetData(objResult); std::vector arr1 = objResult.getValues(); std::vector arr2 = arr1.at( 0 ).getValues(); obj = arr2.at( 1 ); return obj; } /** * LoadData * -------------------------------------------------------- * * Attempt to load data from strData * */ void CGovernanceObject::LoadData() { // todo : 12.1 - resolved //return; if(strData.empty()) { return; } try { // ATTEMPT TO LOAD JSON STRING FROM STRDATA UniValue objResult(UniValue::VOBJ); GetData(objResult); DBG( cout << "CGovernanceObject::LoadData strData = " << GetDataAsString() << endl; ); UniValue obj = GetJSONObject(); nObjectType = obj["type"].get_int(); } catch(std::exception& e) { fUnparsable = true; std::ostringstream ostr; ostr << "CGovernanceObject::LoadData Error parsing JSON" << ", e.what() = " << e.what(); DBG( cout << ostr.str() << endl; ); LogPrintf("%s\n", ostr.str()); return; } catch(...) { fUnparsable = true; std::ostringstream ostr; ostr << "CGovernanceObject::LoadData Unknown Error parsing JSON"; DBG( cout << ostr.str() << endl; ); LogPrintf("%s\n", ostr.str()); return; } } /** * GetData - Example usage: * -------------------------------------------------------- * * Decode governance object data into UniValue(VOBJ) * */ void CGovernanceObject::GetData(UniValue& objResult) { UniValue o(UniValue::VOBJ); std::string s = GetDataAsString(); o.read(s); objResult = o; } /** * GetData - As * -------------------------------------------------------- * */ std::string CGovernanceObject::GetDataAsHex() { return strData; } std::string CGovernanceObject::GetDataAsString() { std::vector v = ParseHex(strData); std::string s(v.begin(), v.end()); return s; } void CGovernanceObject::UpdateLocalValidity() { LOCK(cs_main); // THIS DOES NOT CHECK COLLATERAL, THIS IS CHECKED UPON ORIGINAL ARRIVAL fCachedLocalValidity = IsValidLocally(strLocalValidityError, false); }; bool CGovernanceObject::IsValidLocally(std::string& strError, bool fCheckCollateral) { bool fMissingMasternode = false; bool fMissingConfirmations = false; return IsValidLocally(strError, fMissingMasternode, fMissingConfirmations, fCheckCollateral); } bool CGovernanceObject::IsValidLocally(std::string& strError, bool& fMissingMasternode, bool& fMissingConfirmations, bool fCheckCollateral) { fMissingMasternode = false; fMissingConfirmations = false; if(fUnparsable) { strError = "Object data unparseable"; return false; } switch(nObjectType) { case GOVERNANCE_OBJECT_PROPOSAL: case GOVERNANCE_OBJECT_TRIGGER: case GOVERNANCE_OBJECT_WATCHDOG: break; default: strError = strprintf("Invalid object type %d", nObjectType); return false; } // IF ABSOLUTE NO COUNT (NO-YES VALID VOTES) IS MORE THAN 10% OF THE NETWORK MASTERNODES, OBJ IS INVALID // CHECK COLLATERAL IF REQUIRED (HIGH CPU USAGE) if(fCheckCollateral) { if((nObjectType == GOVERNANCE_OBJECT_TRIGGER) || (nObjectType == GOVERNANCE_OBJECT_WATCHDOG)) { std::string strOutpoint = vinMasternode.prevout.ToStringShort(); masternode_info_t infoMn; if(!mnodeman.GetMasternodeInfo(vinMasternode.prevout, infoMn)) { CMasternode::CollateralStatus err = CMasternode::CheckCollateral(vinMasternode.prevout, CPubKey()); if (err == CMasternode::COLLATERAL_UTXO_NOT_FOUND) { strError = "Failed to find Masternode UTXO, missing masternode=" + strOutpoint + "\n"; } else if (err == CMasternode::COLLATERAL_INVALID_AMOUNT) { strError = "Masternode UTXO should have 1000 DASH, missing masternode=" + strOutpoint + "\n"; } else if (err == CMasternode::COLLATERAL_INVALID_PUBKEY) { fMissingMasternode = true; strError = "Masternode not found: " + strOutpoint; } else if (err == CMasternode::COLLATERAL_OK) { // this should never happen with CPubKey() as a param strError = "CheckCollateral critical failure! Masternode: " + strOutpoint; } return false; } // Check that we have a valid MN signature if(!CheckSignature(infoMn.pubKeyMasternode)) { strError = "Invalid masternode signature for: " + strOutpoint + ", pubkey id = " + infoMn.pubKeyMasternode.GetID().ToString(); return false; } return true; } if (!IsCollateralValid(strError, fMissingConfirmations)) return false; } /* TODO - There might be an issue with multisig in the coinbase on mainnet, we will add support for it in a future release. - Post 12.2+ (test multisig coinbase transaction) */ // 12.1 - todo - compile error // if(address.IsPayToScriptHash()) { // strError = "Governance system - multisig is not currently supported"; // return false; // } return true; } CAmount CGovernanceObject::GetMinCollateralFee() { // Only 1 type has a fee for the moment but switch statement allows for future object types switch(nObjectType) { case GOVERNANCE_OBJECT_PROPOSAL: return GOVERNANCE_PROPOSAL_FEE_TX; case GOVERNANCE_OBJECT_TRIGGER: return 0; case GOVERNANCE_OBJECT_WATCHDOG: return 0; default: return MAX_MONEY; } } bool CGovernanceObject::IsCollateralValid(std::string& strError, bool& fMissingConfirmations) { strError = ""; fMissingConfirmations = false; CAmount nMinFee = GetMinCollateralFee(); uint256 nExpectedHash = GetHash(); CTransaction txCollateral; uint256 nBlockHash; // RETRIEVE TRANSACTION IN QUESTION if(!GetTransaction(nCollateralHash, txCollateral, Params().GetConsensus(), nBlockHash, true)){ strError = strprintf("Can't find collateral tx %s", txCollateral.ToString()); LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError); return false; } if(txCollateral.vout.size() < 1) { strError = strprintf("tx vout size less than 1 | %d", txCollateral.vout.size()); LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError); return false; } // LOOK FOR SPECIALIZED GOVERNANCE SCRIPT (PROOF OF BURN) CScript findScript; findScript << OP_RETURN << ToByteVector(nExpectedHash); DBG( cout << "IsCollateralValid: txCollateral.vout.size() = " << txCollateral.vout.size() << endl; ); DBG( cout << "IsCollateralValid: findScript = " << ScriptToAsmStr( findScript, false ) << endl; ); DBG( cout << "IsCollateralValid: nMinFee = " << nMinFee << endl; ); bool foundOpReturn = false; BOOST_FOREACH(const CTxOut o, txCollateral.vout) { DBG( cout << "IsCollateralValid txout : " << o.ToString() << ", o.nValue = " << o.nValue << ", o.scriptPubKey = " << ScriptToAsmStr( o.scriptPubKey, false ) << endl; ); if(!o.scriptPubKey.IsPayToPublicKeyHash() && !o.scriptPubKey.IsUnspendable()) { strError = strprintf("Invalid Script %s", txCollateral.ToString()); LogPrintf ("CGovernanceObject::IsCollateralValid -- %s\n", strError); return false; } if(o.scriptPubKey == findScript && o.nValue >= nMinFee) { DBG( cout << "IsCollateralValid foundOpReturn = true" << endl; ); foundOpReturn = true; } else { DBG( cout << "IsCollateralValid No match, continuing" << endl; ); } } if(!foundOpReturn){ strError = strprintf("Couldn't find opReturn %s in %s", nExpectedHash.ToString(), txCollateral.ToString()); LogPrintf ("CGovernanceObject::IsCollateralValid -- %s\n", strError); return false; } // GET CONFIRMATIONS FOR TRANSACTION AssertLockHeld(cs_main); int nConfirmationsIn = instantsend.GetConfirmations(nCollateralHash); if (nBlockHash != uint256()) { BlockMap::iterator mi = mapBlockIndex.find(nBlockHash); if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; if (chainActive.Contains(pindex)) { nConfirmationsIn += chainActive.Height() - pindex->nHeight + 1; } } } if(nConfirmationsIn < GOVERNANCE_FEE_CONFIRMATIONS) { strError = strprintf("Collateral requires at least %d confirmations to be relayed throughout the network (it has only %d)", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn); if (nConfirmationsIn >= GOVERNANCE_MIN_RELAY_FEE_CONFIRMATIONS) { fMissingConfirmations = true; strError += ", pre-accepted -- waiting for required confirmations"; } else { strError += ", rejected -- try again later"; } LogPrintf ("CGovernanceObject::IsCollateralValid -- %s\n", strError); return false; } strError = "valid"; return true; } int CGovernanceObject::CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const { int nCount = 0; for(vote_m_cit it = mapCurrentMNVotes.begin(); it != mapCurrentMNVotes.end(); ++it) { const vote_rec_t& recVote = it->second; vote_instance_m_cit it2 = recVote.mapInstances.find(eVoteSignalIn); if(it2 == recVote.mapInstances.end()) { continue; } const vote_instance_t& voteInstance = it2->second; if(voteInstance.eOutcome == eVoteOutcomeIn) { ++nCount; } } return nCount; } /** * Get specific vote counts for each outcome (funding, validity, etc) */ int CGovernanceObject::GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn) const { return GetYesCount(eVoteSignalIn) - GetNoCount(eVoteSignalIn); } int CGovernanceObject::GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn) const { return GetNoCount(eVoteSignalIn) - GetYesCount(eVoteSignalIn); } int CGovernanceObject::GetYesCount(vote_signal_enum_t eVoteSignalIn) const { return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_YES); } int CGovernanceObject::GetNoCount(vote_signal_enum_t eVoteSignalIn) const { return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_NO); } int CGovernanceObject::GetAbstainCount(vote_signal_enum_t eVoteSignalIn) const { return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_ABSTAIN); } bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) { vote_m_it it = mapCurrentMNVotes.find(mnCollateralOutpoint); if (it == mapCurrentMNVotes.end()) { return false; } voteRecord = it->second; return true; } void CGovernanceObject::Relay(CConnman& connman) { // Do not relay until fully synced if(!masternodeSync.IsSynced()) { LogPrint("gobject", "CGovernanceObject::Relay -- won't relay until fully synced\n"); return; } CInv inv(MSG_GOVERNANCE_OBJECT, GetHash()); connman.RelayInv(inv, MIN_GOVERNANCE_PEER_PROTO_VERSION); } void CGovernanceObject::UpdateSentinelVariables() { // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED int nMnCount = mnodeman.CountEnabled(); if(nMnCount == 0) return; // CALCULATE THE MINUMUM VOTE COUNT REQUIRED FOR FULL SIGNAL // todo - 12.1 - should be set to `10` after governance vote compression is implemented int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nMnCount / 10); int nAbsDeleteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, (2 * nMnCount) / 3); // todo - 12.1 - Temporarily set to 1 for testing - reverted //nAbsVoteReq = 1; // SET SENTINEL FLAGS TO FALSE fCachedFunding = false; fCachedValid = true; //default to valid fCachedEndorsed = false; fDirtyCache = false; // SET SENTINEL FLAGS TO TRUE IF MIMIMUM SUPPORT LEVELS ARE REACHED // ARE ANY OF THESE FLAGS CURRENTLY ACTIVATED? if(GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = true; if((GetAbsoluteYesCount(VOTE_SIGNAL_DELETE) >= nAbsDeleteReq) && !fCachedDelete) { fCachedDelete = true; if(nDeletionTime == 0) { nDeletionTime = GetAdjustedTime(); } } if(GetAbsoluteYesCount(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true; if(GetAbsoluteNoCount(VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false; } void CGovernanceObject::swap(CGovernanceObject& first, CGovernanceObject& second) // nothrow { // enable ADL (not necessary in our case, but good practice) using std::swap; // by swapping the members of two classes, // the two classes are effectively swapped swap(first.nHashParent, second.nHashParent); swap(first.nRevision, second.nRevision); swap(first.nTime, second.nTime); swap(first.nDeletionTime, second.nDeletionTime); swap(first.nCollateralHash, second.nCollateralHash); swap(first.strData, second.strData); swap(first.nObjectType, second.nObjectType); // swap all cached valid flags swap(first.fCachedFunding, second.fCachedFunding); swap(first.fCachedValid, second.fCachedValid); swap(first.fCachedDelete, second.fCachedDelete); swap(first.fCachedEndorsed, second.fCachedEndorsed); swap(first.fDirtyCache, second.fDirtyCache); swap(first.fExpired, second.fExpired); } void CGovernanceObject::CheckOrphanVotes(CConnman& connman) { int64_t nNow = GetAdjustedTime(); const vote_mcache_t::list_t& listVotes = mapOrphanVotes.GetItemList(); vote_mcache_t::list_cit it = listVotes.begin(); while(it != listVotes.end()) { bool fRemove = false; const COutPoint& key = it->key; const vote_time_pair_t& pairVote = it->value; const CGovernanceVote& vote = pairVote.first; if(pairVote.second < nNow) { fRemove = true; } else if(!mnodeman.Has(vote.GetMasternodeOutpoint())) { ++it; continue; } CGovernanceException exception; if(!ProcessVote(NULL, vote, exception, connman)) { LogPrintf("CGovernanceObject::CheckOrphanVotes -- Failed to add orphan vote: %s\n", exception.what()); } else { vote.Relay(connman); fRemove = true; } ++it; if(fRemove) { mapOrphanVotes.Erase(key, pairVote); } } }