diff --git a/src/Makefile.test.include b/src/Makefile.test.include index de97a2123..d0e4c8139 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -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 \ diff --git a/src/governance.cpp b/src/governance.cpp index 864381083..0d9ba9560 100644 --- a/src/governance.cpp +++ b/src/governance.cpp @@ -28,7 +28,7 @@ std::map 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; } diff --git a/src/governance.h b/src/governance.h index 18333892a..659d30ce8 100644 --- a/src/governance.h +++ b/src/governance.h @@ -40,6 +40,112 @@ extern CGovernanceManager governance; typedef std::pair object_time_pair_t; +static const int RATE_BUFFER_SIZE = 5; + +class CRateCheckBuffer { +private: + std::vector 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::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 + 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 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; }; diff --git a/src/test/ratecheck_tests.cpp b/src/test/ratecheck_tests.cpp new file mode 100644 index 000000000..7c93a38eb --- /dev/null +++ b/src/test/ratecheck_tests.cpp @@ -0,0 +1,81 @@ +// Copyright (c) 2014-2016 The Dash Core developers + +#include "governance.h" + +#include "test/test_dash.h" + +#include + +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::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()