From 1b90d66ab659ab338851ef22d828fd7b00d43dbc Mon Sep 17 00:00:00 2001 From: Tim Flynn Date: Thu, 24 Nov 2016 13:12:05 -0500 Subject: [PATCH] Implement orphan object and vote timeouts (#1166) --- src/Makefile.am | 2 + src/governance-object.cpp | 753 ++++++++++++++++++++++++++++++++++++++ src/governance-object.h | 350 ++++++++++++++++++ src/governance.cpp | 753 ++------------------------------------ src/governance.h | 321 +--------------- 5 files changed, 1145 insertions(+), 1034 deletions(-) create mode 100644 src/governance-object.cpp create mode 100644 src/governance-object.h diff --git a/src/Makefile.am b/src/Makefile.am index 9fe825297..eba8d98c5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -102,6 +102,7 @@ BITCOIN_CORE_H = \ governance.h \ governance-classes.h \ governance-exceptions.h \ + governance-object.h \ governance-vote.h \ governance-votedb.h \ flat-database.h \ @@ -205,6 +206,7 @@ libbitcoin_server_a_SOURCES = \ dbwrapper.cpp \ governance.cpp \ governance-classes.cpp \ + governance-object.cpp \ governance-vote.cpp \ governance-votedb.cpp \ main.cpp \ diff --git a/src/governance-object.cpp b/src/governance-object.cpp new file mode 100644 index 000000000..c8d5b9104 --- /dev/null +++ b/src/governance-object.cpp @@ -0,0 +1,753 @@ +// Copyright (c) 2014-2016 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 "main.h" +#include "init.h" + +#include "flat-database.h" +#include "governance.h" +#include "governance-object.h" +#include "governance-vote.h" +#include "governance-classes.h" +#include "masternode.h" +#include "governance.h" +#include "darksend.h" +#include "masternodeman.h" +#include "masternode-sync.h" +#include "netfulfilledman.h" +#include "util.h" +#include "addrman.h" +#include +#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) +{ + int nMNIndex = governance.GetMasternodeIndex(vote.GetVinMasternode()); + if(nMNIndex < 0) { + std::ostringstream ostr; + ostr << "CGovernanceObject::ProcessVote -- Masternode index not found\n"; + exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); + if(mapOrphanVotes.Insert(vote.GetVinMasternode(), vote_time_pair_t(vote, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))) { + if(pfrom) { + mnodeman.AskForMN(pfrom, vote.GetVinMasternode()); + } + LogPrintf(ostr.str().c_str()); + } + else { + LogPrint("gobject", ostr.str().c_str()); + } + return false; + } + + vote_m_it it = mapCurrentMNVotes.find(nMNIndex); + if(it == mapCurrentMNVotes.end()) { + it = mapCurrentMNVotes.insert(vote_m_t::value_type(nMNIndex,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" << "\n"; + LogPrint("gobject", ostr.str().c_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()) << "\n"; + LogPrintf(ostr.str().c_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; + int64_t nNow = GetTime(); + 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.GetVinMasternode().prevout.ToStringShort() + << ", governance object hash = " << GetHash().ToString() + << ", time delta = " << nTimeDelta << "\n"; + LogPrint("gobject", ostr.str().c_str()); + exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_TEMPORARY_ERROR); + 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.GetVinMasternode().prevout.ToStringShort() + << ", governance object hash = " << GetHash().ToString() + << ", vote hash = " << vote.GetHash().ToString() << "\n"; + LogPrintf(ostr.str().c_str()); + exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); + governance.AddInvalidVote(vote); + return false; + } + voteInstance = vote_instance_t(vote.GetOutcome(), nNow); + fileVotes.AddVote(vote); + fDirtyCache = true; + return true; +} + +void CGovernanceObject::RebuildVoteMap() +{ + vote_m_t mapMNVotesNew; + for(vote_m_it it = mapCurrentMNVotes.begin(); it != mapCurrentMNVotes.end(); ++it) { + CTxIn vinMasternode; + if(mnodeman.GetMasternodeVinForIndexOld(it->first, vinMasternode)) { + int nNewIndex = mnodeman.GetMasternodeIndex(vinMasternode); + if((nNewIndex >= 0)) { + mapMNVotesNew[nNewIndex] = it->second; + } + } + } + mapCurrentMNVotes = mapMNVotesNew; +} + +void CGovernanceObject::ClearMasternodeVotes() +{ + vote_m_it it = mapCurrentMNVotes.begin(); + while(it != mapCurrentMNVotes.end()) { + bool fIndexRebuilt = false; + CTxIn vinMasternode; + bool fRemove = true; + if(mnodeman.Get(it->first, vinMasternode, fIndexRebuilt)) { + if(mnodeman.Has(vinMasternode)) { + fRemove = false; + } + else { + fileVotes.RemoveVotesFromMasternode(vinMasternode); + } + } + + if(fRemove) { + mapCurrentMNVotes.erase(it++); + } + else { + ++it; + } + } +} + +void CGovernanceObject::SetMasternodeInfo(const CTxIn& vin) +{ + vinMasternode = vin; +} + +bool CGovernanceObject::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode) +{ + LOCK(cs); + + std::string strError; + uint256 nHash = GetHash(); + std::string strMessage = nHash.ToString(); + + if(!darkSendSigner.SignMessage(strMessage, vchSig, keyMasternode)) { + LogPrintf("CGovernanceObject::Sign -- SignMessage() failed\n"); + return false; + } + + if(!darkSendSigner.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) +{ + LOCK(cs); + std::string strError; + uint256 nHash = GetHash(); + std::string strMessage = nHash.ToString(); + + if(!darkSendSigner.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() +{ + // CREATE HASH OF ALL IMPORTANT PIECES OF DATA + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + ss << nHashParent; + ss << nRevision; + ss << nTime; + ss << strData; + // 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( ostr.str().c_str() ); + return; + } + catch(...) { + fUnparsable = true; + std::ostringstream ostr; + ostr << "CGovernanceObject::LoadData Unknown Error parsing JSON"; + DBG( cout << ostr.str() << endl; ); + LogPrintf( ostr.str().c_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(const CBlockIndex *pCurrentBlockIndex) +{ + // THIS DOES NOT CHECK COLLATERAL, THIS IS CHECKED UPON ORIGINAL ARRIVAL + fCachedLocalValidity = IsValidLocally(pCurrentBlockIndex, strLocalValidityError, false); +}; + + +bool CGovernanceObject::IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool fCheckCollateral) +{ + bool fMissingMasternode = false; + + return IsValidLocally(pindex, strError, fMissingMasternode, fCheckCollateral); +} + +bool CGovernanceObject::IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool& fMissingMasternode, bool fCheckCollateral) +{ + fMissingMasternode = false; + if(!pindex) { + strError = "Tip is NULL"; + return true; + } + + 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 + + if(GetAbsoluteNoCount(VOTE_SIGNAL_VALID) > mnodeman.CountEnabled(MIN_GOVERNANCE_PEER_PROTO_VERSION)/10) { + strError = "Automated removal"; + return false; + } + + // 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 = mnodeman.GetMasternodeInfo(vinMasternode); + if(!infoMn.fInfoValid) { + fMissingMasternode = true; + strError = "Masternode not found: " + 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; + } + + // Only perform rate check if we are synced because during syncing it is expected + // that objects will be seen in rapid succession + if(masternodeSync.IsSynced()) { + if(!governance.MasternodeRateCheck(vinMasternode, nObjectType)) { + strError = "Masternode attempting to create too many objects: " + strOutpoint; + return false; + } + } + + return true; + } + + if(!IsCollateralValid(strError)) { + // strError set in IsCollateralValid + if(strError == "") strError = "Collateral is invalid"; + 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) +{ + strError = ""; + 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.IsNormalPaymentScript() && !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 + + LOCK(cs_main); + int nConfirmationsIn = GetIXConfirmations(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 = "valid"; + } else { + strError = strprintf("Collateral requires at least %d confirmations - %d confirmations", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn); + LogPrintf ("CGovernanceObject::IsCollateralValid -- %s - %d confirmations\n", strError, nConfirmationsIn); + return false; + } + + 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 CTxIn& mnCollateralOutpoint, vote_rec_t& voteRecord) +{ + int nMNIndex = governance.GetMasternodeIndex(mnCollateralOutpoint); + vote_m_it it = mapCurrentMNVotes.find(nMNIndex); + if (it == mapCurrentMNVotes.end()) { + return false; + } + voteRecord = it->second; + return true; +} + +void CGovernanceObject::Relay() +{ + CInv inv(MSG_GOVERNANCE_OBJECT, GetHash()); + RelayInv(inv, PROTOCOL_VERSION); +} + +void CGovernanceObject::UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex) +{ + // 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); + // todo - 12.1 - Temporarily set to 1 for testing - reverted + //nAbsVoteReq = 1; + + // SET SENTINEL FLAGS TO FALSE + + fCachedFunding = false; + fCachedValid = true; //default to valid + fCachedDelete = false; + 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_VALID) >= nAbsVoteReq) fCachedValid = true; + if(GetAbsoluteYesCount(VOTE_SIGNAL_DELETE) >= nAbsVoteReq) { + fCachedDelete = true; + nDeletionTime = GetAdjustedTime(); + } + if(GetAbsoluteYesCount(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true; + + // ARE ANY OF THE VOTING FLAGS NEGATIVELY SET BY THE NETWORK? + // THIS CAN BE CACHED, THE VOTES BEING HOT-LOADED AS NEEDED TO RECALCULATE + + if(GetAbsoluteNoCount(VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = false; + if(GetAbsoluteNoCount(VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false; + if(GetAbsoluteNoCount(VOTE_SIGNAL_DELETE) >= nAbsVoteReq) fCachedDelete = false; + if(GetAbsoluteNoCount(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = 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() +{ + 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 CTxIn& 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.GetVinMasternode())) { + ++it; + continue; + } + CGovernanceException exception; + if(!ProcessVote(NULL, vote, exception)) { + LogPrintf("CGovernanceObject::CheckOrphanVotes -- Failed to add orphan vote: %s\n", exception.what()); + } + else { + fRemove = true; + } + ++it; + if(fRemove) { + mapOrphanVotes.Erase(key, pairVote); + } + } +} diff --git a/src/governance-object.h b/src/governance-object.h new file mode 100644 index 000000000..36f1ef70f --- /dev/null +++ b/src/governance-object.h @@ -0,0 +1,350 @@ +// Copyright (c) 2014-2016 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. + +#ifndef GOVERNANCE_OBJECT_H +#define GOVERNANCE_OBJECT_H + +//#define ENABLE_DASH_DEBUG + +#include "util.h" +#include "main.h" +#include "sync.h" +#include "net.h" +#include "key.h" +#include "util.h" +#include "base58.h" +#include "masternode.h" +#include "governance-exceptions.h" +#include "governance-vote.h" +#include "governance-votedb.h" +#include "masternodeman.h" +#include +#include "init.h" +#include +#include "utilstrencodings.h" +#include "cachemap.h" +#include "cachemultimap.h" + +#include +#include + +class CGovernanceManager; +class CGovernanceTriggerManager; +class CGovernanceObject; +class CGovernanceVote; + +static const int MAX_GOVERNANCE_OBJECT_DATA_SIZE = 16 * 1024; +static const int MIN_GOVERNANCE_PEER_PROTO_VERSION = 70203; + +static const int GOVERNANCE_OBJECT_UNKNOWN = 0; +static const int GOVERNANCE_OBJECT_PROPOSAL = 1; +static const int GOVERNANCE_OBJECT_TRIGGER = 2; +static const int GOVERNANCE_OBJECT_WATCHDOG = 3; + +static const CAmount GOVERNANCE_PROPOSAL_FEE_TX = (0.33*COIN); + +static const int64_t GOVERNANCE_FEE_CONFIRMATIONS = 6; +static const int64_t GOVERNANCE_UPDATE_MIN = 60*60; +static const int64_t GOVERNANCE_DELETION_DELAY = 10*60; +static const int64_t GOVERNANCE_ORPHAN_EXPIRATION_TIME = 10*60; + + +// FOR SEEN MAP ARRAYS - GOVERNANCE OBJECTS AND VOTES +static const int SEEN_OBJECT_IS_VALID = 0; +static const int SEEN_OBJECT_ERROR_INVALID = 1; +static const int SEEN_OBJECT_ERROR_IMMATURE = 2; +static const int SEEN_OBJECT_EXECUTED = 3; //used for triggers +static const int SEEN_OBJECT_UNKNOWN = 4; // the default + +typedef std::pair vote_time_pair_t; + +inline bool operator<(const vote_time_pair_t& p1, const vote_time_pair_t& p2) +{ + return (p1.first < p2.first); +} + +struct vote_instance_t { + + vote_outcome_enum_t eOutcome; + int64_t nTime; + + vote_instance_t(vote_outcome_enum_t eOutcomeIn = VOTE_OUTCOME_NONE, int64_t nTimeIn = 0) + : eOutcome(eOutcomeIn), + nTime(nTimeIn) + {} + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + int nOutcome = int(eOutcome); + READWRITE(nOutcome); + READWRITE(nTime); + if(ser_action.ForRead()) { + eOutcome = vote_outcome_enum_t(nOutcome); + } + } +}; + +typedef std::map vote_instance_m_t; + +typedef vote_instance_m_t::iterator vote_instance_m_it; + +typedef vote_instance_m_t::const_iterator vote_instance_m_cit; + +struct vote_rec_t { + vote_instance_m_t mapInstances; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(mapInstances); + } +}; + +/** +* Governance Object +* +*/ + +class CGovernanceObject +{ + friend class CGovernanceManager; + + friend class CGovernanceTriggerManager; + +public: // Types + typedef std::map vote_m_t; + + typedef vote_m_t::iterator vote_m_it; + + typedef vote_m_t::const_iterator vote_m_cit; + + typedef CacheMultiMap vote_mcache_t; + +private: + /// critical section to protect the inner data structures + mutable CCriticalSection cs; + + /// Object typecode + int nObjectType; + + /// parent object, 0 is root + uint256 nHashParent; + + /// object revision in the system + int nRevision; + + /// time this object was created + int64_t nTime; + + /// time this object was marked for deletion + int64_t nDeletionTime; + + /// fee-tx + uint256 nCollateralHash; + + /// Data field - can be used for anything + std::string strData; + + /// Masternode info for signed objects + CTxIn vinMasternode; + std::vector vchSig; + + /// is valid by blockchain + bool fCachedLocalValidity; + std::string strLocalValidityError; + + // VARIOUS FLAGS FOR OBJECT / SET VIA MASTERNODE VOTING + + /// true == minimum network support has been reached for this object to be funded (doesn't mean it will for sure though) + bool fCachedFunding; + + /// true == minimum network has been reached flagging this object as a valid and understood goverance object (e.g, the serialized data is correct format, etc) + bool fCachedValid; + + /// true == minimum network support has been reached saying this object should be deleted from the system entirely + bool fCachedDelete; + + /** true == minimum network support has been reached flagging this object as endorsed by an elected representative body + * (e.g. business review board / technecial review board /etc) + */ + bool fCachedEndorsed; + + /// object was updated and cached values should be updated soon + bool fDirtyCache; + + /// Object is no longer of interest + bool fExpired; + + /// Failed to parse object data + bool fUnparsable; + + vote_m_t mapCurrentMNVotes; + + /// Limited map of votes orphaned by MN + vote_mcache_t mapOrphanVotes; + + CGovernanceObjectVoteFile fileVotes; + +public: + CGovernanceObject(); + + CGovernanceObject(uint256 nHashParentIn, int nRevisionIn, int64_t nTime, uint256 nCollateralHashIn, std::string strDataIn); + + CGovernanceObject(const CGovernanceObject& other); + + void swap(CGovernanceObject& first, CGovernanceObject& second); // nothrow + + // Public Getter methods + + int64_t GetCreationTime() const { + return nTime; + } + + int64_t GetDeletionTime() const { + return nDeletionTime; + } + + int GetObjectType() const { + return nObjectType; + } + + const uint256& GetCollateralHash() const { + return nCollateralHash; + } + + const CTxIn& GetMasternodeVin() const { + return vinMasternode; + } + + bool IsSetCachedFunding() const { + return fCachedFunding; + } + + bool IsSetCachedValid() const { + return fCachedValid; + } + + bool IsSetCachedDelete() const { + return fCachedDelete; + } + + bool IsSetCachedEndorsed() const { + return fCachedEndorsed; + } + + bool IsSetDirtyCache() const { + return fDirtyCache; + } + + bool IsSetExpired() const { + return fExpired; + } + + void InvalidateVoteCache() { + fDirtyCache = true; + } + + CGovernanceObjectVoteFile& GetVoteFile() { + return fileVotes; + } + + // Signature related functions + + void SetMasternodeInfo(const CTxIn& vin); + bool Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode); + bool CheckSignature(CPubKey& pubKeyMasternode); + + // CORE OBJECT FUNCTIONS + + bool IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool fCheckCollateral); + + bool IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool& fMissingMasternode, bool fCheckCollateral); + + /// Check the collateral transaction for the budget proposal/finalized budget + bool IsCollateralValid(std::string& strError); + + void UpdateLocalValidity(const CBlockIndex *pCurrentBlockIndex); + + void UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex); + + int GetObjectSubtype(); + + CAmount GetMinCollateralFee(); + + UniValue GetJSONObject(); + + void Relay(); + + uint256 GetHash(); + + // GET VOTE COUNT FOR SIGNAL + + int CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const; + + int GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn) const; + int GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn) const; + int GetYesCount(vote_signal_enum_t eVoteSignalIn) const; + int GetNoCount(vote_signal_enum_t eVoteSignalIn) const; + int GetAbstainCount(vote_signal_enum_t eVoteSignalIn) const; + + bool GetCurrentMNVotes(const CTxIn& mnCollateralOutpoint, vote_rec_t& voteRecord); + + // FUNCTIONS FOR DEALING WITH DATA STRING + + std::string GetDataAsHex(); + std::string GetDataAsString(); + + // SERIALIZER + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + // SERIALIZE DATA FOR SAVING/LOADING OR NETWORK FUNCTIONS + + READWRITE(nHashParent); + READWRITE(nRevision); + READWRITE(nTime); + READWRITE(nCollateralHash); + READWRITE(LIMITED_STRING(strData, MAX_GOVERNANCE_OBJECT_DATA_SIZE)); + READWRITE(nObjectType); + READWRITE(vinMasternode); + READWRITE(vchSig); + if(nType & SER_DISK) { + // Only include these for the disk file format + LogPrint("gobject", "CGovernanceObject::SerializationOp Reading/writing votes from/to disk\n"); + READWRITE(mapCurrentMNVotes); + READWRITE(fileVotes); + LogPrint("gobject", "CGovernanceObject::SerializationOp hash = %s, vote count = %d\n", GetHash().ToString(), fileVotes.GetVoteCount()); + } + + // AFTER DESERIALIZATION OCCURS, CACHED VARIABLES MUST BE CALCULATED MANUALLY + } + +private: + // FUNCTIONS FOR DEALING WITH DATA STRING + void LoadData(); + void GetData(UniValue& objResult); + + bool ProcessVote(CNode* pfrom, + const CGovernanceVote& vote, + CGovernanceException& exception); + + void RebuildVoteMap(); + + /// Called when MN's which have voted on this object have been removed + void ClearMasternodeVotes(); + + void CheckOrphanVotes(); + +}; + + +#endif diff --git a/src/governance.cpp b/src/governance.cpp index 8ecfce66b..c69782bac 100644 --- a/src/governance.cpp +++ b/src/governance.cpp @@ -8,6 +8,7 @@ #include "flat-database.h" #include "governance.h" +#include "governance-object.h" #include "governance-vote.h" #include "governance-classes.h" #include "masternode.h" @@ -176,7 +177,7 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C bool fIsValid = govobj.IsValidLocally(pCurrentBlockIndex, strError, fMasternodeMissing, true); if(fMasternodeMissing) { - mapMasternodeOrphanObjects.insert(std::make_pair(govobj.GetHash(), govobj)); + mapMasternodeOrphanObjects.insert(std::make_pair(govobj.GetHash(), object_time_pair_t(govobj, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))); LogPrint("gobject", "CGovernanceManager -- Missing masternode for: %s\n", strHash); // fIsValid must also be false here so we will return early in the next if block } @@ -248,21 +249,30 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C void CGovernanceManager::CheckOrphanVotes(CNode* pfrom, CGovernanceObject& govobj, CGovernanceException& exception) { uint256 nHash = govobj.GetHash(); - std::vector vecVotes; - mapOrphanVotes.GetAll(nHash, vecVotes); + std::vector vecVotePairs; + mapOrphanVotes.GetAll(nHash, vecVotePairs); - for(size_t i = 0; i < vecVotes.size(); ++i) { - CGovernanceVote& vote = vecVotes[i]; + 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(govobj.ProcessVote(pfrom, vote, exception)) { - vecVotes[i].Relay(); - mapOrphanVotes.Erase(nHash, vote); + if(pairVote.second < nNow) { + fRemove = true; + } + else if(govobj.ProcessVote(pfrom, vote, exception)) { + vote.Relay(); + fRemove = true; } else { if((exception.GetNodePenalty() != 0) && masternodeSync.IsSynced()) { Misbehaving(pfrom->GetId(), exception.GetNodePenalty()); } } + if(fRemove) { + mapOrphanVotes.Erase(nHash, pairVote); + } } } @@ -682,7 +692,7 @@ bool CGovernanceManager::ProcessVote(CNode* pfrom, const CGovernanceVote& vote, << ", MN outpoint = " << vote.GetVinMasternode().prevout.ToStringShort() << ", governance object hash = " << vote.GetParentHash().ToString() << "\n"; exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); - if(mapOrphanVotes.Insert(nHashGovobj, vote)) { + if(mapOrphanVotes.Insert(nHashGovobj, vote_time_pair_t(vote, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME))) { RequestGovernanceObject(pfrom, nHashGovobj); LogPrintf(ostr.str().c_str()); } @@ -719,10 +729,17 @@ void CGovernanceManager::CheckMasternodeOrphanVotes() void CGovernanceManager::CheckMasternodeOrphanObjects() { LOCK(cs); + int64_t nNow = GetAdjustedTime(); fRateChecksEnabled = false; - object_m_it it = mapMasternodeOrphanObjects.begin(); + object_time_m_it it = mapMasternodeOrphanObjects.begin(); while(it != mapMasternodeOrphanObjects.end()) { - CGovernanceObject& govobj = it->second; + object_time_pair_t& pair = it->second; + CGovernanceObject& govobj = pair.first; + + if(pair.second < nNow) { + mapMasternodeOrphanObjects.erase(it++); + continue; + } string strError; bool fMasternodeMissing = false; @@ -825,640 +842,7 @@ void CGovernanceManager::AddCachedTriggers() } triggerman.AddNewTrigger(govobj.GetHash()); - } -} - -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) -{ - int nMNIndex = governance.GetMasternodeIndex(vote.GetVinMasternode()); - if(nMNIndex < 0) { - std::ostringstream ostr; - ostr << "CGovernanceObject::ProcessVote -- Masternode index not found\n"; - exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); - if(mapOrphanVotes.Insert(vote.GetVinMasternode(), vote)) { - if(pfrom) { - mnodeman.AskForMN(pfrom, vote.GetVinMasternode()); - } - LogPrintf(ostr.str().c_str()); - } - else { - LogPrint("gobject", ostr.str().c_str()); - } - return false; } - - vote_m_it it = mapCurrentMNVotes.find(nMNIndex); - if(it == mapCurrentMNVotes.end()) { - it = mapCurrentMNVotes.insert(vote_m_t::value_type(nMNIndex,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" << "\n"; - LogPrint("gobject", ostr.str().c_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()) << "\n"; - LogPrintf(ostr.str().c_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; - int64_t nNow = GetTime(); - 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.GetVinMasternode().prevout.ToStringShort() - << ", governance object hash = " << GetHash().ToString() - << ", time delta = " << nTimeDelta << "\n"; - LogPrint("gobject", ostr.str().c_str()); - exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_TEMPORARY_ERROR); - 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.GetVinMasternode().prevout.ToStringShort() - << ", governance object hash = " << GetHash().ToString() - << ", vote hash = " << vote.GetHash().ToString() << "\n"; - LogPrintf(ostr.str().c_str()); - exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); - governance.AddInvalidVote(vote); - return false; - } - voteInstance = vote_instance_t(vote.GetOutcome(), nNow); - fileVotes.AddVote(vote); - fDirtyCache = true; - return true; -} - -void CGovernanceObject::RebuildVoteMap() -{ - vote_m_t mapMNVotesNew; - for(vote_m_it it = mapCurrentMNVotes.begin(); it != mapCurrentMNVotes.end(); ++it) { - CTxIn vinMasternode; - if(mnodeman.GetMasternodeVinForIndexOld(it->first, vinMasternode)) { - int nNewIndex = mnodeman.GetMasternodeIndex(vinMasternode); - if((nNewIndex >= 0)) { - mapMNVotesNew[nNewIndex] = it->second; - } - } - } - mapCurrentMNVotes = mapMNVotesNew; -} - -void CGovernanceObject::ClearMasternodeVotes() -{ - vote_m_it it = mapCurrentMNVotes.begin(); - while(it != mapCurrentMNVotes.end()) { - bool fIndexRebuilt = false; - CTxIn vinMasternode; - bool fRemove = true; - if(mnodeman.Get(it->first, vinMasternode, fIndexRebuilt)) { - if(mnodeman.Has(vinMasternode)) { - fRemove = false; - } - else { - fileVotes.RemoveVotesFromMasternode(vinMasternode); - } - } - - if(fRemove) { - mapCurrentMNVotes.erase(it++); - } - else { - ++it; - } - } -} - -void CGovernanceObject::SetMasternodeInfo(const CTxIn& vin) -{ - vinMasternode = vin; -} - -bool CGovernanceObject::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode) -{ - LOCK(cs); - - std::string strError; - uint256 nHash = GetHash(); - std::string strMessage = nHash.ToString(); - - if(!darkSendSigner.SignMessage(strMessage, vchSig, keyMasternode)) { - LogPrintf("CGovernanceObject::Sign -- SignMessage() failed\n"); - return false; - } - - if(!darkSendSigner.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) -{ - LOCK(cs); - std::string strError; - uint256 nHash = GetHash(); - std::string strMessage = nHash.ToString(); - - if(!darkSendSigner.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() -{ - // CREATE HASH OF ALL IMPORTANT PIECES OF DATA - - CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - ss << nHashParent; - ss << nRevision; - ss << nTime; - ss << strData; - // 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( ostr.str().c_str() ); - return; - } - catch(...) { - fUnparsable = true; - std::ostringstream ostr; - ostr << "CGovernanceObject::LoadData Unknown Error parsing JSON"; - DBG( cout << ostr.str() << endl; ); - LogPrintf( ostr.str().c_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(const CBlockIndex *pCurrentBlockIndex) -{ - // THIS DOES NOT CHECK COLLATERAL, THIS IS CHECKED UPON ORIGINAL ARRIVAL - fCachedLocalValidity = IsValidLocally(pCurrentBlockIndex, strLocalValidityError, false); -}; - - -bool CGovernanceObject::IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool fCheckCollateral) -{ - bool fMissingMasternode = false; - - return IsValidLocally(pindex, strError, fMissingMasternode, fCheckCollateral); -} - -bool CGovernanceObject::IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool& fMissingMasternode, bool fCheckCollateral) -{ - fMissingMasternode = false; - if(!pindex) { - strError = "Tip is NULL"; - return true; - } - - 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 - - if(GetAbsoluteNoCount(VOTE_SIGNAL_VALID) > mnodeman.CountEnabled(MIN_GOVERNANCE_PEER_PROTO_VERSION)/10) { - strError = "Automated removal"; - return false; - } - - // 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 = mnodeman.GetMasternodeInfo(vinMasternode); - if(!infoMn.fInfoValid) { - fMissingMasternode = true; - strError = "Masternode not found: " + 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; - } - - // Only perform rate check if we are synced because during syncing it is expected - // that objects will be seen in rapid succession - if(masternodeSync.IsSynced()) { - if(!governance.MasternodeRateCheck(vinMasternode, nObjectType)) { - strError = "Masternode attempting to create too many objects: " + strOutpoint; - return false; - } - } - - return true; - } - - if(!IsCollateralValid(strError)) { - // strError set in IsCollateralValid - if(strError == "") strError = "Collateral is invalid"; - 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) -{ - strError = ""; - 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.IsNormalPaymentScript() && !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 - - LOCK(cs_main); - int nConfirmationsIn = GetIXConfirmations(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 = "valid"; - } else { - strError = strprintf("Collateral requires at least %d confirmations - %d confirmations", GOVERNANCE_FEE_CONFIRMATIONS, nConfirmationsIn); - LogPrintf ("CGovernanceObject::IsCollateralValid -- %s - %d confirmations\n", strError, nConfirmationsIn); - return false; - } - - 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 CTxIn& mnCollateralOutpoint, vote_rec_t& voteRecord) -{ - int nMNIndex = governance.GetMasternodeIndex(mnCollateralOutpoint); - vote_m_it it = mapCurrentMNVotes.find(nMNIndex); - if (it == mapCurrentMNVotes.end()) { - return false; - } - voteRecord = it->second; - return true; -} - -void CGovernanceObject::Relay() -{ - CInv inv(MSG_GOVERNANCE_OBJECT, GetHash()); - RelayInv(inv, PROTOCOL_VERSION); } std::string CGovernanceManager::ToString() const @@ -1493,84 +877,3 @@ void CGovernanceManager::UpdatedBlockTip(const CBlockIndex *pindex) if(!fLiteMode && masternodeSync.IsSynced()) NewBlock(); } - -void CGovernanceObject::UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex) -{ - // 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); - // todo - 12.1 - Temporarily set to 1 for testing - reverted - //nAbsVoteReq = 1; - - // SET SENTINEL FLAGS TO FALSE - - fCachedFunding = false; - fCachedValid = true; //default to valid - fCachedDelete = false; - 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_VALID) >= nAbsVoteReq) fCachedValid = true; - if(GetAbsoluteYesCount(VOTE_SIGNAL_DELETE) >= nAbsVoteReq) { - fCachedDelete = true; - nDeletionTime = GetAdjustedTime(); - } - if(GetAbsoluteYesCount(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = true; - - // ARE ANY OF THE VOTING FLAGS NEGATIVELY SET BY THE NETWORK? - // THIS CAN BE CACHED, THE VOTES BEING HOT-LOADED AS NEEDED TO RECALCULATE - - if(GetAbsoluteNoCount(VOTE_SIGNAL_FUNDING) >= nAbsVoteReq) fCachedFunding = false; - if(GetAbsoluteNoCount(VOTE_SIGNAL_VALID) >= nAbsVoteReq) fCachedValid = false; - if(GetAbsoluteNoCount(VOTE_SIGNAL_DELETE) >= nAbsVoteReq) fCachedDelete = false; - if(GetAbsoluteNoCount(VOTE_SIGNAL_ENDORSED) >= nAbsVoteReq) fCachedEndorsed = 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() -{ - const vote_mcache_t::list_t& listVotes = mapOrphanVotes.GetItemList(); - for(vote_mcache_t::list_cit it = listVotes.begin(); it != listVotes.end(); ++it) { - const CGovernanceVote& vote = it->value; - if(!mnodeman.Has(vote.GetVinMasternode())) { - continue; - } - CGovernanceException exception; - if(!ProcessVote(NULL, vote, exception)) { - LogPrintf("CGovernanceObject::CheckOrphanVotes -- Failed to add orphan vote: %s\n", exception.what()); - } - } -} diff --git a/src/governance.h b/src/governance.h index 2f6bc1758..33429e519 100644 --- a/src/governance.h +++ b/src/governance.h @@ -14,6 +14,7 @@ #include "key.h" #include "util.h" #include "base58.h" +#include "governance-object.h" #include "masternode.h" #include "governance-exceptions.h" #include "governance-vote.h" @@ -34,31 +35,11 @@ class CGovernanceTriggerManager; class CGovernanceObject; class CGovernanceVote; -static const int MAX_GOVERNANCE_OBJECT_DATA_SIZE = 16 * 1024; -static const int MIN_GOVERNANCE_PEER_PROTO_VERSION = 70203; - -static const int GOVERNANCE_OBJECT_UNKNOWN = 0; -static const int GOVERNANCE_OBJECT_PROPOSAL = 1; -static const int GOVERNANCE_OBJECT_TRIGGER = 2; -static const int GOVERNANCE_OBJECT_WATCHDOG = 3; - -static const CAmount GOVERNANCE_PROPOSAL_FEE_TX = (0.33*COIN); - -static const int64_t GOVERNANCE_FEE_CONFIRMATIONS = 6; -static const int64_t GOVERNANCE_UPDATE_MIN = 60*60; -static const int64_t GOVERNANCE_DELETION_DELAY = 10*60; - - -// FOR SEEN MAP ARRAYS - GOVERNANCE OBJECTS AND VOTES -static const int SEEN_OBJECT_IS_VALID = 0; -static const int SEEN_OBJECT_ERROR_INVALID = 1; -static const int SEEN_OBJECT_ERROR_IMMATURE = 2; -static const int SEEN_OBJECT_EXECUTED = 3; //used for triggers -static const int SEEN_OBJECT_UNKNOWN = 4; // the default - extern std::map mapAskedForGovernanceObject; extern CGovernanceManager governance; +typedef std::pair object_time_pair_t; + // // Governance Manager : Contains all proposals for the budget // @@ -67,7 +48,6 @@ class CGovernanceManager friend class CGovernanceObject; public: // Types - typedef std::map object_m_t; typedef object_m_t::iterator object_m_it; @@ -90,7 +70,7 @@ public: // Types typedef CacheMap vote_cache_t; - typedef CacheMultiMap vote_mcache_t; + typedef CacheMultiMap vote_mcache_t; typedef object_m_t::size_type size_type; @@ -106,6 +86,12 @@ public: // Types typedef hash_s_t::const_iterator hash_s_cit; + typedef std::map object_time_m_t; + + typedef object_time_m_t::iterator object_time_m_it; + + typedef object_time_m_t::const_iterator object_time_m_cit; + private: static const int MAX_CACHE_SIZE = 1000000; @@ -122,7 +108,7 @@ private: count_m_t mapSeenGovernanceObjects; - object_m_t mapMasternodeOrphanObjects; + object_time_m_t mapMasternodeOrphanObjects; object_ref_cache_t mapVoteToObject; @@ -274,7 +260,7 @@ private: void AddOrphanVote(const CGovernanceVote& vote) { - mapOrphanVotes.Insert(vote.GetHash(), vote); + mapOrphanVotes.Insert(vote.GetHash(), vote_time_pair_t(vote, GetAdjustedTime() + GOVERNANCE_ORPHAN_EXPIRATION_TIME)); } bool ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception); @@ -300,287 +286,4 @@ private: }; -struct vote_instance_t { - - vote_outcome_enum_t eOutcome; - int64_t nTime; - - vote_instance_t(vote_outcome_enum_t eOutcomeIn = VOTE_OUTCOME_NONE, int64_t nTimeIn = 0) - : eOutcome(eOutcomeIn), - nTime(nTimeIn) - {} - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) - { - int nOutcome = int(eOutcome); - READWRITE(nOutcome); - READWRITE(nTime); - if(ser_action.ForRead()) { - eOutcome = vote_outcome_enum_t(nOutcome); - } - } -}; - -typedef std::map vote_instance_m_t; - -typedef vote_instance_m_t::iterator vote_instance_m_it; - -typedef vote_instance_m_t::const_iterator vote_instance_m_cit; - -struct vote_rec_t { - vote_instance_m_t mapInstances; - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) - { - READWRITE(mapInstances); - } -}; - -/** -* Governance Object -* -*/ - -class CGovernanceObject -{ - friend class CGovernanceManager; - - friend class CGovernanceTriggerManager; - -public: // Types - typedef std::map vote_m_t; - - typedef vote_m_t::iterator vote_m_it; - - typedef vote_m_t::const_iterator vote_m_cit; - - typedef CacheMultiMap vote_mcache_t; - -private: - /// critical section to protect the inner data structures - mutable CCriticalSection cs; - - /// Object typecode - int nObjectType; - - /// parent object, 0 is root - uint256 nHashParent; - - /// object revision in the system - int nRevision; - - /// time this object was created - int64_t nTime; - - /// time this object was marked for deletion - int64_t nDeletionTime; - - /// fee-tx - uint256 nCollateralHash; - - /// Data field - can be used for anything - std::string strData; - - /// Masternode info for signed objects - CTxIn vinMasternode; - std::vector vchSig; - - /// is valid by blockchain - bool fCachedLocalValidity; - std::string strLocalValidityError; - - // VARIOUS FLAGS FOR OBJECT / SET VIA MASTERNODE VOTING - - /// true == minimum network support has been reached for this object to be funded (doesn't mean it will for sure though) - bool fCachedFunding; - - /// true == minimum network has been reached flagging this object as a valid and understood goverance object (e.g, the serialized data is correct format, etc) - bool fCachedValid; - - /// true == minimum network support has been reached saying this object should be deleted from the system entirely - bool fCachedDelete; - - /** true == minimum network support has been reached flagging this object as endorsed by an elected representative body - * (e.g. business review board / technecial review board /etc) - */ - bool fCachedEndorsed; - - /// object was updated and cached values should be updated soon - bool fDirtyCache; - - /// Object is no longer of interest - bool fExpired; - - /// Failed to parse object data - bool fUnparsable; - - vote_m_t mapCurrentMNVotes; - - /// Limited map of votes orphaned by MN - vote_mcache_t mapOrphanVotes; - - CGovernanceObjectVoteFile fileVotes; - -public: - CGovernanceObject(); - - CGovernanceObject(uint256 nHashParentIn, int nRevisionIn, int64_t nTime, uint256 nCollateralHashIn, std::string strDataIn); - - CGovernanceObject(const CGovernanceObject& other); - - void swap(CGovernanceObject& first, CGovernanceObject& second); // nothrow - - // Public Getter methods - - int64_t GetCreationTime() const { - return nTime; - } - - int64_t GetDeletionTime() const { - return nDeletionTime; - } - - int GetObjectType() const { - return nObjectType; - } - - const uint256& GetCollateralHash() const { - return nCollateralHash; - } - - const CTxIn& GetMasternodeVin() const { - return vinMasternode; - } - - bool IsSetCachedFunding() const { - return fCachedFunding; - } - - bool IsSetCachedValid() const { - return fCachedValid; - } - - bool IsSetCachedDelete() const { - return fCachedDelete; - } - - bool IsSetCachedEndorsed() const { - return fCachedEndorsed; - } - - bool IsSetDirtyCache() const { - return fDirtyCache; - } - - bool IsSetExpired() const { - return fExpired; - } - - void InvalidateVoteCache() { - fDirtyCache = true; - } - - CGovernanceObjectVoteFile& GetVoteFile() { - return fileVotes; - } - - // Signature related functions - - void SetMasternodeInfo(const CTxIn& vin); - bool Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode); - bool CheckSignature(CPubKey& pubKeyMasternode); - - // CORE OBJECT FUNCTIONS - - bool IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool fCheckCollateral); - - bool IsValidLocally(const CBlockIndex* pindex, std::string& strError, bool& fMissingMasternode, bool fCheckCollateral); - - /// Check the collateral transaction for the budget proposal/finalized budget - bool IsCollateralValid(std::string& strError); - - void UpdateLocalValidity(const CBlockIndex *pCurrentBlockIndex); - - void UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex); - - int GetObjectSubtype(); - - CAmount GetMinCollateralFee(); - - UniValue GetJSONObject(); - - void Relay(); - - uint256 GetHash(); - - // GET VOTE COUNT FOR SIGNAL - - int CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) const; - - int GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn) const; - int GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn) const; - int GetYesCount(vote_signal_enum_t eVoteSignalIn) const; - int GetNoCount(vote_signal_enum_t eVoteSignalIn) const; - int GetAbstainCount(vote_signal_enum_t eVoteSignalIn) const; - - bool GetCurrentMNVotes(const CTxIn& mnCollateralOutpoint, vote_rec_t& voteRecord); - - // FUNCTIONS FOR DEALING WITH DATA STRING - - std::string GetDataAsHex(); - std::string GetDataAsString(); - - // SERIALIZER - - ADD_SERIALIZE_METHODS; - - template - inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) - { - // SERIALIZE DATA FOR SAVING/LOADING OR NETWORK FUNCTIONS - - READWRITE(nHashParent); - READWRITE(nRevision); - READWRITE(nTime); - READWRITE(nCollateralHash); - READWRITE(LIMITED_STRING(strData, MAX_GOVERNANCE_OBJECT_DATA_SIZE)); - READWRITE(nObjectType); - READWRITE(vinMasternode); - READWRITE(vchSig); - if(nType & SER_DISK) { - // Only include these for the disk file format - LogPrint("gobject", "CGovernanceObject::SerializationOp Reading/writing votes from/to disk\n"); - READWRITE(mapCurrentMNVotes); - READWRITE(fileVotes); - LogPrint("gobject", "CGovernanceObject::SerializationOp hash = %s, vote count = %d\n", GetHash().ToString(), fileVotes.GetVoteCount()); - } - - // AFTER DESERIALIZATION OCCURS, CACHED VARIABLES MUST BE CALCULATED MANUALLY - } - -private: - // FUNCTIONS FOR DEALING WITH DATA STRING - void LoadData(); - void GetData(UniValue& objResult); - - bool ProcessVote(CNode* pfrom, - const CGovernanceVote& vote, - CGovernanceException& exception); - - void RebuildVoteMap(); - - /// Called when MN's which have voted on this object have been removed - void ClearMasternodeVotes(); - - void CheckOrphanVotes(); - -}; - - #endif