Activate ASERT difficulty adjustment at block 6500

This commit is contained in:
Capri 2026-09-08 13:03:50 +00:00
commit a16bb53457
Signed by: VextaProject
SSH key fingerprint: SHA256:739BZv8LvIDY2tR7lXLy/IQ6MaGTQtYcG9GNvCjmpVM
4 changed files with 290 additions and 1 deletions

View file

@ -180,6 +180,8 @@ public:
consensus.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100; consensus.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100;
consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 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.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100;
consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 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.difficultyMinActualTimespan = consensus.difficultyTargetTimespan * 92 / 100;
consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100; consensus.difficultyMaxActualTimespan = consensus.difficultyTargetTimespan * 116 / 100;
consensus.asertActivationHeight = 6500;
consensus.asertHalfLife = 2 * 24 * 60 * 60;
consensus.BIP65Height = 1351; consensus.BIP65Height = 1351;

View file

@ -121,6 +121,8 @@ struct Params {
int64_t difficultyMinActualTimespan; int64_t difficultyMinActualTimespan;
int64_t difficultyMaxActualTimespan; int64_t difficultyMaxActualTimespan;
int asertActivationHeight;
int64_t asertHalfLife;

View file

@ -22,13 +22,84 @@ unsigned int InitialDifficulty(const Consensus::Params& params)
return PowLimit(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<uint16_t>(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) 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) { if (pindexLast == nullptr) {
return InitialDifficulty(params); 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; const CBlockIndex* pindexFirst = pindexLast;
for (int i = 0; pindexFirst && i < params.difficultyAveragingWindow; i++) { for (int i = 0; pindexFirst && i < params.difficultyAveragingWindow; i++) {
pindexFirst = pindexFirst->pprev; pindexFirst = pindexFirst->pprev;

View file

@ -141,4 +141,214 @@ BOOST_AUTO_TEST_CASE(ChainParams_SIGNET_sanity)
sanity_check_chainparams(*m_node.args, CBaseChainParams::SIGNET); 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<CBlockIndex> 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<CBlockIndex> 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<CBlockIndex> 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<CBlockIndex> 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() BOOST_AUTO_TEST_SUITE_END()