From a16bb5345743f8cf62ec594878c55373dec329ec Mon Sep 17 00:00:00 2001 From: Capri Date: Tue, 8 Sep 2026 13:03:50 +0000 Subject: [PATCH] Activate ASERT difficulty adjustment at block 6500 --- src/chainparams.cpp | 6 ++ src/consensus/params.h | 2 + src/pow.cpp | 73 +++++++++++++- src/test/pow_tests.cpp | 210 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 164d1b8..5409e87 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -180,6 +180,8 @@ public: consensus.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100; consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100; + consensus.asertActivationHeight = 6500; + consensus.asertHalfLife = 2 * 24 * 60 * 60; @@ -219,6 +221,8 @@ public: consensus.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100; consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100; + consensus.asertActivationHeight = 6500; + consensus.asertHalfLife = 2 * 24 * 60 * 60; @@ -451,6 +455,8 @@ public: consensus.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100; consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100; + consensus.asertActivationHeight = 6500; + consensus.asertHalfLife = 2 * 24 * 60 * 60; consensus.BIP65Height = 1351; diff --git a/src/consensus/params.h b/src/consensus/params.h index f2c562e..51e91b7 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -121,6 +121,8 @@ struct Params { int64_t difficultyMinActualTimespan; int64_t difficultyMaxActualTimespan; + int asertActivationHeight; + int64_t asertHalfLife; diff --git a/src/pow.cpp b/src/pow.cpp index 525e3b1..ff4a43a 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -22,13 +22,84 @@ unsigned int InitialDifficulty(const Consensus::Params& params) return PowLimit(params); } +unsigned int CalculateASERT(const CBlockIndex* pindexLast, const Consensus::Params& params) +{ + // Anchor ASERT to the last block before activation. + const int anchorHeight = params.asertActivationHeight - 1; + const CBlockIndex* pindexAnchor = pindexLast->GetAncestor(anchorHeight); + + if (pindexAnchor == nullptr) { + return pindexLast->nBits; + } + + const int64_t heightDiff = pindexLast->nHeight - pindexAnchor->nHeight; + const int64_t timeDiff = pindexLast->GetBlockTime() - pindexAnchor->pprev->GetBlockTime(); + + // ASERT exponent in fixed-point 16.16: + // exponent = (timeDiff - idealTime) / halfLife + const int64_t idealTime = (heightDiff + 1) * params.nPowTargetSpacing; + const int64_t exponent = ((timeDiff - idealTime) * 65536) / params.asertHalfLife; + + int64_t shifts = exponent >> 16; + const uint16_t frac = static_cast(exponent); + + // Approximation of 2^(frac/65536), scaled by 65536. + const uint64_t factor = + 65536ULL + + ((195766423245049ULL * frac + + 971821376ULL * frac * frac + + 5127ULL * frac * frac * frac + + (1ULL << 47)) >> 48); + + arith_uint256 target; + target.SetCompact(pindexAnchor->nBits); + target *= factor; + + // factor is scaled by 2^16. + shifts -= 16; + + const arith_uint256 powLimit = UintToArith256(params.powLimit); + + if (shifts <= 0) { + if (-shifts >= 256) { + target = 1; + } else { + target >>= -shifts; + } + } else { + if (shifts >= 256 || target > (powLimit >> shifts)) { + target = powLimit; + } else { + target <<= shifts; + } + } + + if (target == 0) { + target = 1; + } + if (target > powLimit) { + target = powLimit; + } + + return target.GetCompact(); +} + unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params) { - // Vexta is SHA256D-only. Use one global averaging window. + // Vexta is SHA256D-only. if (pindexLast == nullptr) { return InitialDifficulty(params); } + // ASERT activates for the block at asertActivationHeight. + // pindexLast is the previous block, so activation starts when + // the next block height reaches the configured activation height. + if (!params.fPowNoRetargeting && + !params.fEasyPow && + pindexLast->nHeight + 1 >= params.asertActivationHeight) { + return CalculateASERT(pindexLast, params); + } + const CBlockIndex* pindexFirst = pindexLast; for (int i = 0; pindexFirst && i < params.difficultyAveragingWindow; i++) { pindexFirst = pindexFirst->pprev; diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index aa63ed8..3cb02d2 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -141,4 +141,214 @@ BOOST_AUTO_TEST_CASE(ChainParams_SIGNET_sanity) sanity_check_chainparams(*m_node.args, CBaseChainParams::SIGNET); } + +BOOST_AUTO_TEST_CASE(ASERT_behavior_test) +{ + const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); + const auto consensus = chainParams->GetConsensus(); + + std::vector blocks(6501); + + arith_uint256 anchorTargetValue = UintToArith256(consensus.powLimit); + anchorTargetValue >>= 8; + const unsigned int anchorBits = anchorTargetValue.GetCompact(); + const int64_t startTime = 1800000000; + + for (int i = 0; i <= 6500; ++i) { + blocks[i].pprev = i ? &blocks[i - 1] : nullptr; + blocks[i].nHeight = i; + blocks[i].nTime = startTime + i * consensus.nPowTargetSpacing; + blocks[i].nBits = anchorBits; + } + + // Block 6499 is still governed by the legacy DAA. + const unsigned int preActivationBits = + GetNextWorkRequired(&blocks[6498], nullptr, consensus); + BOOST_CHECK_EQUAL(preActivationBits, anchorBits); + + // Block 6500 is the first ASERT block. Ideal 10-minute spacing + // must preserve the anchor target. + const unsigned int idealBits = + GetNextWorkRequired(&blocks[6499], nullptr, consensus); + BOOST_CHECK_EQUAL(idealBits, anchorBits); + + // Block 6501 after a very fast previous block: target must decrease + // (difficulty increases). + blocks[6500].nTime = + blocks[6499].nTime + consensus.nPowTargetSpacing / 10; + + const unsigned int fastBits = + GetNextWorkRequired(&blocks[6500], nullptr, consensus); + + arith_uint256 fastTarget; + fastTarget.SetCompact(fastBits); + + arith_uint256 anchorTarget; + anchorTarget.SetCompact(anchorBits); + + BOOST_CHECK(fastTarget < anchorTarget); + + // Block 6501 after a slow previous block: target must increase + // (difficulty decreases). + blocks[6500].nTime = + blocks[6499].nTime + consensus.nPowTargetSpacing * 3; + + const unsigned int slowBits = + GetNextWorkRequired(&blocks[6500], nullptr, consensus); + + arith_uint256 slowTarget; + slowTarget.SetCompact(slowBits); + + BOOST_CHECK(slowTarget > anchorTarget); + + // Extreme delay must never produce a target above powLimit. + blocks[6500].nTime = + blocks[6499].nTime + consensus.asertHalfLife * 16; + + const unsigned int extremeSlowBits = + GetNextWorkRequired(&blocks[6500], nullptr, consensus); + + BOOST_CHECK_EQUAL( + extremeSlowBits, + UintToArith256(consensus.powLimit).GetCompact()); +} + + +BOOST_AUTO_TEST_CASE(ASERT_reference_vector_run04_test) +{ + auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); + auto consensus = chainParams->GetConsensus(); + + consensus.asertActivationHeight = 2; + consensus.asertHalfLife = 2 * 24 * 60 * 60; + + std::vector blocks(3); + + blocks[0].pprev = nullptr; + blocks[0].nHeight = 0; + blocks[0].nTime = 0; + blocks[0].nBits = 0x01010000; + + blocks[1].pprev = &blocks[0]; + blocks[1].nHeight = 1; + blocks[1].nTime = 600; + blocks[1].nBits = 0x01010000; + + // BCHN official ASERT run04 vector: + // anchor height = 1 + // anchor ancestor time = 0 + // evaluation height = 2 + // evaluation time = 174000 + // expected next target = 0x01020000 + blocks[2].pprev = &blocks[1]; + blocks[2].nHeight = 2; + blocks[2].nTime = 174000; + blocks[2].nBits = 0x01010000; + + const unsigned int result = + GetNextWorkRequired(&blocks[2], nullptr, consensus); + + BOOST_CHECK_EQUAL(result, 0x01020000U); +} + + +BOOST_AUTO_TEST_CASE(ASERT_reference_vector_run05_test) +{ + auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); + auto consensus = chainParams->GetConsensus(); + + consensus.asertActivationHeight = 2; + consensus.asertHalfLife = 2 * 24 * 60 * 60; + + std::vector blocks(291); + + blocks[0].pprev = nullptr; + blocks[0].nHeight = 0; + blocks[0].nTime = 0; + blocks[0].nBits = 0x1d00ffff; + + blocks[1].pprev = &blocks[0]; + blocks[1].nHeight = 1; + blocks[1].nTime = 600; + blocks[1].nBits = 0x1d00ffff; + + // BCHN official ASERT run05 vector, iteration 1: + blocks[2].pprev = &blocks[1]; + blocks[2].nHeight = 2; + blocks[2].nTime = 0; + blocks[2].nBits = 0x1d00ffff; + + BOOST_CHECK_EQUAL( + GetNextWorkRequired(&blocks[2], nullptr, consensus), + 0x1d00fec5U); + + // BCHN official ASERT run05 vector, iteration 2: + // evaluation height = 290, time = 0 + for (int h = 3; h <= 290; ++h) { + blocks[h].pprev = &blocks[h - 1]; + blocks[h].nHeight = h; + blocks[h].nTime = 0; + blocks[h].nBits = 0x1d00ffff; + } + + BOOST_CHECK_EQUAL( + GetNextWorkRequired(&blocks[290], nullptr, consensus), + 0x1c7f62c0U); +} + + +BOOST_AUTO_TEST_CASE(ASERT_reference_vector_run06_test) +{ + auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); + auto consensus = chainParams->GetConsensus(); + + consensus.asertActivationHeight = 2; + consensus.asertHalfLife = 2 * 24 * 60 * 60; + + std::vector blocks(13); + + blocks[0].pprev = nullptr; + blocks[0].nHeight = 0; + blocks[0].nTime = 0; + blocks[0].nBits = 0x1802aee8; + + blocks[1].pprev = &blocks[0]; + blocks[1].nHeight = 1; + blocks[1].nTime = 600; + blocks[1].nBits = 0x1802aee8; + + const int64_t times[] = { + 0, 600, + 1200, 1310, 1327, 1739, 2219, 2604, + 2746, 7099, 7099, 7657, 8626 + }; + + const unsigned int expected[] = { + 0, + 0, + 0x1802aee8U, + 0x1802ad91U, + 0x1802abf8U, + 0x1802ab73U, + 0x1802ab20U, + 0x1802aa89U, + 0x1802a94bU, + 0x1802b39cU, + 0x1802b1f2U, + 0x1802b1d4U, + 0x1802b2dbU + }; + + for (int h = 2; h <= 12; ++h) { + blocks[h].pprev = &blocks[h - 1]; + blocks[h].nHeight = h; + blocks[h].nTime = times[h]; + blocks[h].nBits = 0x1802aee8; + + BOOST_CHECK_EQUAL( + GetNextWorkRequired(&blocks[h], nullptr, consensus), + expected[h]); + } +} + BOOST_AUTO_TEST_SUITE_END()