Add fixed multi-algo chainwork scaling

This commit is contained in:
Capri 2026-09-17 16:07:32 +00:00
commit ef32ac43a6
Signed by: VextaProject
SSH key fingerprint: SHA256:739BZv8LvIDY2tR7lXLy/IQ6MaGTQtYcG9GNvCjmpVM
4 changed files with 170 additions and 5 deletions

View file

@ -178,6 +178,54 @@ arith_uint256 GetBlockProof(const CBlockIndex& block)
return GetBlockProofBase(block); return GetBlockProofBase(block);
} }
static arith_uint256 ScaleMultiAlgoChainwork(
const arith_uint256& work,
uint32_t numerator,
uint32_t denominator)
{
if (work == 0 || numerator == 0 || denominator == 0) {
return 0;
}
if (numerator == denominator) {
return work;
}
// Compute floor(work * numerator / denominator) without first
// multiplying the full 256-bit value. base_uint multiplication silently
// truncates on overflow, so split work into quotient and remainder:
//
// work = quotient * denominator + remainder
//
// Then:
//
// floor(work * numerator / denominator)
// = quotient * numerator
// + floor(remainder * numerator / denominator)
//
// remainder is smaller than the 32-bit denominator, so its product with
// the 32-bit numerator fits comfortably inside 256 bits.
arith_uint256 quotient = work / denominator;
arith_uint256 remainder = work - quotient * denominator;
const arith_uint256 maxValue = ~arith_uint256(0);
if (quotient > maxValue / numerator) {
return 0;
}
quotient *= numerator;
remainder *= numerator;
remainder /= denominator;
if (quotient > maxValue - remainder) {
return 0;
}
return quotient + remainder;
}
arith_uint256 GetBlockProof( arith_uint256 GetBlockProof(
const CBlockIndex& block, const CBlockIndex& block,
const Consensus::Params& params) const Consensus::Params& params)
@ -238,11 +286,12 @@ arith_uint256 GetBlockProof(
arith_uint256 normalizedWork = arith_uint256 normalizedWork =
(~averageTarget / (averageTarget + 1)) + 1; (~averageTarget / (averageTarget + 1)) + 1;
// The normalized work value itself is the post-activation chainwork // Apply a fixed, branch-independent consensus scale. The default 1/1
// increment. It is derived only from consensus difficulty state and does // preserves the normalized-work value exactly.
// not use a branch-specific activation anchor, so competing forks always return ScaleMultiAlgoChainwork(
// use the same chainwork scale. normalizedWork,
return normalizedWork; params.multiAlgoChainworkScaleNumerator,
params.multiAlgoChainworkScaleDenominator);
} }
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from, int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from,

View file

@ -185,6 +185,8 @@ public:
consensus.fastRiseActivationHeight = 7000; consensus.fastRiseActivationHeight = 7000;
consensus.randomXActivationHeight = std::numeric_limits<int>::max(); consensus.randomXActivationHeight = std::numeric_limits<int>::max();
consensus.randomXInitialTarget = uint256S("000000359bc10000000000000000000000000000000000000000000000000000"); consensus.randomXInitialTarget = uint256S("000000359bc10000000000000000000000000000000000000000000000000000");
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 1;
consensus.randomXSeedEpochLength = 2048; consensus.randomXSeedEpochLength = 2048;
consensus.randomXSeedLag = 64; consensus.randomXSeedLag = 64;
@ -231,6 +233,8 @@ public:
consensus.fastRiseActivationHeight = 7000; consensus.fastRiseActivationHeight = 7000;
consensus.randomXActivationHeight = std::numeric_limits<int>::max(); consensus.randomXActivationHeight = std::numeric_limits<int>::max();
consensus.randomXInitialTarget = consensus.powLimit; consensus.randomXInitialTarget = consensus.powLimit;
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 1;
consensus.randomXSeedEpochLength = 2048; consensus.randomXSeedEpochLength = 2048;
consensus.randomXSeedLag = 64; consensus.randomXSeedLag = 64;
@ -389,6 +393,8 @@ public:
consensus.MinBIP9WarningHeight = 0; consensus.MinBIP9WarningHeight = 0;
consensus.powLimit = uint256S("00000377ae000000000000000000000000000000000000000000000000000000"); consensus.powLimit = uint256S("00000377ae000000000000000000000000000000000000000000000000000000");
consensus.randomXInitialTarget = consensus.powLimit; consensus.randomXInitialTarget = consensus.powLimit;
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 1;
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 27; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 27;
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = Consensus::BIP9Deployment::NEVER_ACTIVE;
consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT;
@ -474,6 +480,8 @@ public:
consensus.fastRiseActivationHeight = 7000; consensus.fastRiseActivationHeight = 7000;
consensus.randomXActivationHeight = std::numeric_limits<int>::max(); consensus.randomXActivationHeight = std::numeric_limits<int>::max();
consensus.randomXInitialTarget = consensus.powLimit; consensus.randomXInitialTarget = consensus.powLimit;
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 1;
consensus.randomXSeedEpochLength = 64; consensus.randomXSeedEpochLength = 64;
consensus.randomXSeedLag = 8; consensus.randomXSeedLag = 8;

