Add symmetric SHA256D RandomX difficulty adjustment
This commit is contained in:
parent
49cc3f79a5
commit
89600821d1
2 changed files with 289 additions and 10 deletions
130
src/pow.cpp
130
src/pow.cpp
|
|
@ -192,22 +192,132 @@ static unsigned int ApplyFastRiseProtection(
|
|||
return fastTarget.GetCompact();
|
||||
}
|
||||
|
||||
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params, int algo)
|
||||
static unsigned int CalculateMultiAlgoWorkRequired(
|
||||
const CBlockIndex* pindexLast,
|
||||
const Consensus::Params& params,
|
||||
int algo)
|
||||
{
|
||||
if (algo == ALGO_RANDOMX) {
|
||||
const CBlockIndex* pindexPrevAlgo =
|
||||
GetLastBlockIndexForAlgoFast(pindexLast, ALGO_RANDOMX);
|
||||
if (pindexLast == nullptr) {
|
||||
return InitialDifficulty(params);
|
||||
}
|
||||
|
||||
if (pindexPrevAlgo == nullptr) {
|
||||
return InitialDifficulty(params);
|
||||
}
|
||||
const CBlockIndex* pindexPrevAlgo =
|
||||
GetLastBlockIndexForAlgoFast(pindexLast, algo);
|
||||
|
||||
// RandomX will get its own per-algo retarget logic here.
|
||||
// For now, preserve the last RandomX difficulty.
|
||||
if (pindexPrevAlgo == nullptr) {
|
||||
return InitialDifficulty(params);
|
||||
}
|
||||
|
||||
if (params.fPowNoRetargeting || params.fEasyPow) {
|
||||
return pindexPrevAlgo->nBits;
|
||||
}
|
||||
|
||||
if (algo != ALGO_SHA256D) {
|
||||
// With two algorithms and a 10-minute global block target, each
|
||||
// algorithm is expected to appear approximately once every 20 minutes.
|
||||
// Use 10 samples per algorithm => 20 global blocks.
|
||||
const int64_t averagingBlocks =
|
||||
params.difficultyAveragingWindow * NUM_ALGOS;
|
||||
|
||||
const CBlockIndex* pindexFirst = pindexLast;
|
||||
for (int64_t i = 0; pindexFirst && i < averagingBlocks; ++i) {
|
||||
pindexFirst = pindexFirst->pprev;
|
||||
}
|
||||
|
||||
if (pindexFirst == nullptr) {
|
||||
return pindexPrevAlgo->nBits;
|
||||
}
|
||||
|
||||
const int64_t targetTimespan =
|
||||
averagingBlocks * params.nPowTargetSpacing;
|
||||
|
||||
int64_t actualTimespan =
|
||||
pindexLast->GetMedianTimePast() -
|
||||
pindexFirst->GetMedianTimePast();
|
||||
|
||||
// Preserve the existing Vexta 1/4 damping.
|
||||
actualTimespan =
|
||||
targetTimespan + (actualTimespan - targetTimespan) / 4;
|
||||
|
||||
// Preserve the existing 92% / 116% adjustment bounds, scaled to
|
||||
// the longer multi-algo averaging window.
|
||||
const int64_t minTimespan =
|
||||
targetTimespan * params.difficultyMinActualTimespan /
|
||||
params.difficultyTargetTimespan;
|
||||
|
||||
const int64_t maxTimespan =
|
||||
targetTimespan * params.difficultyMaxActualTimespan /
|
||||
params.difficultyTargetTimespan;
|
||||
|
||||
if (actualTimespan < minTimespan) {
|
||||
actualTimespan = minTimespan;
|
||||
}
|
||||
if (actualTimespan > maxTimespan) {
|
||||
actualTimespan = maxTimespan;
|
||||
}
|
||||
|
||||
arith_uint256 bnNew;
|
||||
bnNew.SetCompact(pindexPrevAlgo->nBits);
|
||||
|
||||
// Global chain-speed adjustment.
|
||||
bnNew *= actualTimespan;
|
||||
bnNew /= targetTimespan;
|
||||
|
||||
// Local per-algo balancing.
|
||||
//
|
||||
// For two algorithms:
|
||||
// ideal next algo: adjustment = 0
|
||||
// same algo twice: adjustment = +1 => 4% harder
|
||||
// algo skipped once: adjustment = -1 => 4% easier
|
||||
const int nAdjustments =
|
||||
pindexPrevAlgo->nHeight + NUM_ALGOS - 1 - pindexLast->nHeight;
|
||||
|
||||
static constexpr int64_t LOCAL_TARGET_ADJUSTMENT = 4;
|
||||
|
||||
if (nAdjustments > 0) {
|
||||
for (int i = 0; i < nAdjustments; ++i) {
|
||||
bnNew *= 100;
|
||||
bnNew /= 100 + LOCAL_TARGET_ADJUSTMENT;
|
||||
}
|
||||
} else if (nAdjustments < 0) {
|
||||
for (int i = 0; i < -nAdjustments; ++i) {
|
||||
bnNew *= 100 + LOCAL_TARGET_ADJUSTMENT;
|
||||
bnNew /= 100;
|
||||
|
||||
if (bnNew > UintToArith256(params.powLimit)) {
|
||||
bnNew = UintToArith256(params.powLimit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bnNew == 0) {
|
||||
bnNew = 1;
|
||||
}
|
||||
|
||||
if (bnNew > UintToArith256(params.powLimit)) {
|
||||
bnNew = UintToArith256(params.powLimit);
|
||||
}
|
||||
|
||||
return bnNew.GetCompact();
|
||||
}
|
||||
|
||||
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params& params, int algo)
|
||||
{
|
||||
if (algo != ALGO_SHA256D && algo != ALGO_RANDOMX) {
|
||||
return InitialDifficulty(params);
|
||||
}
|
||||
|
||||
// Before RandomX activation, preserve the existing SHA256D consensus
|
||||
// exactly. RandomX remains unavailable through validation.
|
||||
//
|
||||
// From activation onward both algorithms use the same symmetric
|
||||
// two-algorithm DAA.
|
||||
if (pindexLast != nullptr &&
|
||||
pindexLast->nHeight + 1 >= params.randomXActivationHeight) {
|
||||
return CalculateMultiAlgoWorkRequired(pindexLast, params, algo);
|
||||
}
|
||||
|
||||
if (algo == ALGO_RANDOMX) {
|
||||
return InitialDifficulty(params);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -532,6 +532,175 @@ BOOST_AUTO_TEST_CASE(FastRise_zero_and_negative_intervals_test)
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(MultiAlgo_DAA_activation_boundary_test)
|
||||
{
|
||||
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||
auto consensus = chainParams->GetConsensus();
|
||||
|
||||
consensus.randomXActivationHeight = 40;
|
||||
|
||||
std::vector<CBlockIndex> blocks(40);
|
||||
|
||||
arith_uint256 baseTarget = UintToArith256(consensus.powLimit);
|
||||
baseTarget >>= 8;
|
||||
const unsigned int baseBits = baseTarget.GetCompact();
|
||||
const int64_t startTime = 1800000000;
|
||||
|
||||
CBlockIndex* latest[NUM_ALGOS_IMPL]{};
|
||||
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
|
||||
blocks[i].nHeight = i;
|
||||
blocks[i].nTime = startTime + i * consensus.nPowTargetSpacing;
|
||||
blocks[i].nBits = baseBits;
|
||||
blocks[i].nVersion =
|
||||
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
|
||||
|
||||
for (int algo = 0; algo < NUM_ALGOS_IMPL; ++algo) {
|
||||
blocks[i].lastAlgoBlocks[algo] = latest[algo];
|
||||
}
|
||||
|
||||
latest[ALGO_SHA256D] = &blocks[i];
|
||||
}
|
||||
|
||||
// Height 39 -> next block is height 40, exactly the multi-algo
|
||||
// activation boundary.
|
||||
//
|
||||
// SHA256D is being requested directly after another SHA256D block,
|
||||
// so the local balancing rule must make it exactly one 4% step harder.
|
||||
const unsigned int firstMultiAlgoShaBits =
|
||||
GetNextWorkRequired(&blocks[39], nullptr, consensus, ALGO_SHA256D);
|
||||
|
||||
arith_uint256 compactBaseTarget;
|
||||
compactBaseTarget.SetCompact(baseBits);
|
||||
|
||||
arith_uint256 expectedShaTarget = compactBaseTarget;
|
||||
expectedShaTarget *= 100;
|
||||
expectedShaTarget /= 104;
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
firstMultiAlgoShaBits,
|
||||
expectedShaTarget.GetCompact());
|
||||
|
||||
// No RandomX block exists yet. For now this deliberately documents
|
||||
// the current fallback to InitialDifficulty(). This is NOT the final
|
||||
// RandomX launch calibration and will be replaced by an explicit
|
||||
// RandomX initial target before activation is enabled.
|
||||
const unsigned int firstRandomXBits =
|
||||
GetNextWorkRequired(&blocks[39], nullptr, consensus, ALGO_RANDOMX);
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
firstRandomXBits,
|
||||
InitialDifficulty(consensus));
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(MultiAlgo_DAA_local_balance_test)
|
||||
{
|
||||
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||
auto consensus = chainParams->GetConsensus();
|
||||
|
||||
// Test-only activation. Mainnet RandomX remains disabled in chainparams.
|
||||
consensus.randomXActivationHeight = 20;
|
||||
|
||||
std::vector<CBlockIndex> blocks(40);
|
||||
|
||||
arith_uint256 baseTarget = UintToArith256(consensus.powLimit);
|
||||
baseTarget >>= 8;
|
||||
const unsigned int baseBits = baseTarget.GetCompact();
|
||||
const int64_t startTime = 1800000000;
|
||||
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
blocks[i].pprev = i ? &blocks[i - 1] : nullptr;
|
||||
blocks[i].nHeight = i;
|
||||
blocks[i].nTime = startTime + i * consensus.nPowTargetSpacing;
|
||||
blocks[i].nBits = baseBits;
|
||||
|
||||
// Before activation the chain is SHA256D-only.
|
||||
// From activation onward, build an ideal alternating sequence.
|
||||
if (i >= consensus.randomXActivationHeight && (i & 1)) {
|
||||
blocks[i].nVersion =
|
||||
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_RANDOMX;
|
||||
} else {
|
||||
blocks[i].nVersion =
|
||||
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
|
||||
}
|
||||
}
|
||||
|
||||
auto rebuildAlgoLinks = [&]() {
|
||||
CBlockIndex* latest[NUM_ALGOS_IMPL]{};
|
||||
|
||||
for (auto& block : blocks) {
|
||||
for (int algo = 0; algo < NUM_ALGOS_IMPL; ++algo) {
|
||||
block.lastAlgoBlocks[algo] = latest[algo];
|
||||
}
|
||||
|
||||
const int algo = block.GetAlgo();
|
||||
BOOST_REQUIRE(algo >= 0);
|
||||
BOOST_REQUIRE(algo < NUM_ALGOS_IMPL);
|
||||
latest[algo] = █
|
||||
}
|
||||
};
|
||||
|
||||
rebuildAlgoLinks();
|
||||
|
||||
// Height 39 is RandomX. Therefore SHA256D is the ideal next algo.
|
||||
// With perfect 10-minute global spacing its target must stay unchanged.
|
||||
const unsigned int balancedShaBits =
|
||||
GetNextWorkRequired(&blocks[39], nullptr, consensus, ALGO_SHA256D);
|
||||
|
||||
BOOST_CHECK_EQUAL(balancedShaBits, baseBits);
|
||||
|
||||
// RandomX was just mined at height 39. Mining RandomX again immediately
|
||||
// must make that algo 4% harder.
|
||||
const unsigned int repeatedRandomXBits =
|
||||
GetNextWorkRequired(&blocks[39], nullptr, consensus, ALGO_RANDOMX);
|
||||
|
||||
arith_uint256 compactBaseTarget;
|
||||
compactBaseTarget.SetCompact(baseBits);
|
||||
|
||||
arith_uint256 expectedRepeatedRandomXTarget = compactBaseTarget;
|
||||
expectedRepeatedRandomXTarget *= 100;
|
||||
expectedRepeatedRandomXTarget /= 104;
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
repeatedRandomXBits,
|
||||
expectedRepeatedRandomXTarget.GetCompact());
|
||||
|
||||
// Now simulate SHA256D also being mined at height 39, directly after
|
||||
// SHA256D at height 38. SHA must become harder while the missing RandomX
|
||||
// side becomes easier.
|
||||
blocks[39].nVersion =
|
||||
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
|
||||
|
||||
rebuildAlgoLinks();
|
||||
|
||||
const unsigned int repeatedShaBits =
|
||||
GetNextWorkRequired(&blocks[39], nullptr, consensus, ALGO_SHA256D);
|
||||
|
||||
const unsigned int missingRandomXBits =
|
||||
GetNextWorkRequired(&blocks[39], nullptr, consensus, ALGO_RANDOMX);
|
||||
|
||||
arith_uint256 expectedRepeatedShaTarget = compactBaseTarget;
|
||||
expectedRepeatedShaTarget *= 100;
|
||||
expectedRepeatedShaTarget /= 104;
|
||||
|
||||
arith_uint256 expectedMissingRandomXTarget = compactBaseTarget;
|
||||
expectedMissingRandomXTarget *= 104;
|
||||
expectedMissingRandomXTarget /= 100;
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
repeatedShaBits,
|
||||
expectedRepeatedShaTarget.GetCompact());
|
||||
|
||||
BOOST_CHECK_EQUAL(
|
||||
missingRandomXBits,
|
||||
expectedMissingRandomXTarget.GetCompact());
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(RandomX_seed_height_test)
|
||||
{
|
||||
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||
|
|
|
|||
Loading…
Reference in a new issue