Convert masternode rate checks to use object timestamp (#1198)

* Change rate check logic to avoid DoS attacks

* Convert rate check to use object timestamp instead of arrival time

* Update cached variables before checking for superblocks

* Ensure that last times are monotonically non-decreasing

* Bump governance manager serialization format

* Improved rate check error reporting
This commit is contained in:
Tim Flynn 2016-12-11 01:17:38 -05:00 committed by UdjinM6
parent a11bd2c5ba
commit df5abf1468
5 changed files with 83 additions and 29 deletions

View File

@ -336,6 +336,8 @@ bool CSuperblockManager::IsSuperblockTriggered(int nBlockHeight)
// MAKE SURE THIS TRIGGER IS ACTIVE VIA FUNDING CACHE FLAG // MAKE SURE THIS TRIGGER IS ACTIVE VIA FUNDING CACHE FLAG
pObj->UpdateSentinelVariables();
if(pObj->IsSetCachedFunding()) { if(pObj->IsSetCachedFunding()) {
LogPrint("gobject", "CSuperblockManager::IsSuperblockTriggered -- fCacheFunding = true, returning true\n"); LogPrint("gobject", "CSuperblockManager::IsSuperblockTriggered -- fCacheFunding = true, returning true\n");
DBG( cout << "IsSuperblockTriggered returning true" << endl; ); DBG( cout << "IsSuperblockTriggered returning true" << endl; );

View File

@ -277,7 +277,7 @@ int CGovernanceObject::GetObjectSubtype()
return -1; return -1;
} }
uint256 CGovernanceObject::GetHash() uint256 CGovernanceObject::GetHash() const
{ {
// CREATE HASH OF ALL IMPORTANT PIECES OF DATA // CREATE HASH OF ALL IMPORTANT PIECES OF DATA
@ -649,7 +649,7 @@ void CGovernanceObject::Relay()
RelayInv(inv, PROTOCOL_VERSION); RelayInv(inv, PROTOCOL_VERSION);
} }
void CGovernanceObject::UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex) void CGovernanceObject::UpdateSentinelVariables()
{ {
// CALCULATE MINIMUM SUPPORT LEVELS REQUIRED // CALCULATE MINIMUM SUPPORT LEVELS REQUIRED

View File

@ -273,7 +273,7 @@ public:
void UpdateLocalValidity(const CBlockIndex *pCurrentBlockIndex); void UpdateLocalValidity(const CBlockIndex *pCurrentBlockIndex);
void UpdateSentinelVariables(const CBlockIndex *pCurrentBlockIndex); void UpdateSentinelVariables();
int GetObjectSubtype(); int GetObjectSubtype();
@ -283,7 +283,7 @@ public:
void Relay(); void Relay();
uint256 GetHash(); uint256 GetHash() const;
// GET VOTE COUNT FOR SIGNAL // GET VOTE COUNT FOR SIGNAL

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-3"; const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-4";
CGovernanceManager::CGovernanceManager() CGovernanceManager::CGovernanceManager()
: pCurrentBlockIndex(NULL), : pCurrentBlockIndex(NULL),
@ -171,7 +171,8 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C
return; return;
} }
if(!MasternodeRateCheck(govobj, true)) { bool fRateCheckBypassed = false;
if(!MasternodeRateCheck(govobj, true, false, fRateCheckBypassed)) {
LogPrintf("MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", strHash, nCachedBlockHeight); LogPrintf("MNGOVERNANCEOBJECT -- masternode rate check failed - %s - (current block height %d) \n", strHash, nCachedBlockHeight);
return; return;
} }
@ -193,9 +194,16 @@ void CGovernanceManager::ProcessMessage(CNode* pfrom, std::string& strCommand, C
return; return;
} }
if(fRateCheckBypassed) {
if(!MasternodeRateCheck(govobj, true, true, fRateCheckBypassed)) {
LogPrintf("MNGOVERNANCEOBJECT -- masternode rate check failed (after signature verification) - %s - (current block height %d) \n", strHash, nCachedBlockHeight);
return;
}
}
// UPDATE CACHED VARIABLES FOR THIS OBJECT AND ADD IT TO OUR MANANGED DATA // UPDATE CACHED VARIABLES FOR THIS OBJECT AND ADD IT TO OUR MANANGED DATA
govobj.UpdateSentinelVariables(pCurrentBlockIndex); //this sets local vars in object govobj.UpdateSentinelVariables(); //this sets local vars in object
if(AddGovernanceObject(govobj)) if(AddGovernanceObject(govobj))
{ {
@ -398,7 +406,7 @@ void CGovernanceManager::UpdateCachesAndClean()
pObj->UpdateLocalValidity(pCurrentBlockIndex); pObj->UpdateLocalValidity(pCurrentBlockIndex);
// UPDATE SENTINEL SIGNALING VARIABLES // UPDATE SENTINEL SIGNALING VARIABLES
pObj->UpdateSentinelVariables(pCurrentBlockIndex); pObj->UpdateSentinelVariables();
} }
// IF DELETE=TRUE, THEN CLEAN THE MESS UP! // IF DELETE=TRUE, THEN CLEAN THE MESS UP!
@ -689,9 +697,17 @@ void CGovernanceManager::Sync(CNode* pfrom, uint256 nProp)
} }
bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast) bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast)
{
bool fRateCheckBypassed = false;
return MasternodeRateCheck(govobj, fUpdateLast, true, fRateCheckBypassed);
}
bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast, bool fForce, bool& fRateCheckBypassed)
{ {
LOCK(cs); LOCK(cs);
fRateCheckBypassed = false;
if(!masternodeSync.IsSynced()) { if(!masternodeSync.IsSynced()) {
return true; return true;
} }
@ -705,19 +721,23 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo
return true; return true;
} }
int64_t nTimestamp = govobj.GetCreationTime();
int64_t nNow = GetTime();
int64_t nSuperblockCycleSeconds = Params().GetConsensus().nSuperblockCycle * Params().GetConsensus().nPowTargetSpacing;
const CTxIn& vin = govobj.GetMasternodeVin(); const CTxIn& vin = govobj.GetMasternodeVin();
txout_m_it it = mapLastMasternodeObject.find(vin.prevout); txout_m_it it = mapLastMasternodeObject.find(vin.prevout);
if(it == mapLastMasternodeObject.end()) { if(it == mapLastMasternodeObject.end()) {
if(fUpdateLast) { if(fUpdateLast) {
it = mapLastMasternodeObject.insert(txout_m_t::value_type(vin.prevout, last_object_rec(0, 0))).first; it = mapLastMasternodeObject.insert(txout_m_t::value_type(vin.prevout, last_object_rec(0, 0, true))).first;
switch(nObjectType) { switch(nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER: case GOVERNANCE_OBJECT_TRIGGER:
it->second.nLastTriggerBlockHeight = nCachedBlockHeight; it->second.nLastTriggerTime = std::max(it->second.nLastTriggerTime, nTimestamp);
break; break;
case GOVERNANCE_OBJECT_WATCHDOG: case GOVERNANCE_OBJECT_WATCHDOG:
it->second.nLastWatchdogBlockHeight = nCachedBlockHeight; it->second.nLastWatchdogTime = std::max(it->second.nLastWatchdogTime, nTimestamp);
break; break;
default: default:
break; break;
@ -726,34 +746,61 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo
return true; return true;
} }
int nMinDiff = 0; if(it->second.fStatusOK && !fForce) {
int nObjectBlock = 0; fRateCheckBypassed = true;
return true;
}
std::string strHash = govobj.GetHash().ToString();
if(nTimestamp < nNow - 2 * nSuperblockCycleSeconds) {
LogPrintf("CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too old timestamp, masternode vin = %s, timestamp = %d, current time = %d\n",
strHash, vin.prevout.ToStringShort(), nTimestamp, nNow);
return false;
}
if(nTimestamp > nNow - 2 * nSuperblockCycleSeconds) {
LogPrintf("CGovernanceManager::MasternodeRateCheck -- object %s rejected due to too new (future) timestamp, masternode vin = %s, timestamp = %d, current time = %d\n",
strHash, vin.prevout.ToStringShort(), nTimestamp, nNow);
return false;
}
int64_t nMinDiff = 0;
int64_t nLastObjectTime = 0;
switch(nObjectType) { switch(nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER: case GOVERNANCE_OBJECT_TRIGGER:
// Allow 1 trigger per mn per cycle, with a small fudge factor // Allow 1 trigger per mn per cycle, with a small fudge factor
nMinDiff = Params().GetConsensus().nSuperblockCycle - Params().GetConsensus().nSuperblockCycle / 10; nMinDiff = int64_t(0.9 * nSuperblockCycleSeconds);
nObjectBlock = it->second.nLastTriggerBlockHeight; nLastObjectTime = it->second.nLastTriggerTime;
if(fUpdateLast) { if(fUpdateLast) {
it->second.nLastTriggerBlockHeight = nCachedBlockHeight; it->second.nLastTriggerTime = std::max(it->second.nLastTriggerTime, nTimestamp);
} }
break; break;
case GOVERNANCE_OBJECT_WATCHDOG: case GOVERNANCE_OBJECT_WATCHDOG:
nMinDiff = 1; nMinDiff = Params().GetConsensus().nPowTargetSpacing;
nObjectBlock = it->second.nLastWatchdogBlockHeight; nLastObjectTime = it->second.nLastWatchdogTime;
if(fUpdateLast) { if(fUpdateLast) {
it->second.nLastWatchdogBlockHeight = nCachedBlockHeight; it->second.nLastWatchdogTime = std::max(it->second.nLastWatchdogTime, nTimestamp);
} }
break; break;
default: default:
break; break;
} }
if((nCachedBlockHeight - nObjectBlock) > nMinDiff) { if((nTimestamp - nLastObjectTime) > nMinDiff) {
if(fUpdateLast) {
it->second.fStatusOK = true;
}
return true; return true;
} }
else {
if(fUpdateLast) {
it->second.fStatusOK = false;
}
}
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: object hash = %s, masternode vin = %s, object timestamp = %d, last timestamp = %d, minimum difference = %d\n",
vin.prevout.ToStringShort(), nCachedBlockHeight, nObjectBlock, nMinDiff); strHash, vin.prevout.ToStringShort(), nTimestamp, nLastObjectTime, nMinDiff);
return false; return false;
} }

