From c31ba8ba4c07e72620bd71753f2103ca103bb1c2 Mon Sep 17 00:00:00 2001 From: Tim Flynn Date: Sun, 13 Nov 2016 12:52:34 -0500 Subject: [PATCH] New vote tallying implementation (#1135) --- src/Makefile.am | 5 + src/Makefile.test.include | 2 + src/activemasternode.cpp | 1 + src/cachemap.h | 202 ++++++++ src/cachemultimap.h | 254 ++++++++++ src/governance-classes.cpp | 6 +- src/governance-classes.h | 4 +- src/governance-exceptions.h | 98 ++++ src/governance-misc.h | 6 +- src/governance-vote.cpp | 49 +- src/governance-vote.h | 21 +- src/governance-votedb.cpp | 84 ++++ src/governance-votedb.h | 93 ++++ src/governance.cpp | 832 +++++++++++++++++++------------ src/governance.h | 346 ++++++++++--- src/main.cpp | 12 +- src/masternode.cpp | 8 + src/masternodeman.cpp | 348 +++++++++---- src/masternodeman.h | 150 +++++- src/qt/masternodelist.cpp | 2 + src/rpcgovernance.cpp | 71 ++- src/rpcmasternode.cpp | 3 + src/serialize.h | 34 ++ src/test/cachemap_tests.cpp | 127 +++++ src/test/cachemultimap_tests.cpp | 176 +++++++ 25 files changed, 2401 insertions(+), 533 deletions(-) create mode 100644 src/cachemap.h create mode 100644 src/cachemultimap.h create mode 100644 src/governance-exceptions.h create mode 100644 src/governance-votedb.cpp create mode 100644 src/governance-votedb.h create mode 100644 src/test/cachemap_tests.cpp create mode 100644 src/test/cachemultimap_tests.cpp diff --git a/src/Makefile.am b/src/Makefile.am index cc31fc21b..9fe825297 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -74,6 +74,8 @@ BITCOIN_CORE_H = \ arith_uint256.h \ base58.h \ bloom.h \ + cachemap.h \ + cachemultimap.h \ chain.h \ chainparams.h \ chainparamsbase.h \ @@ -99,7 +101,9 @@ BITCOIN_CORE_H = \ darksend-relay.h \ governance.h \ governance-classes.h \ + governance-exceptions.h \ governance-vote.h \ + governance-votedb.h \ flat-database.h \ hash.h \ httprpc.h \ @@ -202,6 +206,7 @@ libbitcoin_server_a_SOURCES = \ governance.cpp \ governance-classes.cpp \ governance-vote.cpp \ + governance-votedb.cpp \ main.cpp \ merkleblock.cpp \ miner.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 2eeada3ca..de97a2123 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -43,6 +43,8 @@ BITCOIN_TESTS =\ test/base64_tests.cpp \ test/bip32_tests.cpp \ test/bloom_tests.cpp \ + test/cachemap_tests.cpp \ + test/cachemultimap_tests.cpp \ test/checkblock_tests.cpp \ test/Checkpoints_tests.cpp \ test/coins_tests.cpp \ diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 1dd959536..227631512 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -271,6 +271,7 @@ void CActiveMasternode::ManageStateLocal() //update to masternode list LogPrintf("CActiveMasternode::ManageStateLocal -- Update Masternode List\n"); mnodeman.UpdateMasternodeList(mnb); + mnodeman.NotifyMasternodeUpdates(); //send to all peers LogPrintf("CActiveMasternode::ManageStateLocal -- Relay broadcast, vin=%s\n", vin.ToString()); diff --git a/src/cachemap.h b/src/cachemap.h new file mode 100644 index 000000000..3539be4f5 --- /dev/null +++ b/src/cachemap.h @@ -0,0 +1,202 @@ +// 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 CACHEMAP_H_ +#define CACHEMAP_H_ + +#include +#include +#include + +#include "serialize.h" + +/** + * Serializable structure for key/value items + */ +template +struct CacheItem +{ + CacheItem() + {} + + CacheItem(const K& keyIn, const V& valueIn) + : key(keyIn), + value(valueIn) + {} + + K key; + V value; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(key); + READWRITE(value); + } +}; + + +/** + * Map like container that keeps the N most recently added items + */ +template +class CacheMap +{ +public: + typedef Size size_type; + + typedef CacheItem item_t; + + typedef std::list list_t; + + typedef typename list_t::iterator list_it; + + typedef typename list_t::const_iterator list_cit; + + typedef std::map map_t; + + typedef typename map_t::iterator map_it; + + typedef typename map_t::const_iterator map_cit; + +private: + size_type nMaxSize; + + size_type nCurrentSize; + + list_t listItems; + + map_t mapIndex; + +public: + CacheMap(size_type nMaxSizeIn = 0) + : nMaxSize(nMaxSizeIn), + nCurrentSize(0), + listItems(), + mapIndex() + {} + + CacheMap(const CacheMap& other) + : nMaxSize(other.nMaxSize), + nCurrentSize(other.nCurrentSize), + listItems(other.listItems), + mapIndex() + { + RebuildIndex(); + } + + void Clear() + { + mapIndex.clear(); + listItems.clear(); + nCurrentSize = 0; + } + + void SetMaxSize(size_type nMaxSizeIn) + { + nMaxSize = nMaxSizeIn; + } + + size_type GetMaxSize() const { + return nMaxSize; + } + + size_type GetSize() const { + return nCurrentSize; + } + + void Insert(const K& key, const V& value) + { + map_it it = mapIndex.find(key); + if(it != mapIndex.end()) { + item_t& item = *(it->second); + item.value = value; + return; + } + if(nCurrentSize == nMaxSize) { + PruneLast(); + } + listItems.push_front(item_t(key, value)); + mapIndex[key] = listItems.begin(); + ++nCurrentSize; + } + + bool HasKey(const K& key) const + { + map_cit it = mapIndex.find(key); + return (it != mapIndex.end()); + } + + bool Get(const K& key, V& value) const + { + map_cit it = mapIndex.find(key); + if(it == mapIndex.end()) { + return false; + } + item_t& item = *(it->second); + value = item.value; + return true; + } + + void Erase(const K& key) + { + map_it it = mapIndex.find(key); + if(it == mapIndex.end()) { + return; + } + listItems.erase(it->second); + mapIndex.erase(it); + --nCurrentSize; + } + + const list_t& GetItemList() const { + return listItems; + } + + CacheMap& operator=(const CacheMap& other) + { + nMaxSize = other.nMaxSize; + nCurrentSize = other.nCurrentSize; + listItems = other.listItems; + RebuildIndex(); + return *this; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(nMaxSize); + READWRITE(nCurrentSize); + READWRITE(listItems); + if(ser_action.ForRead()) { + RebuildIndex(); + } + } + +private: + void PruneLast() + { + if(nCurrentSize < 1) { + return; + } + item_t& item = listItems.back(); + mapIndex.erase(item.key); + listItems.pop_back(); + --nCurrentSize; + } + + void RebuildIndex() + { + mapIndex.clear(); + for(list_it it = listItems.begin(); it != listItems.end(); ++it) { + mapIndex[it->key] = it; + } + } +}; + +#endif /* CACHEMAP_H_ */ diff --git a/src/cachemultimap.h b/src/cachemultimap.h new file mode 100644 index 000000000..4cdf12285 --- /dev/null +++ b/src/cachemultimap.h @@ -0,0 +1,254 @@ +// 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 CACHEMULTIMAP_H_ +#define CACHEMULTIMAP_H_ + +#include +#include +#include +#include + +#include "serialize.h" + +#include "cachemap.h" + +/** + * Map like container that keeps the N most recently added items + */ +template +class CacheMultiMap +{ +public: + typedef Size size_type; + + typedef CacheItem item_t; + + typedef std::list list_t; + + typedef typename list_t::iterator list_it; + + typedef typename list_t::const_iterator list_cit; + + typedef std::map it_map_t; + + typedef typename it_map_t::iterator it_map_it; + + typedef typename it_map_t::const_iterator it_map_cit; + + typedef std::map map_t; + + typedef typename map_t::iterator map_it; + + typedef typename map_t::const_iterator map_cit; + +private: + size_type nMaxSize; + + size_type nCurrentSize; + + list_t listItems; + + map_t mapIndex; + +public: + CacheMultiMap(size_type nMaxSizeIn = 0) + : nMaxSize(nMaxSizeIn), + nCurrentSize(0), + listItems(), + mapIndex() + {} + + CacheMultiMap(const CacheMap& other) + : nMaxSize(other.nMaxSize), + nCurrentSize(other.nCurrentSize), + listItems(other.listItems), + mapIndex() + { + RebuildIndex(); + } + + void Clear() + { + mapIndex.clear(); + listItems.clear(); + nCurrentSize = 0; + } + + void SetMaxSize(size_type nMaxSizeIn) + { + nMaxSize = nMaxSizeIn; + } + + size_type GetMaxSize() const { + return nMaxSize; + } + + size_type GetSize() const { + return nCurrentSize; + } + + void Insert(const K& key, const V& value) + { + if(nCurrentSize == nMaxSize) { + PruneLast(); + } + map_it mit = mapIndex.find(key); + if(mit == mapIndex.end()) { + mit = mapIndex.insert(std::pair(key, it_map_t())).first; + } + it_map_t& mapIt = mit->second; + + if(mapIt.count(value) > 0) { + // Don't insert duplicates + return; + } + + listItems.push_front(item_t(key, value)); + list_it lit = listItems.begin(); + + mapIt[value] = lit; + ++nCurrentSize; + } + + bool HasKey(const K& key) const + { + map_cit it = mapIndex.find(key); + return (it != mapIndex.end()); + } + + bool Get(const K& key, V& value) const + { + map_cit it = mapIndex.find(key); + if(it == mapIndex.end()) { + return false; + } + const it_map_t& mapIt = it->second; + const item_t& item = *(mapIt.begin()->second); + value = item.value; + return true; + } + + bool GetAll(const K& key, std::vector& vecValues) + { + map_cit mit = mapIndex.find(key); + if(mit == mapIndex.end()) { + return false; + } + const it_map_t& mapIt = mit->second; + + for(it_map_cit it = mapIt.begin(); it != mapIt.end(); ++it) { + const item_t& item = *(it->second); + vecValues.push_back(item.value); + } + return true; + } + + void Erase(const K& key) + { + map_it mit = mapIndex.find(key); + if(mit == mapIndex.end()) { + return; + } + it_map_t& mapIt = mit->second; + + for(it_map_it it = mapIt.begin(); it != mapIt.end(); ++it) { + listItems.erase(it->second); + --nCurrentSize; + } + + mapIndex.erase(mit); + } + + void Erase(const K& key, const V& value) + { + map_it mit = mapIndex.find(key); + if(mit == mapIndex.end()) { + return; + } + it_map_t& mapIt = mit->second; + + it_map_it it = mapIt.find(value); + if(it == mapIt.end()) { + return; + } + + listItems.erase(it->second); + --nCurrentSize; + mapIt.erase(it); + + if(mapIt.size() < 1) { + mapIndex.erase(mit); + } + } + + const list_t& GetItemList() const { + return listItems; + } + + CacheMap& operator=(const CacheMap& other) + { + nMaxSize = other.nMaxSize; + nCurrentSize = other.nCurrentSize; + listItems = other.listItems; + RebuildIndex(); + return *this; + } + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(nMaxSize); + READWRITE(nCurrentSize); + READWRITE(listItems); + if(ser_action.ForRead()) { + RebuildIndex(); + } + } + +private: + void PruneLast() + { + if(nCurrentSize < 1) { + return; + } + + list_it lit = listItems.end(); + --lit; + item_t& item = *lit; + + map_it mit = mapIndex.find(item.key); + + if(mit != mapIndex.end()) { + it_map_t& mapIt = mit->second; + + mapIt.erase(item.value); + + if(mapIt.size() < 1) { + mapIndex.erase(item.key); + } + } + + listItems.pop_back(); + --nCurrentSize; + } + + void RebuildIndex() + { + mapIndex.clear(); + for(list_it lit = listItems.begin(); lit != listItems.end(); ++lit) { + item_t& item = *lit; + map_it mit = mapIndex.find(item.key); + if(mit == mapIndex.end()) { + mit = mapIndex.insert(std::pair(item.key, it_map_t())).first; + } + it_map_t& mapIt = mit->second; + mapIt[item.value] = lit; + } + } +}; + +#endif /* CACHEMULTIMAP_H_ */ diff --git a/src/governance-classes.cpp b/src/governance-classes.cpp index f1a42a803..b5dc37ff4 100644 --- a/src/governance-classes.cpp +++ b/src/governance-classes.cpp @@ -346,7 +346,7 @@ bool CSuperblockManager::IsSuperblockTriggered(int nBlockHeight) // MAKE SURE THIS TRIGGER IS ACTIVE VIA FUNDING CACHE FLAG - if(pObj->fCachedFunding) { + if(pObj->IsSetCachedFunding()) { LogPrint("gobject", "CSuperblockManager::IsSuperblockTriggered -- fCacheFunding = true, returning true\n"); DBG( cout << "IsSuperblockTriggered returning true" << endl; ); return true; @@ -506,7 +506,7 @@ CSuperblock(uint256& nHash) DBG( cout << "CSuperblock Constructor pGovObj : " << pGovObj->GetDataAsString() - << ", nObjectType = " << pGovObj->nObjectType + << ", nObjectType = " << pGovObj->GetObjectType() << endl; ); if (pGovObj->GetObjectType() != GOVERNANCE_OBJECT_TRIGGER) { @@ -678,7 +678,7 @@ bool CSuperblock::IsValid(const CTransaction& txNew, int nBlockHeight, CAmount b int nMinerPayments = nOutputs - nPayments; LogPrint("gobject", "CSuperblock::IsValid nOutputs = %d, nPayments = %d, strData = %s\n", - nOutputs, nPayments, GetGovernanceObject()->strData); + nOutputs, nPayments, GetGovernanceObject()->GetDataAsHex()); // We require an exact match (including order) between the expected // superblock payments and the payments actually in the block, after diff --git a/src/governance-classes.h b/src/governance-classes.h index 04682d955..6e97f414f 100644 --- a/src/governance-classes.h +++ b/src/governance-classes.h @@ -1,8 +1,8 @@ // 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 GOVERANCE_CLASSES_H -#define GOVERANCE_CLASSES_H +#ifndef GOVERNANCE_CLASSES_H +#define GOVERNANCE_CLASSES_H //#define ENABLE_DASH_DEBUG diff --git a/src/governance-exceptions.h b/src/governance-exceptions.h new file mode 100644 index 000000000..a968b93cc --- /dev/null +++ b/src/governance-exceptions.h @@ -0,0 +1,98 @@ +// 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_EXCEPTIONS_H +#define GOVERNANCE_EXCEPTIONS_H + +#include +#include +#include + +enum governance_exception_type_enum_t { + /// Default value, normally indicates no exception condition occurred + GOVERNANCE_EXCEPTION_NONE = 0, + /// Unusual condition requiring no caller action + GOVERNANCE_EXCEPTION_WARNING = 1, + /// Requested operation cannot be performed + GOVERNANCE_EXCEPTION_PERMANENT_ERROR = 2, + /// Requested operation not currently possible, may resubmit later + GOVERNANCE_EXCEPTION_TEMPORARY_ERROR = 3, + /// Unexpected error (ie. should not happen unless there is a bug in the code) + GOVERNANCE_EXCEPTION_INTERNAL_ERROR = 4 +}; + +inline std::ostream& operator<<(std::ostream& os, governance_exception_type_enum_t eType) +{ + switch(eType) { + case GOVERNANCE_EXCEPTION_NONE: + os << "GOVERNANCE_EXCEPTION_NONE"; + break; + case GOVERNANCE_EXCEPTION_WARNING: + os << "GOVERNANCE_EXCEPTION_WARNING"; + break; + case GOVERNANCE_EXCEPTION_PERMANENT_ERROR: + os << "GOVERNANCE_EXCEPTION_PERMANENT_ERROR"; + break; + case GOVERNANCE_EXCEPTION_TEMPORARY_ERROR: + os << "GOVERNANCE_EXCEPTION_TEMPORARY_ERROR"; + break; + case GOVERNANCE_EXCEPTION_INTERNAL_ERROR: + os << "GOVERNANCE_EXCEPTION_INTERNAL_ERROR"; + break; + } + return os; +} + +/** + * A class which encapsulates information about a governance exception condition + * + * Derives from std::exception so is suitable for throwing + * (ie. will be caught by a std::exception handler) but may also be used as a + * normal object. + */ +class CGovernanceException : public std::exception +{ +private: + std::string strMessage; + + governance_exception_type_enum_t eType; + + int nNodePenalty; + +public: + CGovernanceException(const std::string& strMessageIn = "", + governance_exception_type_enum_t eTypeIn = GOVERNANCE_EXCEPTION_NONE, + int nNodePenaltyIn = 0) + : strMessage(), + eType(eTypeIn), + nNodePenalty(nNodePenaltyIn) + { + std::ostringstream ostr; + ostr << eType << ":" << strMessageIn; + strMessage = ostr.str(); + } + + virtual ~CGovernanceException() throw() {} + + virtual const char* what() const throw() + { + return strMessage.c_str(); + } + + const std::string& GetMessage() const + { + return strMessage; + } + + governance_exception_type_enum_t GetType() const + { + return eType; + } + + int GetNodePenalty() const { + return nNodePenalty; + } +}; + +#endif diff --git a/src/governance-misc.h b/src/governance-misc.h index 8db6c28ae..67f733362 100644 --- a/src/governance-misc.h +++ b/src/governance-misc.h @@ -2,8 +2,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef GOVERANCE_MISC_H -#define GOVERANCE_MISC_H +#ifndef GOVERNANCE_MISC_H +#define GOVERNANCE_MISC_H #include "main.h" #include "governance.h" @@ -51,4 +51,4 @@ class CGovernanceVote; // } // }; -#endif \ No newline at end of file +#endif diff --git a/src/governance-vote.cpp b/src/governance-vote.cpp index a196a13cc..80286d512 100644 --- a/src/governance-vote.cpp +++ b/src/governance-vote.cpp @@ -233,7 +233,7 @@ CGovernanceVote::CGovernanceVote(CTxIn vinMasternodeIn, uint256 nParentHashIn, v vchSig() {} -void CGovernanceVote::Relay() +void CGovernanceVote::Relay() const { CInv inv(MSG_GOVERNANCE_OBJECT_VOTE, GetHash()); RelayInv(inv, PROTOCOL_VERSION); @@ -262,15 +262,15 @@ bool CGovernanceVote::Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode) return true; } -bool CGovernanceVote::IsValid(bool fSignatureCheck) +bool CGovernanceVote::IsValid(bool fSignatureCheck) const { - if(nTime > GetTime() + (60*60)){ + if(nTime > GetTime() + (60*60)) { LogPrint("gobject", "CGovernanceVote::IsValid -- vote is too far ahead of current time - %s - nTime %lli - Max Time %lli\n", GetHash().ToString(), nTime, GetTime() + (60*60)); return false; } // support up to 50 actions (implemented in sentinel) - if(nVoteSignal > 50) + if(nVoteSignal > MAX_SUPPORTED_VOTE_SIGNAL) { LogPrint("gobject", "CGovernanceVote::IsValid -- Client attempted to vote on invalid signal(%d) - %s\n", nVoteSignal, GetHash().ToString()); return false; @@ -302,3 +302,44 @@ bool CGovernanceVote::IsValid(bool fSignatureCheck) return true; } + +bool operator==(const CGovernanceVote& vote1, const CGovernanceVote& vote2) +{ + bool fResult = ((vote1.vinMasternode == vote2.vinMasternode) && + (vote1.nParentHash == vote2.nParentHash) && + (vote1.nVoteOutcome == vote2.nVoteOutcome) && + (vote1.nVoteSignal == vote2.nVoteSignal) && + (vote1.nTime == vote2.nTime)); + return fResult; +} + +bool operator<(const CGovernanceVote& vote1, const CGovernanceVote& vote2) +{ + bool fResult = (vote1.vinMasternode < vote2.vinMasternode); + if(!fResult) { + return false; + } + fResult = (vote1.vinMasternode == vote2.vinMasternode); + + fResult = fResult && (vote1.nParentHash < vote2.nParentHash); + if(!fResult) { + return false; + } + fResult = fResult && (vote1.nParentHash == vote2.nParentHash); + + fResult = fResult && (vote1.nVoteOutcome < vote2.nVoteOutcome); + if(!fResult) { + return false; + } + fResult = fResult && (vote1.nVoteOutcome == vote2.nVoteOutcome); + + fResult = fResult && (vote1.nVoteSignal == vote2.nVoteSignal); + if(!fResult) { + return false; + } + fResult = fResult && (vote1.nVoteSignal == vote2.nVoteSignal); + + fResult = fResult && (vote1.nTime < vote2.nTime); + + return fResult; +} diff --git a/src/governance-vote.h b/src/governance-vote.h index 7afdec29a..a77f40f68 100644 --- a/src/governance-vote.h +++ b/src/governance-vote.h @@ -2,8 +2,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef GOVERANCE_VOTE_H -#define GOVERANCE_VOTE_H +#ifndef GOVERNANCE_VOTE_H +#define GOVERNANCE_VOTE_H #include "main.h" #include "sync.h" @@ -68,6 +68,8 @@ enum vote_signal_enum_t { VOTE_SIGNAL_CUSTOM20 = 35 }; +static const int MAX_SUPPORTED_VOTE_SIGNAL = VOTE_SIGNAL_ENDORSED; + /** * Governance Voting * @@ -89,6 +91,10 @@ public: class CGovernanceVote { + friend bool operator==(const CGovernanceVote& vote1, const CGovernanceVote& vote2); + + friend bool operator<(const CGovernanceVote& vote1, const CGovernanceVote& vote2); + private: bool fValid; //if the vote is currently valid / counted bool fSynced; //if we've sent this to our peers @@ -120,8 +126,12 @@ public: void SetSignature(const std::vector& vchSigIn) { vchSig = vchSigIn; } bool Sign(CKey& keyMasternode, CPubKey& pubKeyMasternode); - bool IsValid(bool fSignatureCheck); - void Relay(); + bool IsValid(bool fSignatureCheck) const; + void Relay() const; + + std::string GetVoteString() const { + return CGovernanceVoting::ConvertOutcomeToString(GetOutcome()); + } CTxIn& GetVinMasternode() { return vinMasternode; } @@ -144,7 +154,7 @@ public: return ss.GetHash(); } - std::string ToString() + std::string ToString() const { std::ostringstream ostr; ostr << vinMasternode.ToString() << ":" @@ -197,6 +207,7 @@ public: }; + /** * 12.1.1 - CGovernanceVoteManager * ------------------------------- diff --git a/src/governance-votedb.cpp b/src/governance-votedb.cpp new file mode 100644 index 000000000..4fd2b733d --- /dev/null +++ b/src/governance-votedb.cpp @@ -0,0 +1,84 @@ +// 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 "governance-votedb.h" + +CGovernanceObjectVoteFile::CGovernanceObjectVoteFile() + : nMemoryVotes(0), + listVotes(), + mapVoteIndex() +{} + +CGovernanceObjectVoteFile::CGovernanceObjectVoteFile(const CGovernanceObjectVoteFile& other) + : nMemoryVotes(other.nMemoryVotes), + listVotes(other.listVotes), + mapVoteIndex() +{ + RebuildIndex(); +} + +void CGovernanceObjectVoteFile::AddVote(const CGovernanceVote& vote) +{ + listVotes.push_front(vote); + mapVoteIndex[vote.GetHash()] = listVotes.begin(); + ++nMemoryVotes; +} + +bool CGovernanceObjectVoteFile::HasVote(const uint256& nHash) const +{ + vote_m_cit it = mapVoteIndex.find(nHash); + if(it == mapVoteIndex.end()) { + return false; + } + return true; +} + +bool CGovernanceObjectVoteFile::GetVote(const uint256& nHash, CGovernanceVote& vote) const +{ + vote_m_cit it = mapVoteIndex.find(nHash); + if(it == mapVoteIndex.end()) { + return false; + } + vote = *(it->second); + return true; +} + +std::vector CGovernanceObjectVoteFile::GetVotes() const +{ + std::vector vecResult; + for(vote_l_cit it = listVotes.begin(); it != listVotes.end(); ++it) { + vecResult.push_back(*it); + } + return vecResult; +} + +void CGovernanceObjectVoteFile::RemoveVotesFromMasternode(const CTxIn& vinMasternode) +{ + vote_l_it it = listVotes.begin(); + while(it != listVotes.end()) { + if(it->GetVinMasternode() == vinMasternode) { + listVotes.erase(it++); + } + else { + ++it; + } + } +} + +CGovernanceObjectVoteFile& CGovernanceObjectVoteFile::operator=(const CGovernanceObjectVoteFile& other) +{ + nMemoryVotes = other.nMemoryVotes; + listVotes = other.listVotes; + RebuildIndex(); + return *this; +} + +void CGovernanceObjectVoteFile::RebuildIndex() +{ + mapVoteIndex.clear(); + for(vote_l_it it = listVotes.begin(); it != listVotes.end(); ++it) { + CGovernanceVote& vote = *it; + mapVoteIndex[vote.GetHash()] = it; + } +} diff --git a/src/governance-votedb.h b/src/governance-votedb.h new file mode 100644 index 000000000..dd8383968 --- /dev/null +++ b/src/governance-votedb.h @@ -0,0 +1,93 @@ +// 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_VOTEDB_H +#define GOVERNANCE_VOTEDB_H + +#include +#include + +#include "governance-vote.h" +#include "serialize.h" +#include "uint256.h" + +/** + * Represents the collection of votes associated with a given CGovernanceObject + * Recently received votes are held in memory until a maximum size is reached after + * which older votes a flushed to a disk file. + * + * Note: This is a stub implementation that doesn't limit the number of votes held + * in memory and doesn't flush to disk. + */ +class CGovernanceObjectVoteFile +{ +public: // Types + typedef std::list vote_l_t; + + typedef vote_l_t::iterator vote_l_it; + + typedef vote_l_t::const_iterator vote_l_cit; + + typedef std::map vote_m_t; + + typedef vote_m_t::iterator vote_m_it; + + typedef vote_m_t::const_iterator vote_m_cit; + +private: + static const int MAX_MEMORY_VOTES = -1; + + int nMemoryVotes; + + vote_l_t listVotes; + + vote_m_t mapVoteIndex; + +public: + CGovernanceObjectVoteFile(); + + CGovernanceObjectVoteFile(const CGovernanceObjectVoteFile& other); + + /** + * Add a vote to the file + */ + void AddVote(const CGovernanceVote& vote); + + /** + * Return true if the vote with this hash is currently cached in memory + */ + bool HasVote(const uint256& nHash) const; + + /** + * Retrieve a vote cached in memory + */ + bool GetVote(const uint256& nHash, CGovernanceVote& vote) const; + + int GetVoteCount() { + return nMemoryVotes; + } + + std::vector GetVotes() const; + + CGovernanceObjectVoteFile& operator=(const CGovernanceObjectVoteFile& other); + + void RemoveVotesFromMasternode(const CTxIn& vinMasternode); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(nMemoryVotes); + READWRITE(listVotes); + if(ser_action.ForRead()) { + RebuildIndex(); + } + } +private: + void RebuildIndex(); + +}; + +#endif diff --git a/src/governance.cpp b/src/governance.cpp index 146a596eb..62e67e53b 100644 --- a/src/governance.cpp +++ b/src/governance.cpp @@ -27,18 +27,19 @@ std::map mapAskedForGovernanceObject; int nSubmittedFinalBudget; +const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-1"; + CGovernanceManager::CGovernanceManager() - : mapCollateral(), - pCurrentBlockIndex(NULL), + : pCurrentBlockIndex(NULL), nTimeLastDiff(0), nCachedBlockHeight(0), mapObjects(), mapSeenGovernanceObjects(), - mapSeenVotes(), - mapOrphanVotes(), - mapVotesByHash(), - mapVotesByType(), + mapVoteToObject(MAX_CACHE_SIZE), + mapInvalidVotes(MAX_CACHE_SIZE), + mapOrphanVotes(MAX_CACHE_SIZE), mapLastMasternodeTrigger(), + setRequestedObjects(), cs() {} @@ -48,12 +49,8 @@ bool CGovernanceManager::HaveObjectForHash(uint256 nHash) { return (mapObjects.count(nHash) == 1); } -bool CGovernanceManager::HaveVoteForHash(uint256 nHash) { - LOCK(cs); - return (mapVotesByHash.count(nHash) == 1); -} - -bool CGovernanceManager::SerializeObjectForHash(uint256 nHash, CDataStream& ss) { +bool CGovernanceManager::SerializeObjectForHash(uint256 nHash, CDataStream& ss) +{ LOCK(cs); object_m_it it = mapObjects.find(nHash); if (it == mapObjects.end()) { @@ -63,13 +60,36 @@ bool CGovernanceManager::SerializeObjectForHash(uint256 nHash, CDataStream& ss) return true; } -bool CGovernanceManager::SerializeVoteForHash(uint256 nHash, CDataStream& ss) { +bool CGovernanceManager::HaveVoteForHash(uint256 nHash) +{ LOCK(cs); - vote_m_it it = mapVotesByHash.find(nHash); - if (it == mapVotesByHash.end()) { + + CGovernanceObject* pGovobj = NULL; + if(!mapVoteToObject.Get(nHash,pGovobj)) { return false; } - ss << it->second; + + if(!pGovobj->GetVoteFile().HasVote(nHash)) { + return false; + } + return true; +} + +bool CGovernanceManager::SerializeVoteForHash(uint256 nHash, CDataStream& ss) +{ + LOCK(cs); + + CGovernanceObject* pGovobj = NULL; + if(!mapVoteToObject.Get(nHash,pGovobj)) { + return false; + } + + CGovernanceVote vote; + if(!pGovobj->GetVoteFile().GetVote(nHash, vote)) { + return false; + } + + ss << vote; return true; } @@ -79,12 +99,6 @@ void CGovernanceManager::AddSeenGovernanceObject(uint256 nHash, int status) mapSeenGovernanceObjects[nHash] = status; } -void CGovernanceManager::AddSeenVote(uint256 nHash, int status) -{ - LOCK(cs); - mapSeenVotes[nHash] = status; -} - void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv) { // lite mode is not supported @@ -93,7 +107,7 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C if(pfrom->nVersion < MIN_GOVERNANCE_PEER_PROTO_VERSION) return; - LOCK(governance.cs); + LOCK(cs); // ANOTHER USER IS ASKING US TO HELP THEM SYNC GOVERNANCE OBJECT DATA if (strCommand == NetMsgType::MNGOVERNANCESYNC) @@ -126,7 +140,6 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C else if (strCommand == NetMsgType::MNGOVERNANCEOBJECT) { - LOCK(cs); // MAKE SURE WE HAVE A VALID REFERENCE TO THE TIP BEFORE CONTINUING if(!pCurrentBlockIndex) { @@ -137,8 +150,20 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C CGovernanceObject govobj; vRecv >> govobj; - if(mapSeenGovernanceObjects.count(govobj.GetHash())){ + uint256 nHash = govobj.GetHash(); + std::string strHash = nHash.ToString(); + + LogPrint("gobject", "CGovernanceManager -- Received object: %s\n", strHash); + + if(!AcceptObjectMessage(nHash)) { + LogPrintf("CGovernanceManager -- Received unrequested object: %s\n", strHash); + Misbehaving(pfrom->GetId(), 20); + return; + } + + if(mapSeenGovernanceObjects.count(nHash)) { // TODO - print error code? what if it's GOVOBJ_ERROR_IMMATURE? + LogPrint("gobject", "CGovernanceManager -- Received already seen object: %s\n", strHash); return; } @@ -146,7 +171,7 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN if(!govobj.IsValidLocally(pCurrentBlockIndex, strError, true)) { - mapSeenGovernanceObjects.insert(std::make_pair(govobj.GetHash(), SEEN_OBJECT_ERROR_INVALID)); + mapSeenGovernanceObjects.insert(std::make_pair(nHash, SEEN_OBJECT_ERROR_INVALID)); LogPrintf("MNGOVERNANCEOBJECT -- Governance object is invalid - %s\n", strError); return; } @@ -157,7 +182,7 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C if(AddGovernanceObject(govobj)) { - LogPrintf("MNGOVERNANCEOBJECT -- %s new\n", govobj.GetHash().ToString()); + LogPrintf("MNGOVERNANCEOBJECT -- %s new\n", strHash); govobj.Relay(); } @@ -168,7 +193,8 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C // WE MIGHT HAVE PENDING/ORPHAN VOTES FOR THIS OBJECT - CheckOrphanVotes(); + CGovernanceException exception; + CheckOrphanVotes(pfrom, govobj, exception); } // A NEW GOVERNANCE OBJECT VOTE HAS ARRIVED @@ -182,59 +208,51 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C CGovernanceVote vote; vRecv >> vote; - //vote.fValid = true; - // IF WE'VE SEEN THIS OBJECT THEN SKIP + LogPrint("gobject", "CGovernanceManager -- Received vote: %s\n", vote.ToString()); - if(mapSeenVotes.count(vote.GetHash())) return; - - // FIND THE MASTERNODE OF THE VOTER - - if(!mnodeman.Has(vote.GetVinMasternode())) { - LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- unknown masternode - vin: %s\n", vote.GetVinMasternode().ToString()); - mnodeman.AskForMN(pfrom, vote.GetVinMasternode()); + if(!AcceptVoteMessage(vote.GetHash())) { + LogPrintf("CGovernanceManager -- Received unrequested vote object: %s, hash: %s, peer = %d\n", + vote.ToString(), + vote.GetHash().ToString(), + pfrom->GetId()); + //Misbehaving(pfrom->GetId(), 20); return; } - // CHECK LOCAL VALIDITY AGAINST BLOCKCHAIN, TIME DATA, ETC - - if(!vote.IsValid(true)){ - LogPrintf("MNGOVERNANCEOBJECTVOTE -- signature invalid\n"); - if(masternodeSync.IsSynced()) Misbehaving(pfrom->GetId(), 20); - // it could just be a non-synced masternode - mnodeman.AskForMN(pfrom, vote.GetVinMasternode()); - mapSeenVotes.insert(std::make_pair(vote.GetHash(), SEEN_OBJECT_ERROR_INVALID)); - return; - } else { - mapSeenVotes.insert(std::make_pair(vote.GetHash(), SEEN_OBJECT_IS_VALID)); - } - - // IF EVERYTHING CHECKS OUT, UPDATE THE GOVERNANCE MANAGER - - std::string strError = ""; - if(AddOrUpdateVote(vote, pfrom, strError)) { - LogPrint("gobject", "MNGOVERNANCEOBJECTVOTE -- %s new\n", vote.GetHash().ToString()); + CGovernanceException exception; + if(ProcessVote(pfrom, vote, exception)) { + LogPrint("gobject", "CGovernanceManager -- Accepted vote\n"); vote.Relay(); - masternodeSync.AddedBudgetItem(vote.GetHash()); - mnodeman.AddGovernanceVote(vote.GetVinMasternode(), vote.GetParentHash()); + } + else { + LogPrint("gobject", "CGovernanceManager -- Rejected vote, error = %s\n", exception.what()); + if((exception.GetNodePenalty() != 0) && masternodeSync.IsSynced()) { + Misbehaving(pfrom->GetId(), exception.GetNodePenalty()); + } + return; } } - } -void CGovernanceManager::CheckOrphanVotes() +void CGovernanceManager::CheckOrphanVotes(CNode* pfrom, CGovernanceObject& govobj, CGovernanceException& exception) { - LOCK(cs); + uint256 nHash = govobj.GetHash(); + std::vector vecVotes; + mapOrphanVotes.GetAll(nHash, vecVotes); - std::string strError = ""; - vote_m_it it1 = mapOrphanVotes.begin(); - while(it1 != mapOrphanVotes.end()){ - if(AddOrUpdateVote(((*it1).second), NULL, strError)){ - LogPrintf("CGovernanceManager::CheckOrphanVotes -- Governance object is known, activating and removing orphan vote\n"); - mapOrphanVotes.erase(it1++); - } else { - ++it1; + for(size_t i = 0; i < vecVotes.size(); ++i) { + CGovernanceVote& vote = vecVotes[i]; + CGovernanceException exception; + if(govobj.ProcessVote(pfrom, vote, exception)) { + vecVotes[i].Relay(); + mapOrphanVotes.Erase(nHash, vote); + } + else { + if((exception.GetNodePenalty() != 0) && masternodeSync.IsSynced()) { + Misbehaving(pfrom->GetId(), exception.GetNodePenalty()); + } } } } @@ -270,6 +288,10 @@ bool CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj) << ", nObjectType = " << govobj.nObjectType << endl; ); + if(govobj.GetObjectType() == GOVERNANCE_OBJECT_TRIGGER) { + mapLastMasternodeTrigger[govobj.GetMasternodeVin().prevout] = nCachedBlockHeight; + } + switch(govobj.nObjectType) { case GOVERNANCE_OBJECT_TRIGGER: mapLastMasternodeTrigger[govobj.vinMasternode.prevout] = nCachedBlockHeight; @@ -299,6 +321,7 @@ void CGovernanceManager::UpdateCachesAndClean() if(it == mapObjects.end()) { continue; } + it->second.ClearMasternodeVotes(); it->second.fDirtyCache = true; } @@ -312,8 +335,6 @@ void CGovernanceManager::UpdateCachesAndClean() object_m_it it = mapObjects.begin(); - count_m_t mapDirtyObjects; - // Clean up any expired or invalid triggers triggerman.CleanAndRemove(); @@ -327,9 +348,7 @@ void CGovernanceManager::UpdateCachesAndClean() } // IF CACHE IS NOT DIRTY, WHY DO THIS? - if(pObj->fDirtyCache) { - mapDirtyObjects.insert(std::make_pair((*it).first, 1)); - + if(pObj->IsSetDirtyCache()) { // UPDATE LOCAL VALIDITY AGAINST CRYPTO DATA pObj->UpdateLocalValidity(pCurrentBlockIndex); @@ -339,31 +358,29 @@ void CGovernanceManager::UpdateCachesAndClean() // IF DELETE=TRUE, THEN CLEAN THE MESS UP! - if(pObj->fCachedDelete || pObj->fExpired) { + if(pObj->IsSetCachedDelete() || pObj->IsSetExpired()) { LogPrintf("CGovernanceManager::UpdateCachesAndClean -- erase obj %s\n", (*it).first.ToString()); mnodeman.RemoveGovernanceObject(pObj->GetHash()); + + // Remove vote references + const object_ref_cache_t::list_t& listItems = mapVoteToObject.GetItemList(); + object_ref_cache_t::list_cit lit = listItems.begin(); + while(lit != listItems.end()) { + if(lit->value == pObj) { + uint256 nKey = lit->key; + ++lit; + mapVoteToObject.Erase(nKey); + } + else { + ++lit; + } + } + mapObjects.erase(it++); } else { ++it; } } - - // CHECK EACH GOVERNANCE OBJECTS VALIDITY (CPU HEAVY) - - // 12.1 todo - compile issues - - // std::map::iterator it = mapVotesByHash.begin(); - // while(it != mapVotes.end()) { - - // // ONLY UPDATE THE DIRTY OBJECTS! - - // if(mapDirtyObjects.count((*it).first)) - // { - // (*it).second.fValid = (*it).second.IsValid(true); - // ++it; - // } - // } - } CGovernanceObject *CGovernanceManager::FindGovernanceObject(const uint256& nHash) @@ -376,21 +393,18 @@ CGovernanceObject *CGovernanceManager::FindGovernanceObject(const uint256& nHash return NULL; } -std::vector CGovernanceManager::GetMatchingVotes(const uint256& nParentHash) +std::vector CGovernanceManager::GetMatchingVotes(const uint256& nParentHash) { - std::vector vecResult; + LOCK(cs); + std::vector vecResult; - // LOOP THROUGH ALL VOTES AND FIND THOSE MATCHING USER HASH - - vote_m_it it2 = mapVotesByType.begin(); - while(it2 != mapVotesByType.end()) { - if((*it2).second.GetParentHash() == nParentHash) { - vecResult.push_back(&(*it2).second); - } - ++it2; + object_m_it it = mapObjects.find(nParentHash); + if(it == mapObjects.end()) { + return vecResult; } + CGovernanceObject& govobj = it->second; - return vecResult; + return govobj.GetVoteFile().GetVotes(); } std::vector CGovernanceManager::GetAllNewerThan(int64_t nMoreThanTime) @@ -404,7 +418,7 @@ std::vector CGovernanceManager::GetAllNewerThan(int64_t nMor { // IF THIS OBJECT IS OLDER THAN TIME, CONTINUE - if((*it).second.nTime < nMoreThanTime) { + if((*it).second.GetCreationTime() < nMoreThanTime) { ++it; continue; } @@ -429,7 +443,7 @@ struct sortProposalsByVotes { bool operator()(const std::pair &left, const std::pair &right) { if (left.second != right.second) return (left.second > right.second); - return (UintToArith256(left.first->nCollateralHash) > UintToArith256(right.first->nCollateralHash)); + return (UintToArith256(left.first->GetCollateralHash()) > UintToArith256(right.first->GetCollateralHash())); } }; @@ -444,8 +458,8 @@ void CGovernanceManager::NewBlock() // CHECK OBJECTS WE'VE ASKED FOR, REMOVE OLD ENTRIES std::map::iterator it = mapAskedForGovernanceObject.begin(); - while(it != mapAskedForGovernanceObject.end()){ - if((*it).second > GetTime() - (60*60*24)){ + while(it != mapAskedForGovernanceObject.end()) { + if((*it).second > GetTime() - (60*60*24)) { ++it; } else { mapAskedForGovernanceObject.erase(it++); @@ -457,6 +471,59 @@ void CGovernanceManager::NewBlock() UpdateCachesAndClean(); } +bool CGovernanceManager::ConfirmInventoryRequest(const CInv& inv) +{ + LOCK(cs); + + LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest inv = %s\n", inv.ToString()); + + // First check if we've already recorded this object + switch(inv.type) { + case MSG_GOVERNANCE_OBJECT: + { + object_m_it it = mapObjects.find(inv.hash); + if(it != mapObjects.end()) { + LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest already have governance object, returning false\n"); + return false; + } + } + break; + case MSG_GOVERNANCE_OBJECT_VOTE: + { + if(mapVoteToObject.HasKey(inv.hash)) { + LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest already have governance vote, returning false\n"); + return false; + } + } + break; + default: + LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest unknown type, returning false\n"); + return false; + } + + + hash_s_t* setHash = NULL; + switch(inv.type) { + case MSG_GOVERNANCE_OBJECT: + setHash = &setRequestedObjects; + break; + case MSG_GOVERNANCE_OBJECT_VOTE: + setHash = &setRequestedVotes; + break; + default: + return false; + } + + hash_s_cit it = setHash->find(inv.hash); + if(it == setHash->end()) { + setHash->insert(inv.hash); + LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest added inv to requested set\n"); + } + + LogPrint("gobject", "CGovernanceManager::ConfirmInventoryRequest reached end, returning true\n"); + return true; +} + void CGovernanceManager::Sync(CNode* pfrom, uint256 nProp) { @@ -470,32 +537,27 @@ void CGovernanceManager::Sync(CNode* pfrom, uint256 nProp) // SYNC GOVERNANCE OBJECTS WITH OTHER CLIENT { - LOCK(cs); - object_m_it it1 = mapObjects.begin(); - while(it1 != mapObjects.end()) { - uint256 h = (*it1).first; + LOCK(cs); + for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { + uint256 h = it->first; - if((*it1).second.fCachedValid && (nProp == uint256() || h == nProp)) { - // Push the inventory budget proposal message over to the other client - pfrom->PushInventory(CInv(MSG_GOVERNANCE_OBJECT, h)); - nInvCount++; - } - ++it1; - } + CGovernanceObject& govobj = it->second; - // SYNC OUR GOVERNANCE OBJECT VOTES WITH THEIR GOVERNANCE OBJECT VOTES + if(govobj.IsSetCachedValid() && (nProp == uint256() || h == nProp)) { + // Push the inventory budget proposal message over to the other client + pfrom->PushInventory(CInv(MSG_GOVERNANCE_OBJECT, h)); + ++nInvCount; - vote_m_it it2 = mapVotesByHash.begin(); - while(it2 != mapVotesByHash.end()) { - CGovernanceVote& vote = it2->second; - if(!vote.IsValid(true)) { - // Don't relay votes that are now invalid (ie. missing MN) to avoid being banned - continue; - } - pfrom->PushInventory(CInv(MSG_GOVERNANCE_OBJECT_VOTE, (*it2).first)); - nInvCount++; - ++it2; - } + std::vector vecVotes = govobj.GetVoteFile().GetVotes(); + for(size_t i = 0; i < vecVotes.size(); ++i) { + if(!vecVotes[i].IsValid(true)) { + continue; + } + pfrom->PushInventory(CInv(MSG_GOVERNANCE_OBJECT_VOTE, vecVotes[i].GetHash())); + ++nInvCount; + } + } + } } pfrom->PushMessage(NetMsgType::SYNCSTATUSCOUNT, MASTERNODE_SYNC_GOVOBJ, nInvCount); @@ -510,92 +572,6 @@ void CGovernanceManager::SyncParentObjectByVote(CNode* pfrom, const CGovernanceV } } -bool CGovernanceManager::AddOrUpdateVote(const CGovernanceVote& vote, CNode* pfrom, std::string& strError) -{ - // MAKE SURE WE HAVE THE PARENT OBJECT THE VOTE IS FOR - - bool syncparent = false; - uint256 votehash; - { - LOCK(cs); - if(!mapObjects.count(vote.GetParentHash())) { - if(pfrom) { - // only ask for missing items after our syncing process is complete -- - // otherwise we'll think a full sync succeeded when they return a result - if(!masternodeSync.IsSynced()) return false; - - // ADD THE VOTE AS AN ORPHAN, TO BE USED UPON RECEIVAL OF THE PARENT OBJECT - - LogPrintf("CGovernanceManager::AddOrUpdateVote -- Unknown object %s, asking for source\n", vote.GetParentHash().ToString()); - mapOrphanVotes[vote.GetParentHash()] = vote; - - // ASK FOR THIS VOTES PARENT SPECIFICALLY FROM THIS USER (THEY SHOULD HAVE IT, NO?) - - if(!mapAskedForGovernanceObject.count(vote.GetParentHash())){ - syncparent = true; - votehash = vote.GetParentHash(); - mapAskedForGovernanceObject[vote.GetParentHash()] = GetTime(); - } else { - strError = "Governance object not found! Sync message has been already pushed."; - return false; - } - } - } - } - - // Need to keep this out of the locked section - if(syncparent) { - pfrom->PushMessage(NetMsgType::MNGOVERNANCESYNC, votehash); - strError = "Governance object not found! Sync message was pushed."; - return false; - } - - // Reestablish lock - LOCK(cs); - // GET DETERMINISTIC HASH WHICH COLLIDES ON MASTERNODE-VIN/GOVOBJ-HASH/VOTE-SIGNAL - - uint256 nTypeHash = vote.GetTypeHash(); - uint256 nHash = vote.GetHash(); - - // LOOK FOR PREVIOUS VOTES BY THIS SPECIFIC MASTERNODE FOR THIS SPECIFIC SIGNAL - - vote_m_it it = mapVotesByType.find(nTypeHash); - if(it != mapVotesByType.end()) { - if(it->second.GetTimestamp() > vote.GetTimestamp()) { - strError = strprintf("new vote older than existing vote - %s", nTypeHash.ToString()); - LogPrint("gobject", "CGovernanceObject::AddOrUpdateVote -- %s\n", strError); - return false; - } - if(vote.GetTimestamp() - it->second.GetTimestamp() < GOVERNANCE_UPDATE_MIN) { - strError = strprintf("time between votes is too soon - %s - %lli", nTypeHash.ToString(), vote.GetTimestamp() - it->second.GetTimestamp()); - LogPrint("gobject", "CGovernanceObject::AddOrUpdateVote -- %s\n", strError); - return false; - } - } - - // UPDATE TO NEWEST VOTE - { - mapVotesByHash[nHash] = vote; - mapVotesByType[nTypeHash] = vote; - } - - // SET CACHE AS DIRTY / WILL BE UPDATED NEXT BLOCK - - CGovernanceObject* pGovObj = FindGovernanceObject(vote.GetParentHash()); - if(pGovObj) - { - pGovObj->fDirtyCache = true; - UpdateCachesAndClean(); - if(pGovObj->GetObjectType() == GOVERNANCE_OBJECT_WATCHDOG) { - mnodeman.UpdateWatchdogVoteTime(vote.GetVinMasternode()); - } - } else { - LogPrintf("CGovernanceObject::AddOrUpdateVote -- Governance object not found! Can't update fDirtyCache - %s\n", vote.GetParentHash().ToString()); - } - - return true; -} - bool CGovernanceManager::MasternodeRateCheck(const CTxIn& vin, int nObjectType) { LOCK(cs); @@ -626,6 +602,118 @@ bool CGovernanceManager::MasternodeRateCheck(const CTxIn& vin, int nObjectType) return false; } +bool CGovernanceManager::ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception) +{ + LOCK(cs); + uint256 nHashVote = vote.GetHash(); + if(mapInvalidVotes.HasKey(nHashVote)) { + std::ostringstream ostr; + ostr << "CGovernanceManager::ProcessVote -- Old invalid vote " + << ", MN outpoint = " << vote.GetVinMasternode().prevout.ToStringShort() + << ", governance object hash = " << vote.GetParentHash().ToString() << "\n"; + LogPrintf(ostr.str().c_str()); + exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_PERMANENT_ERROR, 20); + return false; + } + + uint256 nHashGovobj = vote.GetParentHash(); + object_m_it it = mapObjects.find(nHashGovobj); + if(it == mapObjects.end()) { + mapOrphanVotes.Insert(nHashGovobj, vote); + RequestGovernanceObject(pfrom, nHashGovobj); + std::ostringstream ostr; + ostr << "CGovernanceManager::ProcessVote -- Unknown parent object " + << ", MN outpoint = " << vote.GetVinMasternode().prevout.ToStringShort() + << ", governance object hash = " << vote.GetParentHash().ToString() << "\n"; + LogPrintf(ostr.str().c_str()); + exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); + return false; + } + + CGovernanceObject& govobj = it->second; + bool fOk = govobj.ProcessVote(pfrom, vote, exception); + if(fOk) { + mapVoteToObject.Insert(vote.GetHash(), &govobj); + + if(govobj.GetObjectType() == GOVERNANCE_OBJECT_WATCHDOG) { + mnodeman.UpdateWatchdogVoteTime(vote.GetVinMasternode()); + } + } + return fOk; +} + +void CGovernanceManager::CheckMasternodeOrphanVotes() +{ + LOCK(cs); + for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { + it->second.CheckOrphanVotes(); + } +} + +void CGovernanceManager::RequestGovernanceObject(CNode* pfrom, const uint256& nHash) +{ + if(!pfrom) { + return; + } + + pfrom->PushMessage(NetMsgType::MNGOVERNANCESYNC, nHash); +} + +bool CGovernanceManager::AcceptObjectMessage(const uint256& nHash) +{ + LOCK(cs); + return AcceptMessage(nHash, setRequestedObjects); +} + +bool CGovernanceManager::AcceptVoteMessage(const uint256& nHash) +{ + LOCK(cs); + return AcceptMessage(nHash, setRequestedVotes); +} + +bool CGovernanceManager::AcceptMessage(const uint256& nHash, hash_s_t& setHash) +{ + hash_s_it it = setHash.find(nHash); + if(it == setHash.end()) { + // We never requested this + return false; + } + // Only accept one response + setHash.erase(it); + return true; +} + +void CGovernanceManager::RebuildIndexes() +{ + mapVoteToObject.Clear(); + for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { + CGovernanceObject& govobj = it->second; + std::vector vecVotes = govobj.GetVoteFile().GetVotes(); + for(size_t i = 0; i < vecVotes.size(); ++i) { + mapVoteToObject.Insert(vecVotes[i].GetHash(), &govobj); + } + } +} + +int CGovernanceManager::GetMasternodeIndex(const CTxIn& masternodeVin) +{ + LOCK(cs); + bool fIndexRebuilt = false; + int nMNIndex = mnodeman.GetMasternodeIndex(masternodeVin, fIndexRebuilt); + while(fIndexRebuilt) { + RebuildVoteMaps(); + nMNIndex = mnodeman.GetMasternodeIndex(masternodeVin, fIndexRebuilt); + } + return nMNIndex; +} + +void CGovernanceManager::RebuildVoteMaps() +{ + for(object_m_it it = mapObjects.begin(); it != mapObjects.end(); ++it) { + it->second.RebuildVoteMap(); + } +} + void CGovernanceManager::AddCachedTriggers() { LOCK(cs); @@ -642,74 +730,195 @@ void CGovernanceManager::AddCachedTriggers() } CGovernanceObject::CGovernanceObject() - : cs(), - nHashParent(), - nRevision(0), - nTime(0), - nCollateralHash(), - strData(), - nObjectType(GOVERNANCE_OBJECT_UNKNOWN), - vinMasternode(), - vchSig(), - fCachedLocalValidity(false), - strLocalValidityError(), - fCachedFunding(false), - fCachedValid(true), - fCachedDelete(false), - fCachedEndorsed(false), - fDirtyCache(true), - fUnparsable(false), - fExpired(false) +: cs(), + nObjectType(GOVERNANCE_OBJECT_UNKNOWN), + nHashParent(), + nRevision(0), + nTime(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(), - nHashParent(nHashParentIn), - nRevision(nRevisionIn), - nTime(nTimeIn), - nCollateralHash(nCollateralHashIn), - strData(strDataIn), - nObjectType(GOVERNANCE_OBJECT_UNKNOWN), - vinMasternode(), - vchSig(), - fCachedLocalValidity(false), - strLocalValidityError(), - fCachedFunding(false), - fCachedValid(true), - fCachedDelete(false), - fCachedEndorsed(false), - fDirtyCache(true), - fUnparsable(false), - fExpired(false) +: cs(), + nObjectType(GOVERNANCE_OBJECT_UNKNOWN), + nHashParent(nHashParentIn), + nRevision(nRevisionIn), + nTime(nTimeIn), + 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(), - nHashParent(other.nHashParent), - nRevision(other.nRevision), - nTime(other.nTime), - nCollateralHash(other.nCollateralHash), - strData(other.strData), - nObjectType(other.nObjectType), - 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), - fUnparsable(other.fUnparsable), - fExpired(other.fExpired) +: cs(), + nObjectType(other.nObjectType), + nHashParent(other.nHashParent), + nRevision(other.nRevision), + nTime(other.nTime), + 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) { + mapOrphanVotes.Insert(vote.GetVinMasternode(), vote); + if(pfrom) { + mnodeman.AskForMN(pfrom, vote.GetVinMasternode()); + } + std::ostringstream ostr; + ostr << "CGovernanceObject::UpdateVote -- Masternode index not found\n"; + LogPrintf(ostr.str().c_str()); + exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_WARNING); + 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::UpdateVote -- 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::UpdateVote -- 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(); + int64_t nTimeDelta = nNow - voteInstance.nTime; + if(nTimeDelta < GOVERNANCE_UPDATE_MIN) { + std::ostringstream ostr; + ostr << "CGovernanceObject::UpdateVote -- 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::UpdateVote -- 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; @@ -755,11 +964,6 @@ bool CGovernanceObject::CheckSignature(CPubKey& pubKeyMasternode) return true; } -int CGovernanceObject::GetObjectType() -{ - return nObjectType; -} - int CGovernanceObject::GetObjectSubtype() { // todo - 12.1 @@ -1076,33 +1280,50 @@ bool CGovernanceObject::IsCollateralValid(std::string& strError) 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) +int CGovernanceObject::GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn) const { return GetYesCount(eVoteSignalIn) - GetNoCount(eVoteSignalIn); } -int CGovernanceObject::GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn) +int CGovernanceObject::GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn) const { return GetNoCount(eVoteSignalIn) - GetYesCount(eVoteSignalIn); } -int CGovernanceObject::GetYesCount(vote_signal_enum_t eVoteSignalIn) +int CGovernanceObject::GetYesCount(vote_signal_enum_t eVoteSignalIn) const { - return governance.CountMatchingVotes((*this), eVoteSignalIn, VOTE_OUTCOME_YES); + return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_YES); } -int CGovernanceObject::GetNoCount(vote_signal_enum_t eVoteSignalIn) +int CGovernanceObject::GetNoCount(vote_signal_enum_t eVoteSignalIn) const { - return governance.CountMatchingVotes((*this), eVoteSignalIn, VOTE_OUTCOME_NO); + return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_NO); } -int CGovernanceObject::GetAbstainCount(vote_signal_enum_t eVoteSignalIn) +int CGovernanceObject::GetAbstainCount(vote_signal_enum_t eVoteSignalIn) const { - return governance.CountMatchingVotes((*this), eVoteSignalIn, VOTE_OUTCOME_ABSTAIN); + return CountMatchingVotes(eVoteSignalIn, VOTE_OUTCOME_ABSTAIN); } void CGovernanceObject::Relay() @@ -1116,10 +1337,8 @@ std::string CGovernanceManager::ToString() const std::ostringstream info; info << "Governance Objects: " << (int)mapObjects.size() << - ", Seen Budgets: " << (int)mapSeenGovernanceObjects.size() << - ", Seen Budget Votes: " << (int)mapSeenVotes.size() << - ", VoteByHash Count: " << (int)mapVotesByHash.size() << - ", VoteByType Count: " << (int)mapVotesByType.size(); + ", Seen Budgets : " << (int)mapSeenGovernanceObjects.size() << + ", Vote Count : " << (int)mapVoteToObject.GetSize(); return info.str(); } @@ -1146,32 +1365,6 @@ void CGovernanceManager::UpdatedBlockTip(const CBlockIndex *pindex) NewBlock(); } -int CGovernanceManager::CountMatchingVotes(CGovernanceObject& govobj, vote_signal_enum_t eVoteSignalIn, vote_outcome_enum_t eVoteOutcomeIn) -{ - /* - * - * Count matching votes and return - * - */ - - LOCK(cs); - int nCount = 0; - - uint256 hash = govobj.GetHash(); - std::map::iterator it = mapVotesByType.begin(); - while(it != mapVotesByType.end()) { - if(it->second.IsValid() && - it->second.GetSignal() == eVoteSignalIn && - it->second.GetOutcome() == eVoteOutcomeIn && - it->second.GetParentHash() == hash) { - ++nCount; - } - ++it; - } - - return nCount; -} - void CGovernanceObject::UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex) { // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED @@ -1233,3 +1426,18 @@ void CGovernanceObject::swap(CGovernanceObject& first, CGovernanceObject& second 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 7f0e085e0..8b655c969 100644 --- a/src/governance.h +++ b/src/governance.h @@ -2,8 +2,8 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#ifndef GOVERANCE_H -#define GOVERANCE_H +#ifndef GOVERNANCE_H +#define GOVERNANCE_H //#define ENABLE_DASH_DEBUG @@ -15,17 +15,22 @@ #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; @@ -57,6 +62,8 @@ extern CGovernanceManager governance; // class CGovernanceManager { + friend class CGovernanceObject; + public: // Types typedef std::map object_m_t; @@ -65,6 +72,8 @@ public: // Types typedef object_m_t::const_iterator object_m_cit; + typedef CacheMap object_ref_cache_t; + typedef std::map count_m_t; typedef count_m_t::iterator count_m_it; @@ -77,11 +86,9 @@ public: // Types typedef vote_m_t::const_iterator vote_m_cit; - typedef std::map transaction_m_t; + typedef CacheMap vote_cache_t; - typedef transaction_m_t::iterator transaction_m_it; - - typedef transaction_m_t::const_iterator transaction_m_cit; + typedef CacheMultiMap vote_mcache_t; typedef object_m_t::size_type size_type; @@ -91,10 +98,17 @@ public: // Types typedef txout_m_t::const_iterator txout_m_cit; -private: + typedef std::set hash_s_t; + + typedef hash_s_t::iterator hash_s_it; + + typedef hash_s_t::const_iterator hash_s_cit; + +private: + static const int MAX_CACHE_SIZE = 1000000; + + static const std::string SERIALIZATION_VERSION_STRING; - //hold txes until they mature enough to use - transaction_m_t mapCollateral; // Keep track of current block index const CBlockIndex *pCurrentBlockIndex; @@ -105,57 +119,68 @@ private: object_m_t mapObjects; count_m_t mapSeenGovernanceObjects; - count_m_t mapSeenVotes; - vote_m_t mapOrphanVotes; - // todo: one of these should point to the other - // -- must be carefully managed while adding/removing/updating - vote_m_t mapVotesByHash; - vote_m_t mapVotesByType; + object_ref_cache_t mapVoteToObject; + + vote_cache_t mapInvalidVotes; + + vote_mcache_t mapOrphanVotes; txout_m_t mapLastMasternodeTrigger; + hash_s_t setRequestedObjects; + + hash_s_t setRequestedVotes; + public: // critical section to protect the inner data structures mutable CCriticalSection cs; CGovernanceManager(); + virtual ~CGovernanceManager() {} + void ClearSeen() { LOCK(cs); mapSeenGovernanceObjects.clear(); - mapSeenVotes.clear(); } int CountProposalInventoryItems() { - return mapSeenGovernanceObjects.size() + mapSeenVotes.size(); + // TODO What is this for ? + return mapSeenGovernanceObjects.size(); + //return mapSeenGovernanceObjects.size() + mapSeenVotes.size(); } + /** + * This is called by AlreadyHave in main.cpp as part of the inventory + * retrieval process. Returns true if we want to retrieve the object, otherwise + * false. (Note logic is inverted in AlreadyHave). + */ + bool ConfirmInventoryRequest(const CInv& inv); + void Sync(CNode* node, uint256 nProp); + void SyncParentObjectByVote(CNode* pfrom, const CGovernanceVote& vote); void ProcessMessage(CNode* pfrom, std::string& strCommand, CDataStream& vRecv); + void NewBlock(); CGovernanceObject *FindGovernanceObject(const uint256& nHash); - std::vector GetMatchingVotes(const uint256& nParentHash); + std::vector GetMatchingVotes(const uint256& nParentHash); std::vector GetAllNewerThan(int64_t nMoreThanTime); - int CountMatchingVotes(CGovernanceObject& govobj, vote_signal_enum_t nVoteSignalIn, vote_outcome_enum_t nVoteOutcomeIn); - bool IsBudgetPaymentBlock(int nBlockHeight); bool AddGovernanceObject (CGovernanceObject& govobj); - bool AddOrUpdateVote(const CGovernanceVote& vote, CNode* pfrom, std::string& strError); std::string GetRequiredPaymentsString(int nBlockHeight); - void CleanAndRemove(bool fSignatureCheck); - void UpdateCachesAndClean(); - void CheckAndRemove() {UpdateCachesAndClean();} - void CheckOrphanVotes(); + void UpdateCachesAndClean(); + + void CheckAndRemove() {UpdateCachesAndClean();} void Clear() { @@ -164,10 +189,9 @@ public: LogPrint("gobject", "Governance object manager was cleared\n"); mapObjects.clear(); mapSeenGovernanceObjects.clear(); - mapSeenVotes.clear(); - mapOrphanVotes.clear(); - mapVotesByType.clear(); - mapVotesByHash.clear(); + mapVoteToObject.Clear(); + mapInvalidVotes.Clear(); + mapOrphanVotes.Clear(); mapLastMasternodeTrigger.clear(); } @@ -178,14 +202,25 @@ public: template inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) { LOCK(cs); + std::string strVersion; + if(ser_action.ForRead()) { + READWRITE(strVersion); + } + else { + strVersion = SERIALIZATION_VERSION_STRING; + READWRITE(strVersion); + } READWRITE(mapSeenGovernanceObjects); - READWRITE(mapSeenVotes); + READWRITE(mapInvalidVotes); READWRITE(mapOrphanVotes); READWRITE(mapObjects); - READWRITE(mapVotesByHash); - READWRITE(mapVotesByType); READWRITE(mapLastMasternodeTrigger); + if(ser_action.ForRead() && (strVersion != SERIALIZATION_VERSION_STRING)) { + Clear(); + return; + } if(ser_action.ForRead()) { + RebuildIndexes(); AddCachedTriggers(); } } @@ -211,11 +246,90 @@ public: bool MasternodeRateCheck(const CTxIn& vin, int nObjectType); + bool ProcessVote(const CGovernanceVote& vote, CGovernanceException& exception) { + return ProcessVote(NULL, vote, exception); + } + + void CheckMasternodeOrphanVotes(); + private: + void RequestGovernanceObject(CNode* pfrom, const uint256& nHash); + + void AddInvalidVote(const CGovernanceVote& vote) + { + mapInvalidVotes.Insert(vote.GetHash(), vote); + } + + void AddOrphanVote(const CGovernanceVote& vote) + { + mapOrphanVotes.Insert(vote.GetHash(), vote); + } + + bool ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception); + + /// Called to indicate a requested object has been received + bool AcceptObjectMessage(const uint256& nHash); + + /// Called to indicate a requested vote has been received + bool AcceptVoteMessage(const uint256& nHash); + + static bool AcceptMessage(const uint256& nHash, hash_s_t& setHash); + + void CheckOrphanVotes(CNode* pfrom, CGovernanceObject& govobj, CGovernanceException& exception); + + void RebuildIndexes(); + + /// Returns MN index, handling the case of index rebuilds + int GetMasternodeIndex(const CTxIn& masternodeVin); + + void RebuildVoteMaps(); + void AddCachedTriggers(); }; +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 * @@ -223,41 +337,140 @@ private: 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 + /// critical section to protect the inner data structures mutable CCriticalSection cs; -public: - - uint256 nHashParent; //parent object, 0 is root - int nRevision; //object revision in the system - int64_t nTime; //time this object was created - uint256 nCollateralHash; //fee-tx - std::string strData; // Data field - can be used for anything + /// Object typecode int nObjectType; - // Masternode info for signed objects + /// parent object, 0 is root + uint256 nHashParent; + + /// object revision in the system + int nRevision; + + /// time this object was created + int64_t nTime; + + /// fee-tx + uint256 nCollateralHash; + + /// Data field - can be used for anything + std::string strData; + + /// Masternode info for signed objects CTxIn vinMasternode; std::vector vchSig; - bool fCachedLocalValidity; // is valid by blockchain + /// is valid by blockchain + bool fCachedLocalValidity; std::string strLocalValidityError; // VARIOUS FLAGS FOR OBJECT / SET VIA MASTERNODE VOTING - bool fCachedFunding; // true == minimum network support has been reached for this object to be funded (doesn't mean it will for sure though) - bool fCachedValid; // 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 fCachedDelete; // true == minimum network support has been reached saying this object should be deleted from the system entirely - bool fCachedEndorsed; // 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 fDirtyCache; // object was updated and cached values should be updated soon - bool fUnparsable; // data field was unparsible, object will be rejected - bool fExpired; // Object is no longer of interest + /// 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; + } + + 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); @@ -272,8 +485,9 @@ public: bool IsCollateralValid(std::string& strError); void UpdateLocalValidity(const CBlockIndex *pCurrentBlockIndex); + void UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex); - int GetObjectType(); + int GetObjectSubtype(); CAmount GetMinCollateralFee(); @@ -281,15 +495,18 @@ public: UniValue GetJSONObject(); void Relay(); + uint256 GetHash(); // GET VOTE COUNT FOR SIGNAL - int GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn); - int GetAbsoluteNoCount(vote_signal_enum_t eVoteSignalIn); - int GetYesCount(vote_signal_enum_t eVoteSignalIn); - int GetNoCount(vote_signal_enum_t eVoteSignalIn); - int GetAbstainCount(vote_signal_enum_t eVoteSignalIn); + 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; // FUNCTIONS FOR DEALING WITH DATA STRING @@ -313,16 +530,33 @@ public: 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(); + }; diff --git a/src/main.cpp b/src/main.cpp index 059dd1259..2f820ee69 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4948,14 +4948,11 @@ bool static AlreadyHave(const CInv& inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) return mapDarksendBroadcastTxes.count(inv.hash); case MSG_GOVERNANCE_OBJECT: - return governance.HaveObjectForHash(inv.hash); - case MSG_GOVERNANCE_OBJECT_VOTE: - return governance.HaveVoteForHash(inv.hash); + return ! governance.ConfirmInventoryRequest(inv); case MSG_MASTERNODE_VERIFY: return mnodeman.mapSeenMasternodeVerification.count(inv.hash); - } // Don't know what it is, just say we already got one @@ -6720,9 +6717,15 @@ bool SendMessages(CNode* pto) // // Message: getdata (non-blocks) // + int64_t nFirst = -1; + if(!pto->mapAskFor.empty()) { + nFirst = (*pto->mapAskFor.begin()).first; + } + LogPrint("net", "SendMessages (mapAskFor) -- before loop: nNow = %d, nFirst = %d\n", nNow, nFirst); while (!pto->fDisconnect && !pto->mapAskFor.empty() && (*pto->mapAskFor.begin()).first <= nNow) { const CInv& inv = (*pto->mapAskFor.begin()).second; + LogPrint("net", "SendMessages (mapAskFor) -- inv = %s peer=%d\n", inv.ToString(), pto->id); if (!AlreadyHave(inv)) { if (fDebug) @@ -6735,6 +6738,7 @@ bool SendMessages(CNode* pto) } } else { //If we're not going to ask, don't expect a response. + LogPrint("net", "SendMessages -- already have inv = %s peer=%d\n", inv.ToString(), pto->id); pto->setAskFor.erase(inv.hash); } pto->mapAskFor.erase(pto->mapAskFor.begin()); diff --git a/src/masternode.cpp b/src/masternode.cpp index e6ac5bac6..196beaa1b 100644 --- a/src/masternode.cpp +++ b/src/masternode.cpp @@ -882,6 +882,14 @@ void CMasternode::UpdateWatchdogVoteTime() */ void CMasternode::FlagGovernanceItemsAsDirty() { + std::map::iterator it = mapGovernanceObjectsVotedOn.begin(); + while(it != mapGovernanceObjectsVotedOn.end()){ + CGovernanceObject *pObj = governance.FindGovernanceObject((*it).first); + + if(pObj) pObj->InvalidateVoteCache(); + ++it; + } + std::vector vecDirty; { std::map::iterator it = mapGovernanceObjectsVotedOn.begin(); diff --git a/src/masternodeman.cpp b/src/masternodeman.cpp index 3d1fdd425..c83dd8737 100644 --- a/src/masternodeman.cpp +++ b/src/masternodeman.cpp @@ -5,6 +5,7 @@ #include "masternodeman.h" #include "activemasternode.h" #include "darksend.h" +#include "governance.h" #include "masternode.h" #include "masternode-payments.h" #include "masternode-sync.h" @@ -38,7 +39,51 @@ struct CompareScoreMN } }; +CMasternodeIndex::CMasternodeIndex() + : nSize(0), + mapIndex(), + mapReverseIndex() +{} + +bool CMasternodeIndex::Get(int nIndex, CTxIn& vinMasternode) const +{ + rindex_m_cit it = mapReverseIndex.find(nIndex); + if(it == mapReverseIndex.end()) { + return false; + } + vinMasternode = it->second; + return true; +} + +int CMasternodeIndex::GetMasternodeIndex(const CTxIn& vinMasternode) const +{ + index_m_cit it = mapIndex.find(vinMasternode); + if(it == mapIndex.end()) { + return -1; + } + return it->second; +} + +void CMasternodeIndex::AddMasternodeVIN(const CTxIn& vinMasternode) +{ + index_m_it it = mapIndex.find(vinMasternode); + if(it != mapIndex.end()) { + return; + } + int nNextIndex = nSize; + mapIndex[vinMasternode] = nNextIndex; + mapReverseIndex[nNextIndex] = vinMasternode; + ++nSize; +} + +void CMasternodeIndex::Clear() +{ + mapIndex.clear(); + mapReverseIndex.clear(); + nSize = 0; +} struct CompareByAddr + { bool operator()(const CMasternode* t1, const CMasternode* t2) const @@ -47,6 +92,33 @@ struct CompareByAddr } }; +void CMasternodeIndex::RebuildIndex() +{ + nSize = mapIndex.size(); + for(index_m_it it = mapIndex.begin(); it != mapIndex.end(); ++it) { + mapReverseIndex[it->second] = it->first; + } +} + +CMasternodeMan::CMasternodeMan() +: cs(), + vMasternodes(), + mAskedUsForMasternodeList(), + mWeAskedForMasternodeList(), + mWeAskedForMasternodeListEntry(), + nLastIndexRebuildTime(0), + indexMasternodes(), + indexMasternodesOld(), + fIndexRebuilt(false), + fMasternodesAdded(false), + fMasternodesRemoved(false), + vecDirtyGovernanceObjectHashes(), + nLastWatchdogVoteTime(0), + mapSeenMasternodeBroadcast(), + mapSeenMasternodePing(), + nDsqCount(0) +{} + bool CMasternodeMan::Add(CMasternode &mn) { LOCK(cs); @@ -59,13 +131,15 @@ bool CMasternodeMan::Add(CMasternode &mn) LogPrint("masternode", "CMasternodeMan::Add -- Adding new Masternode: addr=%s, %i now\n", mn.addr.ToString(), size() + 1); mn.nTimeLastWatchdogVote = mn.sigTime; vMasternodes.push_back(mn); + indexMasternodes.AddMasternodeVIN(mn.vin); + fMasternodesAdded = true; return true; } return false; } -void CMasternodeMan::AskForMN(CNode* pnode, CTxIn &vin) +void CMasternodeMan::AskForMN(CNode* pnode, const CTxIn &vin) { if(!pnode) return; @@ -99,106 +173,117 @@ void CMasternodeMan::CheckAndRemove(bool fForceExpiredRemoval) Check(); - LOCK(cs); + { + LOCK(cs); - // Remove inactive and outdated masternodes - std::vector::iterator it = vMasternodes.begin(); - while(it != vMasternodes.end()) { - bool fRemove = // If it's marked to be removed from the list by CMasternode::Check for whatever reason ... - (*it).nActiveState == CMasternode::MASTERNODE_REMOVE || - // or collateral was spent ... - (*it).nActiveState == CMasternode::MASTERNODE_OUTPOINT_SPENT || - // or we were asked to remove exired entries ... - (fForceExpiredRemoval && (*it).nActiveState == CMasternode::MASTERNODE_EXPIRED); + // Remove inactive and outdated masternodes + std::vector::iterator it = vMasternodes.begin(); + while(it != vMasternodes.end()) { + bool fRemove = // If it's marked to be removed from the list by CMasternode::Check for whatever reason ... + (*it).nActiveState == CMasternode::MASTERNODE_REMOVE || + // or collateral was spent ... + (*it).nActiveState == CMasternode::MASTERNODE_OUTPOINT_SPENT || + // or we were asked to remove exired entries ... + (fForceExpiredRemoval && (*it).nActiveState == CMasternode::MASTERNODE_EXPIRED); - if (fRemove) { - LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing Masternode: %s addr=%s %i now\n", (*it).GetStatus(), (*it).addr.ToString(), size() - 1); + if (fRemove) { + LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing Masternode: %s addr=%s %i now\n", (*it).GetStatus(), (*it).addr.ToString(), size() - 1); - // erase all of the broadcasts we've seen from this txin, ... - mapSeenMasternodeBroadcast.erase(CMasternodeBroadcast(*it).GetHash()); - // allow us to ask for this masternode again if we see another ping ... - mWeAskedForMasternodeListEntry.erase((*it).vin.prevout); + // erase all of the broadcasts we've seen from this txin, ... + mapSeenMasternodeBroadcast.erase(CMasternodeBroadcast(*it).GetHash()); + // allow us to ask for this masternode again if we see another ping ... + mWeAskedForMasternodeListEntry.erase((*it).vin.prevout); - // and finally remove it from the list - it = vMasternodes.erase(it); - } else { - ++it; + // and finally remove it from the list + it = vMasternodes.erase(it); + fMasternodesRemoved = true; + } else { + ++it; + } + } + + // check who's asked for the Masternode list + std::map::iterator it1 = mAskedUsForMasternodeList.begin(); + while(it1 != mAskedUsForMasternodeList.end()){ + if((*it1).second < GetTime()) { + mAskedUsForMasternodeList.erase(it1++); + } else { + ++it1; + } + } + + // check who we asked for the Masternode list + it1 = mWeAskedForMasternodeList.begin(); + while(it1 != mWeAskedForMasternodeList.end()){ + if((*it1).second < GetTime()){ + mWeAskedForMasternodeList.erase(it1++); + } else { + ++it1; + } + } + + // check which Masternodes we've asked for + std::map::iterator it2 = mWeAskedForMasternodeListEntry.begin(); + while(it2 != mWeAskedForMasternodeListEntry.end()){ + if((*it2).second < GetTime()){ + mWeAskedForMasternodeListEntry.erase(it2++); + } else { + ++it2; + } + } + + std::map::iterator itv1 = mWeAskedForVerification.begin(); + while(itv1 != mWeAskedForVerification.end()){ + if(itv1->second.nBlockHeight < pCurrentBlockIndex->nHeight - MAX_POSE_BLOCKS) { + mWeAskedForVerification.erase(itv1++); + } else { + ++itv1; + } + } + + // remove expired mapSeenMasternodeBroadcast + std::map::iterator it3 = mapSeenMasternodeBroadcast.begin(); + while(it3 != mapSeenMasternodeBroadcast.end()){ + if((*it3).second.lastPing.sigTime < GetTime() - MASTERNODE_REMOVAL_SECONDS*2){ + LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing expired Masternode broadcast: hash=%s\n", (*it3).second.GetHash().ToString()); + mapSeenMasternodeBroadcast.erase(it3++); + } else { + ++it3; + } + } + + // remove expired mapSeenMasternodePing + std::map::iterator it4 = mapSeenMasternodePing.begin(); + while(it4 != mapSeenMasternodePing.end()){ + if((*it4).second.sigTime < GetTime() - MASTERNODE_REMOVAL_SECONDS*2){ + LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing expired Masternode ping: hash=%s\n", (*it4).second.GetHash().ToString()); + mapSeenMasternodePing.erase(it4++); + } else { + ++it4; + } + } + + // remove expired mapSeenMasternodeVerification + std::map::iterator itv2 = mapSeenMasternodeVerification.begin(); + while(itv2 != mapSeenMasternodeVerification.end()){ + if((*itv2).second.nBlockHeight < pCurrentBlockIndex->nHeight - MAX_POSE_BLOCKS){ + LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing expired Masternode verification: hash=%s\n", (*itv2).first.ToString()); + mapSeenMasternodeVerification.erase(itv2++); + } else { + ++itv2; + } + } + + LogPrintf("CMasternodeMan::CheckAndRemove -- %s\n", ToString()); + + if(fMasternodesRemoved) { + CheckAndRebuildMasternodeIndex(); } } - // check who's asked for the Masternode list - std::map::iterator it1 = mAskedUsForMasternodeList.begin(); - while(it1 != mAskedUsForMasternodeList.end()){ - if((*it1).second < GetTime()) { - mAskedUsForMasternodeList.erase(it1++); - } else { - ++it1; - } + if(fMasternodesRemoved) { + NotifyMasternodeUpdates(); } - - // check who we asked for the Masternode list - it1 = mWeAskedForMasternodeList.begin(); - while(it1 != mWeAskedForMasternodeList.end()){ - if((*it1).second < GetTime()){ - mWeAskedForMasternodeList.erase(it1++); - } else { - ++it1; - } - } - - // check which Masternodes we've asked for - std::map::iterator it2 = mWeAskedForMasternodeListEntry.begin(); - while(it2 != mWeAskedForMasternodeListEntry.end()){ - if((*it2).second < GetTime()){ - mWeAskedForMasternodeListEntry.erase(it2++); - } else { - ++it2; - } - } - - std::map::iterator itv1 = mWeAskedForVerification.begin(); - while(itv1 != mWeAskedForVerification.end()){ - if(itv1->second.nBlockHeight < pCurrentBlockIndex->nHeight - MAX_POSE_BLOCKS) { - mWeAskedForVerification.erase(itv1++); - } else { - ++itv1; - } - } - - // remove expired mapSeenMasternodeBroadcast - std::map::iterator it3 = mapSeenMasternodeBroadcast.begin(); - while(it3 != mapSeenMasternodeBroadcast.end()){ - if((*it3).second.lastPing.sigTime < GetTime() - MASTERNODE_REMOVAL_SECONDS*2){ - LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing expired Masternode broadcast: hash=%s\n", (*it3).second.GetHash().ToString()); - mapSeenMasternodeBroadcast.erase(it3++); - } else { - ++it3; - } - } - - // remove expired mapSeenMasternodePing - std::map::iterator it4 = mapSeenMasternodePing.begin(); - while(it4 != mapSeenMasternodePing.end()){ - if((*it4).second.sigTime < GetTime() - MASTERNODE_REMOVAL_SECONDS*2){ - LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing expired Masternode ping: hash=%s\n", (*it4).second.GetHash().ToString()); - mapSeenMasternodePing.erase(it4++); - } else { - ++it4; - } - } - - // remove expired mapSeenMasternodeVerification - std::map::iterator itv2 = mapSeenMasternodeVerification.begin(); - while(itv2 != mapSeenMasternodeVerification.end()){ - if((*itv2).second.nBlockHeight < pCurrentBlockIndex->nHeight - MAX_POSE_BLOCKS){ - LogPrint("masternode", "CMasternodeMan::CheckAndRemove -- Removing expired Masternode verification: hash=%s\n", (*itv2).first.ToString()); - mapSeenMasternodeVerification.erase(itv2++); - } else { - ++itv2; - } - } - - LogPrintf("CMasternodeMan::CheckAndRemove -- %s\n", ToString()); } void CMasternodeMan::Clear() @@ -610,20 +695,25 @@ void CMasternodeMan::ProcessMessage(CNode* pfrom, std::string& strCommand, CData if (strCommand == NetMsgType::MNANNOUNCE) { //Masternode Broadcast - CMasternodeBroadcast mnb; - vRecv >> mnb; + { + LOCK(cs); - int nDos = 0; + CMasternodeBroadcast mnb; + vRecv >> mnb; - if (CheckMnbAndUpdateMasternodeList(mnb, nDos)) { - // use announced Masternode as a peer - addrman.Add(CAddress(mnb.addr), pfrom->addr, 2*60*60); - } else if(nDos > 0) { - Misbehaving(pfrom->GetId(), nDos); + int nDos = 0; + + if (CheckMnbAndUpdateMasternodeList(mnb, nDos)) { + // use announced Masternode as a peer + addrman.Add(CAddress(mnb.addr), pfrom->addr, 2*60*60); + } else if(nDos > 0) { + Misbehaving(pfrom->GetId(), nDos); + } + } + if(fMasternodesAdded) { + NotifyMasternodeUpdates(); } - } else if (strCommand == NetMsgType::MNPING) { //Masternode Ping - // ignore masternode pings until masternode list is synced if (!masternodeSync.IsMasternodeListSynced()) return; @@ -657,7 +747,6 @@ void CMasternodeMan::ProcessMessage(CNode* pfrom, std::string& strCommand, CData AskForMN(pfrom, mnp.vin); } else if (strCommand == NetMsgType::DSEG) { //Get Masternode list or specific entry - // 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. @@ -722,6 +811,8 @@ void CMasternodeMan::ProcessMessage(CNode* pfrom, std::string& strCommand, CData } else if (strCommand == NetMsgType::MNVERIFY) { // Masternode Verify + LOCK(cs); + CMasternodeVerification mnv; vRecv >> mnv; @@ -1290,6 +1381,32 @@ void CMasternodeMan::UpdateLastPaid(const CBlockIndex *pindex) IsFirstRun = !masternodeSync.IsWinnersListSynced(); } +void CMasternodeMan::CheckAndRebuildMasternodeIndex() +{ + LOCK(cs); + + if(GetTime() - nLastIndexRebuildTime < MIN_INDEX_REBUILD_TIME) { + return; + } + + if(indexMasternodes.GetSize() <= MAX_EXPECTED_INDEX_SIZE) { + return; + } + + if(indexMasternodes.GetSize() <= int(vMasternodes.size())) { + return; + } + + indexMasternodesOld = indexMasternodes; + indexMasternodes.Clear(); + for(size_t i = 0; i < vMasternodes.size(); ++i) { + indexMasternodes.AddMasternodeVIN(vMasternodes[i].vin); + } + + fIndexRebuilt = true; + nLastIndexRebuildTime = GetTime(); +} + void CMasternodeMan::UpdateWatchdogVoteTime(const CTxIn& vin) { LOCK(cs); @@ -1406,3 +1523,26 @@ void CMasternodeMan::UpdatedBlockTip(const CBlockIndex *pindex) UpdateLastPaid(pindex); } } + +void CMasternodeMan::NotifyMasternodeUpdates() +{ + // Avoid double locking + bool fMasternodesAddedLocal = false; + bool fMasternodesRemovedLocal = false; + { + LOCK(cs); + fMasternodesAddedLocal = fMasternodesAdded; + fMasternodesRemovedLocal = fMasternodesRemoved; + } + + if(fMasternodesAddedLocal) { + governance.CheckMasternodeOrphanVotes(); + } + if(fMasternodesRemovedLocal) { + governance.UpdateCachesAndClean(); + } + + LOCK(cs); + fMasternodesAdded = false; + fMasternodesRemoved = false; +} diff --git a/src/masternodeman.h b/src/masternodeman.h index 7b5838e06..398eee766 100644 --- a/src/masternodeman.h +++ b/src/masternodeman.h @@ -14,9 +14,85 @@ class CMasternodeMan; extern CMasternodeMan mnodeman; +/** + * Provides a forward and reverse index between MN vin's and integers. + * + * This mapping is normally add-only and is expected to be permanent + * It is only rebuilt if the size of the index exceeds the expected maximum number + * of MN's and the current number of known MN's. + * + * The external interface to this index is provided via delegation by CMasternodeMan + */ +class CMasternodeIndex +{ +public: // Types + typedef std::map index_m_t; + + typedef index_m_t::iterator index_m_it; + + typedef index_m_t::const_iterator index_m_cit; + + typedef std::map rindex_m_t; + + typedef rindex_m_t::iterator rindex_m_it; + + typedef rindex_m_t::const_iterator rindex_m_cit; + +private: + int nSize; + + index_m_t mapIndex; + + rindex_m_t mapReverseIndex; + +public: + CMasternodeIndex(); + + int GetSize() const { + return nSize; + } + + /// Retrieve masternode vin by index + bool Get(int nIndex, CTxIn& vinMasternode) const; + + /// Get index of a masternode vin + int GetMasternodeIndex(const CTxIn& vinMasternode) const; + + void AddMasternodeVIN(const CTxIn& vinMasternode); + + void Clear(); + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) + { + READWRITE(mapIndex); + if(ser_action.ForRead()) { + RebuildIndex(); + } + } + +private: + void RebuildIndex(); + +}; + class CMasternodeMan { +public: + typedef std::map index_m_t; + + typedef index_m_t::iterator index_m_it; + + typedef index_m_t::const_iterator index_m_cit; + private: + static const int MAX_EXPECTED_INDEX_SIZE = 30000; + + /// Only allow 1 index rebuild per hour + static const int64_t MIN_INDEX_REBUILD_TIME = 3600; + static const std::string SERIALIZATION_VERSION_STRING; static const int DSEG_UPDATE_SECONDS = 3 * 60 * 60; @@ -45,6 +121,21 @@ private: // who we asked for the masternode verification std::map mWeAskedForVerification; + int64_t nLastIndexRebuildTime; + + CMasternodeIndex indexMasternodes; + + CMasternodeIndex indexMasternodesOld; + + /// Set when index has been rebuilt, clear when read + bool fIndexRebuilt; + + /// Set when masternodes are added, cleared when CGovernanceManager is notified + bool fMasternodesAdded; + + /// Set when masternodes are removed, cleared when CGovernanceManager is notified + bool fMasternodesRemoved; + std::vector vecDirtyGovernanceObjectHashes; int64_t nLastWatchdogVoteTime; @@ -62,8 +153,6 @@ public: int64_t nDsqCount; - CMasternodeMan() : nLastWatchdogVoteTime(0), nDsqCount(0) {} - ADD_SERIALIZE_METHODS; template @@ -87,16 +176,19 @@ public: READWRITE(mapSeenMasternodeBroadcast); READWRITE(mapSeenMasternodePing); + READWRITE(indexMasternodes); if(ser_action.ForRead() && (strVersion != SERIALIZATION_VERSION_STRING)) { Clear(); } } + CMasternodeMan(); + /// Add an entry bool Add(CMasternode &mn); /// Ask (source) node for mnb - void AskForMN(CNode *pnode, CTxIn &vin); + void AskForMN(CNode *pnode, const CTxIn &vin); /// Check all Masternodes void Check(); @@ -128,6 +220,49 @@ public: bool Get(const CPubKey& pubKeyMasternode, CMasternode& masternode); bool Get(const CTxIn& vin, CMasternode& masternode); + /// Retrieve masternode vin by index + bool Get(int nIndex, CTxIn& vinMasternode, bool& fIndexRebuiltOut) { + LOCK(cs); + fIndexRebuiltOut = fIndexRebuilt; + return indexMasternodes.Get(nIndex, vinMasternode); + } + + bool GetIndexRebuiltFlag() { + LOCK(cs); + return fIndexRebuilt; + } + + /// Get index of a masternode vin + int GetMasternodeIndex(const CTxIn& vinMasternode) { + LOCK(cs); + return indexMasternodes.GetMasternodeIndex(vinMasternode); + } + + /// Get old index of a masternode vin + int GetMasternodeIndexOld(const CTxIn& vinMasternode) { + LOCK(cs); + return indexMasternodesOld.GetMasternodeIndex(vinMasternode); + } + + /// Get masternode VIN for an old index value + bool GetMasternodeVinForIndexOld(int nMasternodeIndex, CTxIn& vinMasternodeOut) { + LOCK(cs); + return indexMasternodesOld.Get(nMasternodeIndex, vinMasternodeOut); + } + + /// Get index of a masternode vin, returning rebuild flag + int GetMasternodeIndex(const CTxIn& vinMasternode, bool& fIndexRebuiltOut) { + LOCK(cs); + fIndexRebuiltOut = fIndexRebuilt; + return indexMasternodes.GetMasternodeIndex(vinMasternode); + } + + void ClearOldMasternodeIndex() { + LOCK(cs); + indexMasternodesOld.Clear(); + fIndexRebuilt = false; + } + bool Has(const CTxIn& vin); masternode_info_t GetMasternodeInfo(const CTxIn& vin); @@ -173,6 +308,8 @@ public: void UpdateLastPaid(const CBlockIndex *pindex); + void CheckAndRebuildMasternodeIndex(); + void AddDirtyGovernanceObjectHash(const uint256& nHash) { LOCK(cs); @@ -202,6 +339,13 @@ public: void SetMasternodeLastPing(const CTxIn& vin, const CMasternodePing& mnp); void UpdatedBlockTip(const CBlockIndex *pindex); + + /** + * Called to notify CGovernanceManager that the masternode index has been updated. + * Must be called while not holding the CMasternodeMan::cs mutex + */ + void NotifyMasternodeUpdates(); + }; #endif diff --git a/src/qt/masternodelist.cpp b/src/qt/masternodelist.cpp index dbe4a081d..fe46c900c 100644 --- a/src/qt/masternodelist.cpp +++ b/src/qt/masternodelist.cpp @@ -106,6 +106,7 @@ void MasternodeList::StartAlias(std::string strAlias) strStatusHtml += "
Successfully started masternode."; mnodeman.UpdateMasternodeList(mnb); mnb.Relay(); + mnodeman.NotifyMasternodeUpdates(); } else { strStatusHtml += "
Failed to start masternode.
Error: " + strError; } @@ -142,6 +143,7 @@ void MasternodeList::StartAll(std::string strCommand) nCountSuccessful++; mnodeman.UpdateMasternodeList(mnb); mnb.Relay(); + mnodeman.NotifyMasternodeUpdates(); } else { nCountFailed++; strFailedHtml += "\nFailed to start " + mne.getAlias() + ". Error: " + strError; diff --git a/src/rpcgovernance.cpp b/src/rpcgovernance.cpp index 53f925569..d3fac8494 100644 --- a/src/rpcgovernance.cpp +++ b/src/rpcgovernance.cpp @@ -283,16 +283,15 @@ UniValue gobject(const UniValue& params, bool fHelp) return returnObj; } - std::string strError = ""; - if(governance.AddOrUpdateVote(vote, NULL, strError)) { - governance.AddSeenVote(vote.GetHash(), SEEN_OBJECT_IS_VALID); - vote.Relay(); + CGovernanceException exception; + if(governance.ProcessVote(vote, exception)) { success++; statusObj.push_back(Pair("result", "success")); - } else { + } + else { failed++; statusObj.push_back(Pair("result", "failed")); - statusObj.push_back(Pair("errorMessage", strError.c_str())); + statusObj.push_back(Pair("errorMessage", exception.GetMessage())); } resultsObj.push_back(Pair("dash.conf", statusObj)); @@ -386,15 +385,15 @@ UniValue gobject(const UniValue& params, bool fHelp) continue; } - if(governance.AddOrUpdateVote(vote, NULL, strError)) { - governance.AddSeenVote(vote.GetHash(), SEEN_OBJECT_IS_VALID); - vote.Relay(); + CGovernanceException exception; + if(governance.ProcessVote(vote, exception)) { success++; statusObj.push_back(Pair("result", "success")); - } else { + } + else { failed++; statusObj.push_back(Pair("result", "failed")); - statusObj.push_back(Pair("errorMessage", strError.c_str())); + statusObj.push_back(Pair("errorMessage", exception.GetMessage())); } resultsObj.push_back(Pair(mne.getAlias(), statusObj)); @@ -511,15 +510,15 @@ UniValue gobject(const UniValue& params, bool fHelp) // UPDATE LOCAL DATABASE WITH NEW OBJECT SETTINGS - if(governance.AddOrUpdateVote(vote, NULL, strError)) { - governance.AddSeenVote(vote.GetHash(), SEEN_OBJECT_IS_VALID); - vote.Relay(); + CGovernanceException exception; + if(governance.ProcessVote(vote, exception)) { success++; statusObj.push_back(Pair("result", "success")); - } else { + } + else { failed++; statusObj.push_back(Pair("result", "failed")); - statusObj.push_back(Pair("errorMessage", strError.c_str())); + statusObj.push_back(Pair("errorMessage", exception.GetMessage())); } resultsObj.push_back(Pair(mne.getAlias(), statusObj)); @@ -573,13 +572,13 @@ UniValue gobject(const UniValue& params, bool fHelp) BOOST_FOREACH(CGovernanceObject* pGovObj, objs) { // IF WE HAVE A SPECIFIC NODE REQUESTED TO VOTE, DO THAT - if(strShow == "valid" && !pGovObj->fCachedValid) continue; + if(strShow == "valid" && !pGovObj->IsSetCachedValid()) continue; UniValue bObj(UniValue::VOBJ); bObj.push_back(Pair("DataHex", pGovObj->GetDataAsHex())); bObj.push_back(Pair("DataString", pGovObj->GetDataAsString())); bObj.push_back(Pair("Hash", pGovObj->GetHash().ToString())); - bObj.push_back(Pair("CollateralHash", pGovObj->nCollateralHash.ToString())); + bObj.push_back(Pair("CollateralHash", pGovObj->GetCollateralHash().ToString())); // REPORT STATUS FOR FUNDING VOTES SPECIFICALLY bObj.push_back(Pair("AbsoluteYesCount", pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING))); @@ -591,10 +590,10 @@ UniValue gobject(const UniValue& params, bool fHelp) std::string strError = ""; bObj.push_back(Pair("fBlockchainValidity", pGovObj->IsValidLocally(pindex , strError, false))); bObj.push_back(Pair("IsValidReason", strError.c_str())); - bObj.push_back(Pair("fCachedValid", pGovObj->fCachedValid)); - bObj.push_back(Pair("fCachedFunding", pGovObj->fCachedFunding)); - bObj.push_back(Pair("fCachedDelete", pGovObj->fCachedDelete)); - bObj.push_back(Pair("fCachedEndorsed", pGovObj->fCachedEndorsed)); + bObj.push_back(Pair("fCachedValid", pGovObj->IsSetCachedValid())); + bObj.push_back(Pair("fCachedFunding", pGovObj->IsSetCachedFunding())); + bObj.push_back(Pair("fCachedDelete", pGovObj->IsSetCachedDelete())); + bObj.push_back(Pair("fCachedEndorsed", pGovObj->IsSetCachedEndorsed())); objResult.push_back(Pair(pGovObj->GetHash().ToString(), bObj)); } @@ -625,7 +624,7 @@ UniValue gobject(const UniValue& params, bool fHelp) objResult.push_back(Pair("DataHex", pGovObj->GetDataAsHex())); objResult.push_back(Pair("DataString", pGovObj->GetDataAsString())); objResult.push_back(Pair("Hash", pGovObj->GetHash().ToString())); - objResult.push_back(Pair("CollateralHash", pGovObj->nCollateralHash.ToString())); + objResult.push_back(Pair("CollateralHash", pGovObj->GetCollateralHash().ToString())); // SHOW (MUCH MORE) INFORMATION ABOUT VOTES FOR GOVERNANCE OBJECT (THAN LIST/DIFF ABOVE) // -- FUNDING VOTING RESULTS @@ -665,11 +664,10 @@ UniValue gobject(const UniValue& params, bool fHelp) std::string strError = ""; objResult.push_back(Pair("fLocalValidity", pGovObj->IsValidLocally(chainActive.Tip(), strError, false))); objResult.push_back(Pair("IsValidReason", strError.c_str())); - objResult.push_back(Pair("fCachedValid", pGovObj->fCachedValid)); - objResult.push_back(Pair("fCachedFunding", pGovObj->fCachedFunding)); - objResult.push_back(Pair("fCachedDelete", pGovObj->fCachedDelete)); - objResult.push_back(Pair("fCachedEndorsed", pGovObj->fCachedEndorsed)); - + objResult.push_back(Pair("fCachedValid", pGovObj->IsSetCachedValid())); + objResult.push_back(Pair("fCachedFunding", pGovObj->IsSetCachedFunding())); + objResult.push_back(Pair("fCachedDelete", pGovObj->IsSetCachedDelete())); + objResult.push_back(Pair("fCachedEndorsed", pGovObj->IsSetCachedEndorsed())); return objResult; } @@ -701,9 +699,9 @@ UniValue gobject(const UniValue& params, bool fHelp) // GET MATCHING VOTES BY HASH, THEN SHOW USERS VOTE INFORMATION - std::vector vecVotes = governance.GetMatchingVotes(hash); - BOOST_FOREACH(CGovernanceVote* pVote, vecVotes) { - bResult.push_back(Pair(pVote->GetHash().ToString(), pVote->ToString())); + std::vector vecVotes = governance.GetMatchingVotes(hash); + BOOST_FOREACH(CGovernanceVote vote, vecVotes) { + bResult.push_back(Pair(vote.GetHash().ToString(), vote.ToString())); } return bResult; @@ -764,13 +762,12 @@ UniValue voteraw(const UniValue& params, bool fHelp) throw JSONRPCError(RPC_INTERNAL_ERROR, "Failure to verify vote."); } - std::string strError = ""; - if(governance.AddOrUpdateVote(vote, NULL, strError)) { - governance.AddSeenVote(vote.GetHash(), SEEN_OBJECT_IS_VALID); - vote.Relay(); + CGovernanceException exception; + if(governance.ProcessVote(vote, exception)) { return "Voted successfully"; - } else { - throw JSONRPCError(RPC_INTERNAL_ERROR, "Error voting : " + strError); + } + else { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Error voting : " + exception.GetMessage()); } } diff --git a/src/rpcmasternode.cpp b/src/rpcmasternode.cpp index ce32054b7..cc10ee166 100644 --- a/src/rpcmasternode.cpp +++ b/src/rpcmasternode.cpp @@ -272,6 +272,7 @@ UniValue masternode(const UniValue& params, bool fHelp) } else { statusObj.push_back(Pair("errorMessage", strError)); } + mnodeman.NotifyMasternodeUpdates(); break; } } @@ -328,6 +329,7 @@ UniValue masternode(const UniValue& params, bool fHelp) resultsObj.push_back(Pair("status", statusObj)); } + mnodeman.NotifyMasternodeUpdates(); UniValue returnObj(UniValue::VOBJ); returnObj.push_back(Pair("overall", strprintf("Successfully started %d masternodes, failed to start %d, total %d", nSuccessful, nFailed, nSuccessful + nFailed))); @@ -781,6 +783,7 @@ UniValue masternodebroadcast(const UniValue& params, bool fHelp) mnb.Relay(); fResult = true; } + mnodeman.NotifyMasternodeUpdates(); } else fResult = false; if(fResult) { diff --git a/src/serialize.h b/src/serialize.h index 41a51d237..649fe6972 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -886,6 +887,39 @@ void Unserialize(Stream& is, std::set& m, int nType, int nVersion) } } +/** + * list + */ +template +unsigned int GetSerializeSize(const std::list& l, int nType, int nVersion) +{ + unsigned int nSize = GetSizeOfCompactSize(l.size()); + for (typename std::list::const_iterator it = l.begin(); it != l.end(); ++it) + nSize += GetSerializeSize((*it), nType, nVersion); + return nSize; +} + +template +void Serialize(Stream& os, const std::list& l, int nType, int nVersion) +{ + WriteCompactSize(os, l.size()); + for (typename std::list::const_iterator it = l.begin(); it != l.end(); ++it) + Serialize(os, (*it), nType, nVersion); +} + +template +void Unserialize(Stream& is, std::list& l, int nType, int nVersion) +{ + l.clear(); + unsigned int nSize = ReadCompactSize(is); + for (unsigned int i = 0; i < nSize; i++) + { + T val; + Unserialize(is, val, nType, nVersion); + l.push_back(val); + } +} + /** diff --git a/src/test/cachemap_tests.cpp b/src/test/cachemap_tests.cpp new file mode 100644 index 000000000..5a9a0a3b2 --- /dev/null +++ b/src/test/cachemap_tests.cpp @@ -0,0 +1,127 @@ +// Copyright (c) 2014-2016 The Dash Core developers + +#include "cachemap.h" + +#include "test/test_dash.h" + +#include + +BOOST_FIXTURE_TEST_SUITE(cachemap_tests, BasicTestingSetup) + +bool Compare(const CacheMap& map1, const CacheMap& map2 ) +{ + if(map1.GetMaxSize() != map2.GetMaxSize()) { + return false; + } + + if(map1.GetSize() != map2.GetSize()) { + return false; + } + + const CacheMap::list_t& items1 = map1.GetItemList(); + for(CacheMap::list_cit it = items1.begin(); it != items1.end(); ++it) { + if(!map2.HasKey(it->key)) { + return false; + } + int val = 0; + if(!map2.Get(it->key, val)) { + return false; + } + if(it->value != val) { + return false; + } + } + + const CacheMap::list_t& items2 = map2.GetItemList(); + for(CacheMap::list_cit it = items2.begin(); it != items2.end(); ++it) { + if(!map1.HasKey(it->key)) { + return false; + } + int val = 0; + if(!map1.Get(it->key, val)) { + return false; + } + if(it->value != val) { + return false; + } + } + + return true; +} + +BOOST_AUTO_TEST_CASE(cachemap_test) +{ + // create a CacheMap limited to 10 items + CacheMap mapTest1(10); + + // check that the max size is 10 + BOOST_CHECK(mapTest1.GetMaxSize() == 10); + + // check that the size is 0 + BOOST_CHECK(mapTest1.GetSize() == 0); + + // insert (-1, -1) + mapTest1.Insert(-1, -1); + + // make sure that the size is updated + BOOST_CHECK(mapTest1.GetSize() == 1); + + // make sure the map contains the key + BOOST_CHECK(mapTest1.HasKey(-1) == true); + + // add 10 items + for(int i = 0; i < 10; ++i) { + mapTest1.Insert(i, i); + } + + // check that the size is 10 + BOOST_CHECK(mapTest1.GetSize() == 10); + + // check that the map contains the expected items + for(int i = 0; i < 10; ++i) { + int nVal = 0; + BOOST_CHECK(mapTest1.Get(i, nVal) == true); + BOOST_CHECK(nVal == i); + } + + // check that the map no longer contains the first item + BOOST_CHECK(mapTest1.HasKey(-1) == false); + + // erase an item + mapTest1.Erase(5); + + // check the size + BOOST_CHECK(mapTest1.GetSize() == 9); + + // check that the map no longer contains the item + BOOST_CHECK(mapTest1.HasKey(5) == false); + + // check that the map contains the expected items + int expected[] = { 0, 1, 2, 3, 4, 6, 7, 8, 9 }; + for(size_t i = 0; i < 9; ++i) { + int nVal = 0; + int eVal = expected[i]; + BOOST_CHECK(mapTest1.Get(eVal, nVal) == true); + BOOST_CHECK(nVal == eVal); + } + + // test serialization + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << mapTest1; + + CacheMap mapTest2; + ss >> mapTest2; + + BOOST_CHECK(Compare(mapTest1, mapTest2)); + + // test copy constructor + CacheMap mapTest3(mapTest1); + BOOST_CHECK(Compare(mapTest1, mapTest3)); + + // test assignment operator + CacheMap mapTest4; + mapTest4 = mapTest1; + BOOST_CHECK(Compare(mapTest1, mapTest4)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/cachemultimap_tests.cpp b/src/test/cachemultimap_tests.cpp new file mode 100644 index 000000000..66d694d6b --- /dev/null +++ b/src/test/cachemultimap_tests.cpp @@ -0,0 +1,176 @@ +// Copyright (c) 2014-2016 The Dash Core developers + +#include "cachemultimap.h" + +#include "test/test_dash.h" + +#include +#include + +#include + +BOOST_FIXTURE_TEST_SUITE(cachemultimap_tests, BasicTestingSetup) + +void DumpMap(const CacheMultiMap& map) +{ + const CacheMultiMap::list_t& listItems = map.GetItemList(); + for(CacheMultiMap::list_cit it = listItems.begin(); it != listItems.end(); ++it) { + const CacheItem& item = *it; + std::cout << item.key << " : " << item.value << std::endl; + } +} + +bool Compare(const CacheMultiMap& map1, const CacheMultiMap& map2 ) +{ + if(map1.GetMaxSize() != map2.GetMaxSize()) { + std::cout << "Compare returning false: max size mismatch" << std::endl; + return false; + } + + if(map1.GetSize() != map2.GetSize()) { + std::cout << "Compare returning false: size mismatch" << std::endl; + return false; + } + + const CacheMultiMap::list_t& items1 = map1.GetItemList(); + const CacheMultiMap::list_t& items2 = map2.GetItemList(); + CacheMultiMap::list_cit it2 = items2.begin(); + for(CacheMultiMap::list_cit it1 = items1.begin(); it1 != items1.end(); ++it1) { + const CacheItem& item1 = *it1; + const CacheItem& item2 = *it2; + if(item1.key != item2.key) { + return false; + } + if(item1.value != item2.value) { + return false; + } + ++it2; + } + + return true; +} + +bool CheckExpected(const CacheMultiMap& map, int* expected, CacheMultiMap::size_type nSize) +{ + if(map.GetSize() != nSize) { + return false; + } + for(CacheMultiMap::size_type i = 0; i < nSize; ++i) { + int nVal = 0; + int eVal = expected[i]; + if(!map.Get(eVal, nVal)) { + return false; + } + if(nVal != eVal) { + return false; + } + } + return true; +} + +BOOST_AUTO_TEST_CASE(cachemultimap_test) +{ + // create a CacheMultiMap limited to 10 items + CacheMultiMap mapTest1(10); + + // check that the max size is 10 + BOOST_CHECK(mapTest1.GetMaxSize() == 10); + + // check that the size is 0 + BOOST_CHECK(mapTest1.GetSize() == 0); + + // insert (-1, -1) + mapTest1.Insert(-1, -1); + + // make sure that the size is updated + BOOST_CHECK(mapTest1.GetSize() == 1); + + // make sure the map contains the key + BOOST_CHECK(mapTest1.HasKey(-1) == true); + + // add 10 items + for(int i = 0; i < 10; ++i) { + mapTest1.Insert(i, i); + } + + // check that the size is 10 + BOOST_CHECK(mapTest1.GetSize() == 10); + + // check that the map contains the expected items + for(int i = 0; i < 10; ++i) { + int nVal = 0; + BOOST_CHECK(mapTest1.Get(i, nVal) == true); + BOOST_CHECK(nVal == i); + } + + // check that the map no longer contains the first item + BOOST_CHECK(mapTest1.HasKey(-1) == false); + + // erase an item + mapTest1.Erase(5); + + // check the size + BOOST_CHECK(mapTest1.GetSize() == 9); + + // check that the map no longer contains the item + BOOST_CHECK(mapTest1.HasKey(5) == false); + + // check that the map contains the expected items + int expected[] = { 0, 1, 2, 3, 4, 6, 7, 8, 9 }; + BOOST_CHECK(CheckExpected(mapTest1, expected, 9 ) == true); + + // add multiple items for the same key + mapTest1.Insert(5, 2); + mapTest1.Insert(5, 1); + mapTest1.Insert(5, 4); + + // check the size + BOOST_CHECK(mapTest1.GetSize() == 10); + + // check that 2 keys have been removed + BOOST_CHECK(mapTest1.HasKey(0) == false); + BOOST_CHECK(mapTest1.HasKey(1) == false); + BOOST_CHECK(mapTest1.HasKey(2) == true); + + // check multiple values + std::vector vecVals; + BOOST_CHECK(mapTest1.GetAll(5, vecVals) == true); + BOOST_CHECK(vecVals.size() == 3); + BOOST_CHECK(vecVals[0] == 1); + BOOST_CHECK(vecVals[1] == 2); + BOOST_CHECK(vecVals[2] == 4); + +// std::cout << "mapTest1 dump:" << std::endl; +// DumpMap(mapTest1); + + // test serialization + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << mapTest1; + + CacheMultiMap mapTest2; + ss >> mapTest2; + +// std::cout << "mapTest2 dump:" << std::endl; +// DumpMap(mapTest2); + + // check multiple values + std::vector vecVals2; + BOOST_CHECK(mapTest2.GetAll(5, vecVals2) == true); + BOOST_CHECK(vecVals2.size() == 3); + BOOST_CHECK(vecVals2[0] == 1); + BOOST_CHECK(vecVals2[1] == 2); + BOOST_CHECK(vecVals2[2] == 4); + + BOOST_CHECK(Compare(mapTest1, mapTest2)); + + // test copy constructor + CacheMultiMap mapTest3(mapTest1); + BOOST_CHECK(Compare(mapTest1, mapTest3)); + + // test assignment operator + CacheMultiMap mapTest4; + mapTest4 = mapTest1; + BOOST_CHECK(Compare(mapTest1, mapTest4)); +} + +BOOST_AUTO_TEST_SUITE_END()