Normalize multi-algo chainwork and bootstrap RandomX difficulty
This commit is contained in:
parent
89600821d1
commit
287a84a533
8 changed files with 173 additions and 8 deletions
|
|
@ -174,10 +174,77 @@ arith_uint256 GetBlockProofBase(const CBlockIndex& block)
|
||||||
|
|
||||||
arith_uint256 GetBlockProof(const CBlockIndex& block)
|
arith_uint256 GetBlockProof(const CBlockIndex& block)
|
||||||
{
|
{
|
||||||
// Vexta is SHA256D-only, so block proof is based only on this block's nBits.
|
// Raw proof derived directly from this block's nBits.
|
||||||
return GetBlockProofBase(block);
|
return GetBlockProofBase(block);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arith_uint256 GetBlockProof(
|
||||||
|
const CBlockIndex& block,
|
||||||
|
const Consensus::Params& params)
|
||||||
|
{
|
||||||
|
// Preserve historical SHA256D chainwork exactly before multi-algo
|
||||||
|
// activation.
|
||||||
|
if (block.nHeight < params.randomXActivationHeight) {
|
||||||
|
return GetBlockProofBase(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Genesis has no parent state from which per-algo targets can be
|
||||||
|
// calculated. This path is only relevant if activation were configured
|
||||||
|
// at height zero.
|
||||||
|
if (block.pprev == nullptr) {
|
||||||
|
return GetBlockProofBase(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
const int blockAlgo = block.GetAlgo();
|
||||||
|
if (blockAlgo != ALGO_SHA256D && blockAlgo != ALGO_RANDOMX) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize heterogeneous PoW families using the geometric mean of the
|
||||||
|
// expected targets for all active algorithms, calculated from the same
|
||||||
|
// parent state. Competing SHA256D and RandomX children therefore receive
|
||||||
|
// the same chainwork increment regardless of their native difficulty
|
||||||
|
// scales.
|
||||||
|
arith_uint256 averageTarget(1);
|
||||||
|
|
||||||
|
for (int algo = 0; algo < NUM_ALGOS_IMPL; ++algo) {
|
||||||
|
const unsigned int nBits =
|
||||||
|
GetNextWorkRequired(block.pprev, nullptr, params, algo);
|
||||||
|
|
||||||
|
arith_uint256 target;
|
||||||
|
bool negative;
|
||||||
|
bool overflow;
|
||||||
|
target.SetCompact(nBits, &negative, &overflow);
|
||||||
|
|
||||||
|
if (negative ||
|
||||||
|
overflow ||
|
||||||
|
target == 0 ||
|
||||||
|
target > UintToArith256(params.powLimit)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const arith_uint256 root = target.ApproxNthRoot(NUM_ALGOS);
|
||||||
|
if (root == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
averageTarget *= root;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (averageTarget == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
arith_uint256 normalizedWork =
|
||||||
|
(~averageTarget / (averageTarget + 1)) + 1;
|
||||||
|
|
||||||
|
// The normalized work value itself is the post-activation chainwork
|
||||||
|
// increment. It is derived only from consensus difficulty state and does
|
||||||
|
// not use a branch-specific activation anchor, so competing forks always
|
||||||
|
// use the same chainwork scale.
|
||||||
|
return normalizedWork;
|
||||||
|
}
|
||||||
|
|
||||||
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from,
|
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from,
|
||||||
const CBlockIndex& tip, const Consensus::Params& params)
|
const CBlockIndex& tip, const Consensus::Params& params)
|
||||||
{
|
{
|
||||||
|
|
@ -189,7 +256,7 @@ int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& fr
|
||||||
r = from.nChainWork - to.nChainWork;
|
r = from.nChainWork - to.nChainWork;
|
||||||
sign = -1;
|
sign = -1;
|
||||||
}
|
}
|
||||||
r = r * arith_uint256(params.nPowTargetSpacing) / GetBlockProof(tip);
|
r = r * arith_uint256(params.nPowTargetSpacing) / GetBlockProof(tip, params);
|
||||||
if (r.bits() > 63) {
|
if (r.bits() > 63) {
|
||||||
return sign * std::numeric_limits<int64_t>::max();
|
return sign * std::numeric_limits<int64_t>::max();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,9 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
arith_uint256 GetBlockProof(const CBlockIndex& block);
|
arith_uint256 GetBlockProof(const CBlockIndex& block);
|
||||||
|
arith_uint256 GetBlockProof(
|
||||||
|
const CBlockIndex& block,
|
||||||
|
const Consensus::Params& params);
|
||||||
|
|
||||||
/** Return the time it would take to redo the work difference between from and to, assuming the current hashrate corresponds to the difficulty at tip, in seconds. */
|
/** Return the time it would take to redo the work difference between from and to, assuming the current hashrate corresponds to the difficulty at tip, in seconds. */
|
||||||
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from, const CBlockIndex& tip, const Consensus::Params&);
|
int64_t GetBlockProofEquivalentTime(const CBlockIndex& to, const CBlockIndex& from, const CBlockIndex& tip, const Consensus::Params&);
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,7 @@ public:
|
||||||
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
||||||
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.randomXSeedEpochLength = 2048;
|
consensus.randomXSeedEpochLength = 2048;
|
||||||
consensus.randomXSeedLag = 64;
|
consensus.randomXSeedLag = 64;
|
||||||
|
|
||||||
|
|
@ -229,6 +230,7 @@ public:
|
||||||
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
||||||
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.randomXSeedEpochLength = 2048;
|
consensus.randomXSeedEpochLength = 2048;
|
||||||
consensus.randomXSeedLag = 64;
|
consensus.randomXSeedLag = 64;
|
||||||
|
|
||||||
|
|
@ -386,6 +388,7 @@ public:
|
||||||
|
|
||||||
consensus.MinBIP9WarningHeight = 0;
|
consensus.MinBIP9WarningHeight = 0;
|
||||||
consensus.powLimit = uint256S("00000377ae000000000000000000000000000000000000000000000000000000");
|
consensus.powLimit = uint256S("00000377ae000000000000000000000000000000000000000000000000000000");
|
||||||
|
consensus.randomXInitialTarget = consensus.powLimit;
|
||||||
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;
|
||||||
|
|
@ -470,6 +473,7 @@ public:
|
||||||
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
consensus.asertHalfLife = 2 * 24 * 60 * 60;
|
||||||
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.randomXSeedEpochLength = 64;
|
consensus.randomXSeedEpochLength = 64;
|
||||||
consensus.randomXSeedLag = 8;
|
consensus.randomXSeedLag = 8;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,6 +125,7 @@ struct Params {
|
||||||
int64_t asertHalfLife;
|
int64_t asertHalfLife;
|
||||||
int fastRiseActivationHeight;
|
int fastRiseActivationHeight;
|
||||||
int randomXActivationHeight;
|
int randomXActivationHeight;
|
||||||
|
uint256 randomXInitialTarget;
|
||||||
int randomXSeedEpochLength;
|
int randomXSeedEpochLength;
|
||||||
int randomXSeedLag;
|
int randomXSeedLag;
|
||||||
|
|
||||||
|
|
|
||||||
11
src/pow.cpp
11
src/pow.cpp
|
|
@ -69,6 +69,11 @@ unsigned int InitialDifficulty(const Consensus::Params& params)
|
||||||
return PowLimit(params);
|
return PowLimit(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsigned int RandomXInitialDifficulty(const Consensus::Params& params)
|
||||||
|
{
|
||||||
|
return UintToArith256(params.randomXInitialTarget).GetCompact();
|
||||||
|
}
|
||||||
|
|
||||||
unsigned int CalculateASERT(const CBlockIndex* pindexLast, const Consensus::Params& params)
|
unsigned int CalculateASERT(const CBlockIndex* pindexLast, const Consensus::Params& params)
|
||||||
{
|
{
|
||||||
// Anchor ASERT to the last block before activation.
|
// Anchor ASERT to the last block before activation.
|
||||||
|
|
@ -205,7 +210,9 @@ static unsigned int CalculateMultiAlgoWorkRequired(
|
||||||
GetLastBlockIndexForAlgoFast(pindexLast, algo);
|
GetLastBlockIndexForAlgoFast(pindexLast, algo);
|
||||||
|
|
||||||
if (pindexPrevAlgo == nullptr) {
|
if (pindexPrevAlgo == nullptr) {
|
||||||
return InitialDifficulty(params);
|
return algo == ALGO_RANDOMX
|
||||||
|
? RandomXInitialDifficulty(params)
|
||||||
|
: InitialDifficulty(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.fPowNoRetargeting || params.fEasyPow) {
|
if (params.fPowNoRetargeting || params.fEasyPow) {
|
||||||
|
|
@ -318,7 +325,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead
|
||||||
}
|
}
|
||||||
|
|
||||||
if (algo == ALGO_RANDOMX) {
|
if (algo == ALGO_RANDOMX) {
|
||||||
return InitialDifficulty(params);
|
return RandomXInitialDifficulty(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Existing SHA256D difficulty logic below remains unchanged.
|
// Existing SHA256D difficulty logic below remains unchanged.
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ class CBlockIndex;
|
||||||
class uint256;
|
class uint256;
|
||||||
|
|
||||||
unsigned int InitialDifficulty(const Consensus::Params& params);
|
unsigned int InitialDifficulty(const Consensus::Params& params);
|
||||||
|
unsigned int RandomXInitialDifficulty(const Consensus::Params& params);
|
||||||
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params&, int algo);
|
unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader *pblock, const Consensus::Params&, int algo);
|
||||||
|
|
||||||
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
|
/** Check whether a block hash satisfies the proof-of-work requirement specified by nBits */
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,88 @@ BOOST_AUTO_TEST_CASE(CheckProofOfWork_test_zero_target)
|
||||||
BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
|
BOOST_CHECK(!CheckProofOfWork(hash, nBits, consensus));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(MultiAlgo_chainwork_normalization_test)
|
||||||
|
{
|
||||||
|
auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||||
|
auto consensus = chainParams->GetConsensus();
|
||||||
|
|
||||||
|
consensus.randomXActivationHeight = 40;
|
||||||
|
|
||||||
|
std::vector<CBlockIndex> blocks(40);
|
||||||
|
|
||||||
|
arith_uint256 shaTarget = UintToArith256(consensus.powLimit);
|
||||||
|
shaTarget >>= 8;
|
||||||
|
const unsigned int shaBits = shaTarget.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 = shaBits;
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before multi-algo activation, consensus-aware proof must remain
|
||||||
|
// bit-for-bit identical to the historical SHA256D raw proof.
|
||||||
|
BOOST_CHECK(
|
||||||
|
GetBlockProof(blocks[39], consensus) ==
|
||||||
|
GetBlockProof(blocks[39]));
|
||||||
|
|
||||||
|
// Height 40 is the first multi-algo height. Build two alternative
|
||||||
|
// children of the same parent: one SHA256D and one RandomX.
|
||||||
|
CBlockIndex shaChild;
|
||||||
|
shaChild.pprev = &blocks[39];
|
||||||
|
shaChild.nHeight = 40;
|
||||||
|
shaChild.nTime =
|
||||||
|
blocks[39].nTime + consensus.nPowTargetSpacing;
|
||||||
|
shaChild.nVersion =
|
||||||
|
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_SHA256D;
|
||||||
|
shaChild.nBits =
|
||||||
|
GetNextWorkRequired(
|
||||||
|
&blocks[39], nullptr, consensus, ALGO_SHA256D);
|
||||||
|
|
||||||
|
CBlockIndex randomXChild;
|
||||||
|
randomXChild.pprev = &blocks[39];
|
||||||
|
randomXChild.nHeight = 40;
|
||||||
|
randomXChild.nTime =
|
||||||
|
blocks[39].nTime + consensus.nPowTargetSpacing;
|
||||||
|
randomXChild.nVersion =
|
||||||
|
BLOCK_VERSION_DEFAULT | BLOCK_VERSION_RANDOMX;
|
||||||
|
randomXChild.nBits =
|
||||||
|
GetNextWorkRequired(
|
||||||
|
&blocks[39], nullptr, consensus, ALGO_RANDOMX);
|
||||||
|
|
||||||
|
// The two algorithms deliberately have different raw targets here.
|
||||||
|
BOOST_REQUIRE_NE(shaChild.nBits, randomXChild.nBits);
|
||||||
|
|
||||||
|
// Raw proof is therefore different.
|
||||||
|
BOOST_REQUIRE(
|
||||||
|
GetBlockProof(shaChild) !=
|
||||||
|
GetBlockProof(randomXChild));
|
||||||
|
|
||||||
|
// But normalized chainwork for competing children of the same parent
|
||||||
|
// must be identical. Otherwise one PoW family could win chain selection
|
||||||
|
// merely because its numeric difficulty scale differs.
|
||||||
|
BOOST_CHECK(
|
||||||
|
GetBlockProof(shaChild, consensus) ==
|
||||||
|
GetBlockProof(randomXChild, consensus));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
|
BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test)
|
||||||
{
|
{
|
||||||
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
const auto chainParams = CreateChainParams(*m_node.args, CBaseChainParams::MAIN);
|
||||||
|
|
@ -593,7 +675,7 @@ BOOST_AUTO_TEST_CASE(MultiAlgo_DAA_activation_boundary_test)
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(
|
BOOST_CHECK_EQUAL(
|
||||||
firstRandomXBits,
|
firstRandomXBits,
|
||||||
InitialDifficulty(consensus));
|
RandomXInitialDifficulty(consensus));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1337,7 +1337,7 @@ void CChainState::CheckForkWarningConditions()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pindexBestInvalid && pindexBestInvalid->nChainWork > m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip()) * 6)) {
|
if (pindexBestInvalid && pindexBestInvalid->nChainWork > m_chain.Tip()->nChainWork + (GetBlockProof(*m_chain.Tip(), m_params.GetConsensus()) * 6)) {
|
||||||
LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks longer than our best chain.\nChain state database corruption likely.\n", __func__);
|
LogPrintf("%s: Warning: Found invalid chain at least ~6 blocks longer than our best chain.\nChain state database corruption likely.\n", __func__);
|
||||||
SetfLargeWorkInvalidChainFound(true);
|
SetfLargeWorkInvalidChainFound(true);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3069,7 +3069,7 @@ CBlockIndex* BlockManager::AddToBlockIndex(
|
||||||
}
|
}
|
||||||
|
|
||||||
pindexNew->nTimeMax = (pindexNew->pprev ? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime) : pindexNew->nTime);
|
pindexNew->nTimeMax = (pindexNew->pprev ? std::max(pindexNew->pprev->nTimeMax, pindexNew->nTime) : pindexNew->nTime);
|
||||||
pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew);
|
pindexNew->nChainWork = (pindexNew->pprev ? pindexNew->pprev->nChainWork : 0) + GetBlockProof(*pindexNew, consensus_params);
|
||||||
pindexNew->RaiseValidity(BLOCK_VALID_TREE);
|
pindexNew->RaiseValidity(BLOCK_VALID_TREE);
|
||||||
if (pindexBestHeader == nullptr || pindexBestHeader->nChainWork < pindexNew->nChainWork)
|
if (pindexBestHeader == nullptr || pindexBestHeader->nChainWork < pindexNew->nChainWork)
|
||||||
pindexBestHeader = pindexNew;
|
pindexBestHeader = pindexNew;
|
||||||
|
|
@ -3959,7 +3959,7 @@ bool BlockManager::LoadBlockIndex(
|
||||||
}
|
}
|
||||||
|
|
||||||
nHeight = pindex-> nHeight;
|
nHeight = pindex-> nHeight;
|
||||||
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex);
|
pindex->nChainWork = (pindex->pprev ? pindex->pprev->nChainWork : 0) + GetBlockProof(*pindex, consensus_params);
|
||||||
pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime);
|
pindex->nTimeMax = (pindex->pprev ? std::max(pindex->pprev->nTimeMax, pindex->nTime) : pindex->nTime);
|
||||||
// We can link the chain of blocks for which we've received transactions at some point.
|
// We can link the chain of blocks for which we've received transactions at some point.
|
||||||
// Pruned nodes may have deleted the block.
|
// Pruned nodes may have deleted the block.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue