Implement rate check using buffer (#1202)

* Implemented buffer for masternode rate checks

* Change upper limit on object timestamp to 1 hour in the future
This commit is contained in:
Tim Flynn 2016-12-14 10:28:55 -05:00 committed by UdjinM6
parent 711a5fbf20
commit 8fc8e6c8c2
4 changed files with 217 additions and 24 deletions

View File

@ -66,6 +66,7 @@ BITCOIN_TESTS =\
test/policyestimator_tests.cpp \
test/pow_tests.cpp \
test/prevector_tests.cpp \
test/ratecheck_tests.cpp \
test/reverselock_tests.cpp \
test/rpc_tests.cpp \
test/sanity_tests.cpp \

View File

@ -28,7 +28,7 @@ std::map<uint256, int64_t> mapAskedForGovernanceObject;
int nSubmittedFinalBudget;
const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-4";
const std::string CGovernanceManager::SERIALIZATION_VERSION_STRING = "CGovernanceManager-Version-5";
CGovernanceManager::CGovernanceManager()
: pCurrentBlockIndex(NULL),
@ -745,13 +745,13 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo
if(it == mapLastMasternodeObject.end()) {
if(fUpdateLast) {
it = mapLastMasternodeObject.insert(txout_m_t::value_type(vin.prevout, last_object_rec(0, 0, true))).first;
it = mapLastMasternodeObject.insert(txout_m_t::value_type(vin.prevout, last_object_rec(true))).first;
switch(nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER:
it->second.nLastTriggerTime = std::max(it->second.nLastTriggerTime, nTimestamp);
it->second.triggerBuffer.AddTimestamp(nTimestamp);
break;
case GOVERNANCE_OBJECT_WATCHDOG:
it->second.nLastWatchdogTime = std::max(it->second.nLastWatchdogTime, nTimestamp);
it->second.watchdogBuffer.AddTimestamp(nTimestamp);
break;
default:
break;
@ -773,35 +773,40 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo
return false;
}
if(nTimestamp > nNow + 2 * nSuperblockCycleSeconds) {
if(nTimestamp > nNow + 60*60) {
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;
double dMaxRate = 1.1 / nSuperblockCycleSeconds;
double dRate = 0.0;
CRateCheckBuffer buffer;
switch(nObjectType) {
case GOVERNANCE_OBJECT_TRIGGER:
// Allow 1 trigger per mn per cycle, with a small fudge factor
nMinDiff = int64_t(0.9 * nSuperblockCycleSeconds);
nLastObjectTime = it->second.nLastTriggerTime;
dMaxRate = 1.1 / nSuperblockCycleSeconds;
buffer = it->second.triggerBuffer;
buffer.AddTimestamp(nTimestamp);
dRate = buffer.GetRate();
if(fUpdateLast) {
it->second.nLastTriggerTime = std::max(it->second.nLastTriggerTime, nTimestamp);
it->second.triggerBuffer.AddTimestamp(nTimestamp);
}
break;
case GOVERNANCE_OBJECT_WATCHDOG:
nMinDiff = Params().GetConsensus().nPowTargetSpacing;
nLastObjectTime = it->second.nLastWatchdogTime;
dMaxRate = 1.1 / 3600.;
buffer = it->second.watchdogBuffer;
buffer.AddTimestamp(nTimestamp);
dRate = buffer.GetRate();
if(fUpdateLast) {
it->second.nLastWatchdogTime = std::max(it->second.nLastWatchdogTime, nTimestamp);
it->second.watchdogBuffer.AddTimestamp(nTimestamp);
}
break;
default:
break;
}
if((nTimestamp - nLastObjectTime) > nMinDiff) {
if(dRate < dMaxRate) {
if(fUpdateLast) {
it->second.fStatusOK = true;
}
@ -813,8 +818,8 @@ bool CGovernanceManager::MasternodeRateCheck(const CGovernanceObject& govobj, bo
}
}
LogPrintf("CGovernanceManager::MasternodeRateCheck -- Rate too high: object hash = %s, masternode vin = %s, object timestamp = %d, last timestamp = %d, minimum difference = %d\n",
strHash, vin.prevout.ToStringShort(), nTimestamp, nLastObjectTime, nMinDiff);
LogPrintf("CGovernanceManager::MasternodeRateCheck -- Rate too high: object hash = %s, masternode vin = %s, object timestamp = %d, rate = %f, max rate = %f\n",
strHash, vin.prevout.ToStringShort(), nTimestamp, dRate, dMaxRate);
return false;
}

View File

@ -40,6 +40,112 @@ extern CGovernanceManager governance;
typedef std::pair<CGovernanceObject, int64_t> object_time_pair_t;
static const int RATE_BUFFER_SIZE = 5;
class CRateCheckBuffer {
private:
std::vector<int64_t> vecTimestamps;
int nDataStart;
int nDataEnd;
bool fBufferEmpty;
public:
CRateCheckBuffer()
: vecTimestamps(RATE_BUFFER_SIZE),
nDataStart(0),
nDataEnd(0),
fBufferEmpty(true)
{}
void AddTimestamp(int64_t nTimestamp)
{
if((nDataEnd == nDataStart) && !fBufferEmpty) {
// Buffer full, discard 1st element
nDataStart = (nDataStart + 1) % RATE_BUFFER_SIZE;
}
vecTimestamps[nDataEnd] = nTimestamp;
nDataEnd = (nDataEnd + 1) % RATE_BUFFER_SIZE;
fBufferEmpty = false;
}
int64_t GetMinTimestamp()
{
int nIndex = nDataStart;
int64_t nMin = numeric_limits<int64_t>::max();
if(fBufferEmpty) {
return nMin;
}
do {
if(vecTimestamps[nIndex] < nMin) {
nMin = vecTimestamps[nIndex];
}
nIndex = (nIndex + 1) % RATE_BUFFER_SIZE;
} while(nIndex != nDataEnd);
return nMin;
}
int64_t GetMaxTimestamp()
{
int nIndex = nDataStart;
int64_t nMax = 0;
if(fBufferEmpty) {
return nMax;
}
do {
if(vecTimestamps[nIndex] > nMax) {
nMax = vecTimestamps[nIndex];
}
nIndex = (nIndex + 1) % RATE_BUFFER_SIZE;
} while(nIndex != nDataEnd);
return nMax;
}
int GetCount()
{
int nCount = 0;
if(fBufferEmpty) {
return 0;
}
if(nDataEnd > nDataStart) {
nCount = nDataEnd - nDataStart;
}
else {
nCount = RATE_BUFFER_SIZE - nDataStart + nDataEnd;
}
return nCount;
}
double GetRate()
{
int nCount = GetCount();
if(nCount < 2) {
return 0.0;
}
int64_t nMin = GetMinTimestamp();
int64_t nMax = GetMaxTimestamp();
if(nMin == nMax) {
// multiple objects with the same timestamp => infinite rate
return 1.0e10;
}
return double(nCount) / double(nMax - nMin);
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{
READWRITE(vecTimestamps);
READWRITE(nDataStart);
READWRITE(nDataEnd);
READWRITE(fBufferEmpty);
}
};
//
// Governance Manager : Contains all proposals for the budget
//
@ -49,9 +155,9 @@ class CGovernanceManager
public: // Types
struct last_object_rec {
last_object_rec(int64_t nLastTriggerTimeIn = 0, int64_t nLastWatchdogTimeIn = 0, bool fStatusOKIn = true)
: nLastTriggerTime(nLastTriggerTimeIn),
nLastWatchdogTime(nLastWatchdogTimeIn),
last_object_rec(bool fStatusOKIn = true)
: triggerBuffer(),
watchdogBuffer(),
fStatusOK(fStatusOKIn)
{}
@ -60,13 +166,13 @@ public: // Types
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{
READWRITE(nLastTriggerTime);
READWRITE(nLastWatchdogTime);
READWRITE(triggerBuffer);
READWRITE(watchdogBuffer);
READWRITE(fStatusOK);
}
int64_t nLastTriggerTime;
int64_t nLastWatchdogTime;
CRateCheckBuffer triggerBuffer;
CRateCheckBuffer watchdogBuffer;
bool fStatusOK;
};

View File

@ -0,0 +1,81 @@
// Copyright (c) 2014-2016 The Dash Core developers
#include "governance.h"
#include "test/test_dash.h"
#include <boost/test/unit_test.hpp>
BOOST_FIXTURE_TEST_SUITE(ratecheck_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(ratecheck_test)
{
CRateCheckBuffer buffer;
BOOST_CHECK(buffer.GetCount() == 0);
BOOST_CHECK(buffer.GetMinTimestamp() == numeric_limits<int64_t>::max());
BOOST_CHECK(buffer.GetMaxTimestamp() == 0);
BOOST_CHECK(buffer.GetRate() == 0.0);
buffer.AddTimestamp(1);
std::cout << "buffer.GetMinTimestamp() = " << buffer.GetMinTimestamp() << std::endl;
BOOST_CHECK(buffer.GetCount() == 1);
BOOST_CHECK(buffer.GetMinTimestamp() == 1);
BOOST_CHECK(buffer.GetMaxTimestamp() == 1);
BOOST_CHECK(buffer.GetRate() == 0.0);
buffer.AddTimestamp(2);
BOOST_CHECK(buffer.GetCount() == 2);
BOOST_CHECK(buffer.GetMinTimestamp() == 1);
BOOST_CHECK(buffer.GetMaxTimestamp() == 2);
BOOST_CHECK(fabs(buffer.GetRate() - 2.0) < 1.0e-9);
buffer.AddTimestamp(3);
BOOST_CHECK(buffer.GetCount() == 3);
BOOST_CHECK(buffer.GetMinTimestamp() == 1);
BOOST_CHECK(buffer.GetMaxTimestamp() == 3);
int64_t nMin = buffer.GetMinTimestamp();
int64_t nMax = buffer.GetMaxTimestamp();
double dRate = buffer.GetRate();
std::cout << "buffer.GetCount() = " << buffer.GetCount() << std::endl;
std::cout << "nMin = " << nMin << std::endl;
std::cout << "nMax = " << nMax << std::endl;
std::cout << "buffer.GetRate() = " << dRate << std::endl;
BOOST_CHECK(fabs(buffer.GetRate() - (3.0/2.0)) < 1.0e-9);
buffer.AddTimestamp(4);
BOOST_CHECK(buffer.GetCount() == 4);
BOOST_CHECK(buffer.GetMinTimestamp() == 1);
BOOST_CHECK(buffer.GetMaxTimestamp() == 4);
BOOST_CHECK(fabs(buffer.GetRate() - (4.0/3.0)) < 1.0e-9);
buffer.AddTimestamp(5);
BOOST_CHECK(buffer.GetCount() == 5);
BOOST_CHECK(buffer.GetMinTimestamp() == 1);
BOOST_CHECK(buffer.GetMaxTimestamp() == 5);
BOOST_CHECK(fabs(buffer.GetRate() - (5.0/4.0)) < 1.0e-9);
buffer.AddTimestamp(6);
BOOST_CHECK(buffer.GetCount() == 5);
BOOST_CHECK(buffer.GetMinTimestamp() == 2);
BOOST_CHECK(buffer.GetMaxTimestamp() == 6);
BOOST_CHECK(fabs(buffer.GetRate() - (5.0/4.0)) < 1.0e-9);
CRateCheckBuffer buffer2;
std::cout << "Before loop tests" << std::endl;
for(int64_t i = 1; i < 11; ++i) {
std::cout << "In loop: i = " << i << std::endl;
buffer2.AddTimestamp(i);
BOOST_CHECK(buffer2.GetCount() == (i <= 5 ? i : 5));
BOOST_CHECK(buffer2.GetMinTimestamp() == max(int64_t(1), i - 4));
BOOST_CHECK(buffer2.GetMaxTimestamp() == i);
}
}
BOOST_AUTO_TEST_SUITE_END()