View file

@ -126,6 +126,14 @@ struct Params {
int fastRiseActivationHeight; int fastRiseActivationHeight;
int randomXActivationHeight; int randomXActivationHeight;
uint256 randomXInitialTarget; uint256 randomXInitialTarget;
// Fixed, branch-independent scaling applied to normalized
// multi-algo chainwork after RandomX activation.
//
// A value of 1/1 preserves the current normalized-work scale.
uint32_t multiAlgoChainworkScaleNumerator{1};
uint32_t multiAlgoChainworkScaleDenominator{1};
int randomXSeedEpochLength; int randomXSeedEpochLength;
int randomXSeedLag; int randomXSeedLag;

View file

@ -153,6 +153,106 @@ BOOST_AUTO_TEST_CASE(MultiAlgo_chainwork_normalization_test)
BOOST_AUTO_TEST_CASE(MultiAlgo_chainwork_scale_test)
{
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
auto consensus = chainParams->GetConsensus();
consensus.randomXActivationHeight = 2;
CBlockIndex genesisLike;
genesisLike.nHeight = 0;
genesisLike.nTime = 1800000000;
genesisLike.nBits =
UintToArith256(consensus.powLimit).GetCompact();
genesisLike.nVersion =
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
CBlockIndex parent;
parent.pprev = &genesisLike;
parent.nHeight = 1;
parent.nTime =
genesisLike.nTime + consensus.nPowTargetSpacing;
parent.nBits =
UintToArith256(consensus.powLimit).GetCompact();
parent.nVersion =
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
parent.lastAlgoBlocks[ALGO_SHA256D] = &genesisLike;
CBlockIndex child;
child.pprev = &parent;
child.nHeight = 2;
child.nTime =
parent.nTime + consensus.nPowTargetSpacing;
child.nVersion =
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
child.nBits =
GetNextWorkRequired(
&parent,
nullptr,
consensus,
ALGO_SHA256D);
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 1;
const arith_uint256 unscaled =
GetBlockProof(child, consensus);
BOOST_REQUIRE(unscaled > 0);
consensus.multiAlgoChainworkScaleNumerator = 2;
consensus.multiAlgoChainworkScaleDenominator = 1;
BOOST_CHECK(
GetBlockProof(child, consensus) ==
unscaled * 2);
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 2;
BOOST_CHECK(
GetBlockProof(child, consensus) ==
unscaled / 2);
consensus.multiAlgoChainworkScaleNumerator = 1;
consensus.multiAlgoChainworkScaleDenominator = 0;
BOOST_CHECK(
GetBlockProof(child, consensus) == 0);
consensus.multiAlgoChainworkScaleNumerator = 0;
consensus.multiAlgoChainworkScaleDenominator = 1;
BOOST_CHECK(
GetBlockProof(child, consensus) == 0);
// Force the overflow guard using consensus state that produces an
// extremely large normalized work value. Post-activation chainwork is
// derived from expected per-algo targets, not from child.nBits.
auto overflowConsensus = consensus;
overflowConsensus.fPowNoRetargeting = true;
overflowConsensus.randomXActivationHeight = 2;
arith_uint256 tinyTarget(1);
const unsigned int tinyBits = tinyTarget.GetCompact();
overflowConsensus.randomXInitialTarget =
ArithToUint256(tinyTarget);
CBlockIndex overflowParent = parent;
overflowParent.nBits = tinyBits;
overflowParent.nVersion =
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
CBlockIndex overflowChild = child;
overflowChild.pprev = &overflowParent;
overflowChild.nHeight = 2;
overflowConsensus.multiAlgoChainworkScaleNumerator =
std::numeric_limits<uint32_t>::max();
overflowConsensus.multiAlgoChainworkScaleDenominator = 1;
BOOST_CHECK(
GetBlockProof(overflowChild, overflowConsensus) == 0);
}
BOOST_AUTO_TEST_CASE(MultiAlgo_chainwork_branch_accumulation_test) BOOST_AUTO_TEST_CASE(MultiAlgo_chainwork_branch_accumulation_test)
{ {
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN); auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);