From f8a8daaae58b75082821b06cc227ad038b844653 Mon Sep 17 00:00:00 2001 From: Capri Date: Wed, 9 Sep 2026 20:38:21 +0000 Subject: [PATCH] Add Fast-Rise difficulty protection --- src/chainparams.cpp | 3 + src/consensus/params.h | 1 + src/pow.cpp | 64 ++++++++++++++- src/test/pow_tests.cpp | 178 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 5409e87..647cb92 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -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; diff --git a/src/consensus/params.h b/src/consensus/params.h index 51e91b7..b4f80a6 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -123,6 +123,7 @@ struct Params { int64_t difficultyMaxActualTimespan; int asertActivationHeight; int64_t asertHalfLife; + int fastRiseActivationHeight; diff --git a/src/pow.cpp b/src/pow.cpp index ff4a43a..228696b 100644 --- a/src/pow.cpp +++ b/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; diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 3cb02d2..d536a70 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -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 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 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 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()