View File

@ -49,9 +49,10 @@ class CGovernanceManager
public: // Types public: // Types
struct last_object_rec { struct last_object_rec {
last_object_rec(int nLastTriggerBlockHeightIn = 0, int nLastWatchdogBlockHeightIn = 0) last_object_rec(int64_t nLastTriggerTimeIn = 0, int64_t nLastWatchdogTimeIn = 0, bool fStatusOKIn = true)
: nLastTriggerBlockHeight(nLastTriggerBlockHeightIn), : nLastTriggerTime(nLastTriggerTimeIn),
nLastWatchdogBlockHeight(nLastWatchdogBlockHeightIn) nLastWatchdogTime(nLastWatchdogTimeIn),
fStatusOK(fStatusOKIn)
{} {}
ADD_SERIALIZE_METHODS; ADD_SERIALIZE_METHODS;
@ -59,12 +60,14 @@ public: // Types
template <typename Stream, typename Operation> template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{ {
READWRITE(nLastTriggerBlockHeight); READWRITE(nLastTriggerTime);
READWRITE(nLastWatchdogBlockHeight); READWRITE(nLastWatchdogTime);
READWRITE(fStatusOK);
} }
int nLastTriggerBlockHeight; int64_t nLastTriggerTime;
int nLastWatchdogBlockHeight; int64_t nLastWatchdogTime;
bool fStatusOK;
}; };
@ -257,6 +260,8 @@ public:
bool MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast = false); bool MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast = false);
bool MasternodeRateCheck(const CGovernanceObject& govobj, bool fUpdateLast, bool fForce, bool& fRateCheckBypassed);
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);
if(fOK) { if(fOK) {