Rate check fixes (#1196)

* Fix rate checks for governance objects

* Do not update last vote time when rate checks are disabled

* Bump governance serialization version
This commit is contained in:
Tim Flynn 2016-12-08 15:00:49 -05:00 committed by UdjinM6
parent 4dac0025a7
commit 15bb93d5e8
4 changed files with 86 additions and 33 deletions

View File

@ -150,6 +150,7 @@ bool CGovernanceObject::ProcessVote(CNode* pfrom,
} }
vote_instance_t& voteInstance = it2->second; vote_instance_t& voteInstance = it2->second;
int64_t nNow = GetTime(); int64_t nNow = GetTime();
int64_t nVoteTimeUpdate = voteInstance.nTime;
if(governance.AreRateChecksEnabled()) { if(governance.AreRateChecksEnabled()) {
int64_t nTimeDelta = nNow - voteInstance.nTime; int64_t nTimeDelta = nNow - voteInstance.nTime;
if(nTimeDelta < GOVERNANCE_UPDATE_MIN) { if(nTimeDelta < GOVERNANCE_UPDATE_MIN) {
@ -160,6 +161,7 @@ bool CGovernanceObject::ProcessVote(CNode* pfrom,
<< ", time delta = " << nTimeDelta << "\n"; << ", time delta = " << nTimeDelta << "\n";
LogPrint("gobject", ostr.str().c_str()); LogPrint("gobject", ostr.str().c_str());
exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_TEMPORARY_ERROR); exception = CGovernanceException(ostr.str(), GOVERNANCE_EXCEPTION_TEMPORARY_ERROR);
nVoteTimeUpdate = nNow;
return false; return false;
} }
} }
@ -175,7 +177,7 @@ bool CGovernanceObject::ProcessVote(CNode* pfrom,
governance.AddInvalidVote(vote); governance.AddInvalidVote(vote);
return false; return false;
} }
voteInstance = vote_instance_t(vote.GetOutcome(), nNow); voteInstance = vote_instance_t(vote.GetOutcome(), nVoteTimeUpdate);
fileVotes.AddVote(vote); fileVotes.AddVote(vote);
fDirtyCache = true; fDirtyCache = true;
return true; return true;
@ -459,15 +461,6 @@ bool CGovernanceObject::IsValidLocally(const CBlockIndex* pindex, std::string& s
return false; return false;
} }
// Only perform rate check if we are synced because during syncing it is expected
// that objects will be seen in rapid succession
if(masternodeSync.IsSynced()) {
if(!governance.MasternodeRateCheck(vinMasternode, nObjectType)) {
strError = "Masternode attempting to create too many objects: " + strOutpoint;
return false;
}
}
return true; return true;
} }

View File

