Add Fast-Rise difficulty protection
This commit is contained in:
parent
84a2107f42
commit
f8a8daaae5
4 changed files with 245 additions and 1 deletions
|
|
@ -182,6 +182,7 @@ public:
|
|||
consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100;
|
||||
consensus.asertActivationHeight = 6500;
|
||||
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
||||
consensus.fastRiseActivationHeight = 7000;
|
||||
|
||||
|
||||
|
||||
|
|
@ -223,6 +224,7 @@ public:
|
|||
consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100;
|
||||
consensus.asertActivationHeight = 6500;
|
||||
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
||||
consensus.fastRiseActivationHeight = 7000;
|
||||
|
||||
|
||||
|
||||
|
|
@ -457,6 +459,7 @@ public:
|
|||
consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100;
|
||||
consensus.asertActivationHeight = 6500;
|
||||
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
||||
consensus.fastRiseActivationHeight = 7000;
|
||||
|
||||
|
||||
consensus.BIP65Height = 1351;
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ struct Params {
|
|||
int64_t difficultyMaxActualTimespan;
|
||||
int asertActivationHeight;
|
||||
int64_t asertHalfLife;
|
||||
int fastRiseActivationHeight;
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
64
src/pow.cpp
64
src/pow.cpp
|
|
@ -84,6 +84,67 @@ unsigned int CalculateASERT(const CBlockIndex* pindexLast, const Consensus::Para
|
|||
return target.GetCompact();
|
||||
}
|
||||
|
||||
static bool ShouldTriggerFastRise(const CBlockIndex* pindexLast, const Consensus::Params& params)
|
||||
{
|
||||
// Trigger when at least 3 of the last 4 solve intervals are below
|
||||
// 20% of the target spacing (120 seconds on Vexta).
|
||||
// Zero or negative raw intervals also count as fast to prevent
|
||||
// backward/equal timestamp manipulation from bypassing the trigger.
|
||||
const int64_t fastThreshold = params.nPowTargetSpacing / 5;
|
||||
int fastIntervals = 0;
|
||||
|
||||
const CBlockIndex* pindex = pindexLast;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (pindex == nullptr || pindex->pprev == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int64_t solveTime =
|
||||
pindex->GetBlockTime() - pindex->pprev->GetBlockTime();
|
||||
|
||||
if (solveTime < fastThreshold) {
|
||||
++fastIntervals;
|
||||
}
|
||||
|
||||
pindex = pindex->pprev;
|
||||
}
|
||||
|
||||
return fastIntervals >= 3;
|
||||
}
|
||||
|
||||
static unsigned int ApplyFastRiseProtection(
|
||||
const CBlockIndex* pindexLast,
|
||||
unsigned int asertBits,
|
||||
const Consensus::Params& params)
|
||||
{
|
||||
if (pindexLast->nHeight + 1 < params.fastRiseActivationHeight) {
|
||||
return asertBits;
|
||||
}
|
||||
|
||||
if (!ShouldTriggerFastRise(pindexLast, params)) {
|
||||
return asertBits;
|
||||
}
|
||||
|
||||
arith_uint256 asertTarget;
|
||||
asertTarget.SetCompact(asertBits);
|
||||
|
||||
arith_uint256 fastTarget;
|
||||
fastTarget.SetCompact(pindexLast->nBits);
|
||||
fastTarget >>= 1;
|
||||
|
||||
if (fastTarget == 0) {
|
||||
fastTarget = 1;
|
||||
}
|
||||
|
||||
// Never weaken standard ASERT. Under a Fast-Rise trigger, require
|
||||
// at least one 2x difficulty step relative to the previous block.
|
||||
if (asertTarget < fastTarget) {
|
||||
return asertBits;
|
||||
}
|
||||
|
||||
return fastTarget.GetCompact();
|
||||
}
|
||||
|
||||
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params)
|
||||
{
|
||||
// Vexta is SHA256D-only.
|
||||
|
|
@ -97,7 +158,8 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
|
|||
if (!params.fPowNoRetargeting &&
|
||||
!params.fEasyPow &&
|
||||
pindexLast->nHeight + 1 >= params.asertActivationHeight) {
|
||||
return CalculateASERT(pindexLast, params);
|
||||
const unsigned int asertBits = CalculateASERT(pindexLast, params);
|
||||
return ApplyFastRiseProtection(pindexLast, asertBits, params);
|
||||
}
|
||||
|
||||
const CBlockIndex* pindexFirst = pindexLast;
|
||||
|
|
|
|||
|
|
@ -351,4 +351,182 @@ BOOST_AUTO_TEST_CASE(ASERT_reference_vector_run06_test)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(FastRise_activation_and_trigger_test)
|
||||
{
|
||||
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||
auto consensus = chainParams->GetConsensus();
|
||||
|
||||
consensus.asertActivationHeight = 6500;
|
||||
consensus.fastRiseActivationHeight = 7000;
|
||||
|
||||
std::vector<CBlockIndex> blocks(7001);
|
||||
|
||||
arith_uint256 baseTarget = UintToArith256(consensus.powLimit);
|
||||
baseTarget >>= 8;
|
||||
const unsigned int baseBits = baseTarget.GetCompact();
|
||||
const int64_t startTime = 1800000000;
|
||||
|
||||
// Build an otherwise perfectly spaced chain.
|
||||
for (int i = 0; i <= 7000; ++i) {
|
||||
blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
|
||||
blocks[i].nHeight = i;
|
||||
blocks[i].nTime = startTime + i * consensus.nPowTargetSpacing;
|
||||
blocks[i].nBits = baseBits;
|
||||
}
|
||||
|
||||
// Two fast intervals out of the last four must NOT trigger Fast-Rise.
|
||||
blocks[6995].nTime = startTime + 6995 * consensus.nPowTargetSpacing;
|
||||
blocks[6996].nTime = blocks[6995].nTime + 60;
|
||||
blocks[6997].nTime = blocks[6996].nTime + 60;
|
||||
blocks[6998].nTime = blocks[6997].nTime + 600;
|
||||
blocks[6999].nTime = blocks[6998].nTime + 600;
|
||||
|
||||
auto baselineConsensus = consensus;
|
||||
baselineConsensus.fastRiseActivationHeight = 1000000;
|
||||
|
||||
const unsigned int twoFastBaseline =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, baselineConsensus);
|
||||
const unsigned int twoFastResult =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, consensus);
|
||||
|
||||
BOOST_CHECK_EQUAL(twoFastResult, twoFastBaseline);
|
||||
|
||||
// Three fast intervals out of the last four MUST trigger Fast-Rise.
|
||||
blocks[6998].nTime = blocks[6997].nTime + 60;
|
||||
blocks[6999].nTime = blocks[6998].nTime + 600;
|
||||
|
||||
const unsigned int threeFastBaseline =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, baselineConsensus);
|
||||
const unsigned int threeFastResult =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, consensus);
|
||||
|
||||
arith_uint256 expectedFastTarget;
|
||||
expectedFastTarget.SetCompact(blocks[6999].nBits);
|
||||
expectedFastTarget >>= 1;
|
||||
|
||||
BOOST_CHECK_NE(threeFastResult, threeFastBaseline);
|
||||
BOOST_CHECK_EQUAL(threeFastResult, expectedFastTarget.GetCompact());
|
||||
|
||||
// The same pattern before activation must still behave exactly like ASERT.
|
||||
consensus.fastRiseActivationHeight = 7001;
|
||||
|
||||
const unsigned int preActivationResult =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, consensus);
|
||||
|
||||
BOOST_CHECK_EQUAL(preActivationResult, threeFastBaseline);
|
||||
}
|
||||
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(FastRise_returns_to_ASERT_test)
|
||||
{
|
||||
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||
auto consensus = chainParams->GetConsensus();
|
||||
|
||||
consensus.asertActivationHeight = 6500;
|
||||
consensus.fastRiseActivationHeight = 7000;
|
||||
|
||||
std::vector<CBlockIndex> blocks(7002);
|
||||
|
||||
arith_uint256 baseTarget = UintToArith256(consensus.powLimit);
|
||||
baseTarget >>= 8;
|
||||
const unsigned int baseBits = baseTarget.GetCompact();
|
||||
const int64_t startTime = 1800000000;
|
||||
|
||||
for (int i = 0; i <= 7001; ++i) {
|
||||
blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
|
||||
blocks[i].nHeight = i;
|
||||
blocks[i].nTime = startTime + i * consensus.nPowTargetSpacing;
|
||||
blocks[i].nBits = baseBits;
|
||||
}
|
||||
|
||||
// Create a 3-of-4 fast pattern before block 7000.
|
||||
blocks[6995].nTime = startTime + 6995 * consensus.nPowTargetSpacing;
|
||||
blocks[6996].nTime = blocks[6995].nTime + 60;
|
||||
blocks[6997].nTime = blocks[6996].nTime + 60;
|
||||
blocks[6998].nTime = blocks[6997].nTime + 60;
|
||||
blocks[6999].nTime = blocks[6998].nTime + 600;
|
||||
|
||||
const unsigned int triggeredBits =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, consensus);
|
||||
|
||||
arith_uint256 triggeredTarget;
|
||||
triggeredTarget.SetCompact(triggeredBits);
|
||||
|
||||
arith_uint256 expectedTriggeredTarget;
|
||||
expectedTriggeredTarget.SetCompact(blocks[6999].nBits);
|
||||
expectedTriggeredTarget >>= 1;
|
||||
|
||||
BOOST_CHECK_EQUAL(triggeredTarget.GetCompact(), expectedTriggeredTarget.GetCompact());
|
||||
|
||||
// Simulate block 7000 being mined at the Fast-Rise target.
|
||||
blocks[7000].nBits = triggeredBits;
|
||||
blocks[7000].nTime = blocks[6999].nTime + 600;
|
||||
|
||||
// The last four intervals are now 60, 60, 600, 600:
|
||||
// only two are fast, so Fast-Rise must stop immediately.
|
||||
auto baselineConsensus = consensus;
|
||||
baselineConsensus.fastRiseActivationHeight = 1000000;
|
||||
|
||||
const unsigned int expectedASERT =
|
||||
GetNextWorkRequired(&blocks[7000], nullptr, baselineConsensus);
|
||||
|
||||
const unsigned int actual =
|
||||
GetNextWorkRequired(&blocks[7000], nullptr, consensus);
|
||||
|
||||
BOOST_CHECK_EQUAL(actual, expectedASERT);
|
||||
}
|
||||
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(FastRise_zero_and_negative_intervals_test)
|
||||
{
|
||||
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||
auto consensus = chainParams->GetConsensus();
|
||||
|
||||
consensus.asertActivationHeight = 6500;
|
||||
consensus.fastRiseActivationHeight = 7000;
|
||||
|
||||
std::vector<CBlockIndex> blocks(7001);
|
||||
|
||||
arith_uint256 baseTarget = UintToArith256(consensus.powLimit);
|
||||
baseTarget >>= 8;
|
||||
const unsigned int baseBits = baseTarget.GetCompact();
|
||||
const int64_t startTime = 1800000000;
|
||||
|
||||
for (int i = 0; i <= 7000; ++i) {
|
||||
blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
|
||||
blocks[i].nHeight = i;
|
||||
blocks[i].nTime = startTime + i * consensus.nPowTargetSpacing;
|
||||
blocks[i].nBits = baseBits;
|
||||
}
|
||||
|
||||
// Last four raw intervals:
|
||||
// 60, 0, -30, 600
|
||||
// Zero and negative intervals must count as fast.
|
||||
blocks[6995].nTime = startTime + 6995 * consensus.nPowTargetSpacing;
|
||||
blocks[6996].nTime = blocks[6995].nTime + 60;
|
||||
blocks[6997].nTime = blocks[6996].nTime;
|
||||
blocks[6998].nTime = blocks[6997].nTime - 30;
|
||||
blocks[6999].nTime = blocks[6998].nTime + 600;
|
||||
|
||||
auto baselineConsensus = consensus;
|
||||
baselineConsensus.fastRiseActivationHeight = 1000000;
|
||||
|
||||
const unsigned int baseline =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, baselineConsensus);
|
||||
|
||||
const unsigned int actual =
|
||||
GetNextWorkRequired(&blocks[6999], nullptr, consensus);
|
||||
|
||||
arith_uint256 expectedFastTarget;
|
||||
expectedFastTarget.SetCompact(blocks[6999].nBits);
|
||||
expectedFastTarget >>= 1;
|
||||
|
||||
BOOST_CHECK_NE(actual, baseline);
|
||||
BOOST_CHECK_EQUAL(actual, expectedFastTarget.GetCompact());
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
Loading…
Reference in a new issue