@ -28,7 +28,7 @@ std::map<uint256, int64_t> mapAskedForGovernanceObject;
int nSubmittedFinalBudget; int nSubmittedFinalBudget;
const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-2"; const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-3";
CGovernanceManager::CGovernanceManager() CGovernanceManager::CGovernanceManager()
: pCurrentBlockIndex(NULL), : pCurrentBlockIndex(NULL),
@ -41,7 +41,7 @@ CGovernanceManager::CGovernanceManager()
mapVoteToObject(MAX_CACHE_SIZE), mapVoteToObject(MAX_CACHE_SIZE),
mapInvalidVotes(MAX_CACHE_SIZE), mapInvalidVotes(MAX_CACHE_SIZE),
mapOrphanVotes(MAX_CACHE_SIZE), mapOrphanVotes(MAX_CACHE_SIZE),
mapLastMasternodeTrigger(), mapLastMasternodeObject(),
setRequestedObjects(), setRequestedObjects(),
fRateChecksEnabled(true), fRateChecksEnabled(true),
cs() cs()
@ -171,6 +171,11 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C
return; return;
} }
if(!MasternodeRateCheck(govobj, true)) {
LogPrintf("MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", strHash, nCachedBlockHeight);
return;
}
std::string strError = ""; std::string strError = "";
// CHECK OBJECT AGAINST LOCAL BLOCKCHAIN // CHECK OBJECT AGAINST LOCAL BLOCKCHAIN
@ -256,6 +261,7 @@ void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj, CGovernance
std::vector<vote_time_pair_t> vecVotePairs; std::vector<vote_time_pair_t> vecVotePairs;
mapOrphanVotes.GetAll(nHash, vecVotePairs); mapOrphanVotes.GetAll(nHash, vecVotePairs);
fRateChecksEnabled = false;
int64_t nNow = GetAdjustedTime(); int64_t nNow = GetAdjustedTime();
for(size_t i = 0; i < vecVotePairs.size(); ++i) { for(size_t i = 0; i < vecVotePairs.size(); ++i) {
bool fRemove = false; bool fRemove = false;
@ -272,6 +278,7 @@ void CGovernanceManager::CheckOrphanVotes(CGovernanceObject& govobj, CGovernance
mapOrphanVotes.Erase(nHash, pairVote); mapOrphanVotes.Erase(nHash, pairVote);
} }
} }
fRateChecksEnabled = true;
} }
bool CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj) bool CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj)
@ -307,13 +314,8 @@ bool CGovernanceManager::AddGovernanceObject(CGovernanceObject& govobj)
<< ", nObjectType = " << govobj.nObjectType << ", nObjectType = " << govobj.nObjectType
<< endl; ); << endl; );
if(govobj.GetObjectType() == GOVERNANCE_OBJECT_TRIGGER) {
mapLastMasternodeTrigger[govobj.GetMasternodeVin().prevout] = nCachedBlockHeight;
}
switch(govobj.nObjectType) { switch(govobj.nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER: case GOVERNANCE_OBJECT_TRIGGER:
mapLastMasternodeTrigger[govobj.vinMasternode.prevout] = nCachedBlockHeight;
DBG( cout << "CGovernanceManager::AddGovernanceObject Before AddNewTrigger" << endl; ); DBG( cout << "CGovernanceManager::AddGovernanceObject Before AddNewTrigger" << endl; );
triggerman.AddNewTrigger(nHash); triggerman.AddNewTrigger(nHash);
DBG( cout << "CGovernanceManager::AddGovernanceObject After AddNewTrigger" << endl; ); DBG( cout << "CGovernanceManager::AddGovernanceObject After AddNewTrigger" << endl; );
@ -686,37 +688,72 @@ void CGovernanceManager::Sync(CNode* pfrom, uint256 nProp)
LogPrintf("CGovernanceManager::Sync -- sent %d objects and %d votes to peer=%d\n", nObjCount, nVoteCount, pfrom->id); LogPrintf("CGovernanceManager::Sync -- sent %d objects and %d votes to peer=%d\n", nObjCount, nVoteCount, pfrom->id);
} }
bool CGovernanceManager::MasternodeRateCheck(const CTxIn& vin, int nObjectType) bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast)
{ {
LOCK(cs); LOCK(cs);
if(!masternodeSync.IsSynced()) {
return true;
}
if(!fRateChecksEnabled) { if(!fRateChecksEnabled) {
return true; return true;
} }
int mindiff = 0; int nObjectType = govobj.GetObjectType();
if((nObjectType != GOVERNANCE_OBJECT_TRIGGER) && (nObjectType != GOVERNANCE_OBJECT_WATCHDOG)) {
return true;
}
const CTxIn& vin = govobj.GetMasternodeVin();
txout_m_it it = mapLastMasternodeObject.find(vin.prevout);
if(it == mapLastMasternodeObject.end()) {
if(fUpdateLast) {
it = mapLastMasternodeObject.insert(txout_m_t::value_type(vin.prevout, last_object_rec(0, 0))).first;
switch(nObjectType) { switch(nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER: case GOVERNANCE_OBJECT_TRIGGER:
mindiff = Params().GetConsensus().nSuperblockCycle - Params().GetConsensus().nSuperblockCycle / 10; it->second.nLastTriggerBlockHeight = nCachedBlockHeight;
break; break;
case GOVERNANCE_OBJECT_WATCHDOG: case GOVERNANCE_OBJECT_WATCHDOG:
mindiff = 1; it->second.nLastWatchdogBlockHeight = nCachedBlockHeight;
break;
default:
break;
}
}
return true;
}
int nMinDiff = 0;
int nObjectBlock = 0;
switch(nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER:
// Allow 1 trigger per mn per cycle, with a small fudge factor
nMinDiff = Params().GetConsensus().nSuperblockCycle - Params().GetConsensus().nSuperblockCycle / 10;
nObjectBlock = it->second.nLastTriggerBlockHeight;
if(fUpdateLast) {
it->second.nLastTriggerBlockHeight = nCachedBlockHeight;
}
break;
case GOVERNANCE_OBJECT_WATCHDOG:
nMinDiff = 1;
nObjectBlock = it->second.nLastWatchdogBlockHeight;
if(fUpdateLast) {
it->second.nLastWatchdogBlockHeight = nCachedBlockHeight;
}
break; break;
default: default:
break; break;
} }
txout_m_it it = mapLastMasternodeTrigger.find(vin.prevout); if((nCachedBlockHeight - nObjectBlock) > nMinDiff) {
if(it == mapLastMasternodeTrigger.end()) {
return true;
}
// Allow 1 trigger per mn per cycle, with a small fudge factor
if((nCachedBlockHeight - it->second) > mindiff) {
return true; return true;
} }
LogPrintf("CGovernanceManager::MasternodeRateCheck -- Rate too high: vin = %s, current height = %d, last MN height = %d, minimum difference = %d\n", LogPrintf("CGovernanceManager::MasternodeRateCheck -- Rate too high: vin = %s, current height = %d, last MN height = %d, minimum difference = %d\n",
vin.prevout.ToStringShort(), nCachedBlockHeight, it->second, mindiff); vin.prevout.ToStringShort(), nCachedBlockHeight, nObjectBlock, nMinDiff);
return false; return false;
} }

View File

@ -48,6 +48,26 @@ class CGovernanceManager
friend class CGovernanceObject; friend class CGovernanceObject;
public: // Types public: // Types
struct last_object_rec {
last_object_rec(int nLastTriggerBlockHeightIn = 0, int nLastWatchdogBlockHeightIn = 0)
: nLastTriggerBlockHeight(nLastTriggerBlockHeightIn),
nLastWatchdogBlockHeight(nLastWatchdogBlockHeightIn)
{}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{
READWRITE(nLastTriggerBlockHeight);
READWRITE(nLastWatchdogBlockHeight);
}
int nLastTriggerBlockHeight;
int nLastWatchdogBlockHeight;
};
typedef std::map<uint256, CGovernanceObject> object_m_t; typedef std::map<uint256, CGovernanceObject> object_m_t;
typedef object_m_t::iterator object_m_it; typedef object_m_t::iterator object_m_it;
@ -74,7 +94,7 @@ public: // Types
typedef object_m_t::size_type size_type; typedef object_m_t::size_type size_type;
typedef std::map<COutPoint, int> txout_m_t; typedef std::map<COutPoint, last_object_rec > txout_m_t;
typedef txout_m_t::iterator txout_m_it; typedef txout_m_t::iterator txout_m_it;
@ -124,7 +144,7 @@ private:
vote_mcache_t mapOrphanVotes; vote_mcache_t mapOrphanVotes;
txout_m_t mapLastMasternodeTrigger; txout_m_t mapLastMasternodeObject;
hash_s_t setRequestedObjects; hash_s_t setRequestedObjects;
@ -186,7 +206,7 @@ public:
mapVoteToObject.Clear(); mapVoteToObject.Clear();
mapInvalidVotes.Clear(); mapInvalidVotes.Clear();
mapOrphanVotes.Clear(); mapOrphanVotes.Clear();
mapLastMasternodeTrigger.clear(); mapLastMasternodeObject.clear();
} }
std::string ToString() const; std::string ToString() const;
@ -209,7 +229,7 @@ public:
READWRITE(mapOrphanVotes); READWRITE(mapOrphanVotes);
READWRITE(mapObjects); READWRITE(mapObjects);
READWRITE(mapWatchdogObjects); READWRITE(mapWatchdogObjects);
READWRITE(mapLastMasternodeTrigger); READWRITE(mapLastMasternodeObject);
if(ser_action.ForRead() && (strVersion != SERIALIZATION_VERSION_STRING)) { if(ser_action.ForRead() && (strVersion != SERIALIZATION_VERSION_STRING)) {
Clear(); Clear();
return; return;
@ -235,7 +255,7 @@ public:
void AddSeenVote(uint256 nHash, int status); void AddSeenVote(uint256 nHash, int status);
bool MasternodeRateCheck(const CTxIn& vin, int nObjectType); bool MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast = false);
bool ProcessVoteAndRelay(const CGovernanceVote& vote, CGovernanceException& exception) { bool ProcessVoteAndRelay(const CGovernanceVote& vote, CGovernanceException& exception) {
bool fOK = ProcessVote(NULL, vote, exception); bool fOK = ProcessVote(NULL, vote, exception);

View File

@ -217,6 +217,9 @@ UniValue gobject(const UniValue& params, bool fHelp)
} }
// RELAY THIS OBJECT // RELAY THIS OBJECT
if(!governance.MasternodeRateCheck(govobj)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Object creation rate limit exceeded");
}
governance.AddSeenGovernanceObject(govobj.GetHash(), SEEN_OBJECT_IS_VALID); governance.AddSeenGovernanceObject(govobj.GetHash(), SEEN_OBJECT_IS_VALID);
govobj.Relay(); govobj.Relay();
governance.AddGovernanceObject(govobj); governance.AddGovernanceObject(govobj);