fix: gate the RandomX PoW-verification skip on the in-index checkpoint

RandomXValidationRequired skipped RandomX verification for blocks below the
static top checkpoint (GetTotalBlocksEstimate), while the fork-rejection guard
uses the in-index checkpoint (GetLastCheckpoint). Once the checkpoint list
extends above the RandomX activation height, that asymmetry opens a gap during
IBD/eclipse in which a peer with no RandomX hashpower can get SHA256-grinded,
RandomX-forged blocks accepted (CheckProofOfWork hashes the header including the
attacker-controlled nSolution). Gate the skip on GetLastCheckpoint()->GetHeight()
so a block is PoW-exempt only when provably below a checkpoint the node has
locked into its index -- the same boundary the fork guard uses. Currently inert
(top checkpoint 2838000 < activation 2838976) but becomes live at the next
checkpoint refresh; the check runs under cs_main and only ever adds verification.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-08 05:48:39 +02:00
parent 810bd6712f
commit 7914fca0f2

View File

@@ -739,8 +739,18 @@ bool RandomXValidationRequired(int32_t height)
if (HUSH_LOADINGBLOCKS != 0)
return false;
extern bool fCheckpointsEnabled;
if (fCheckpointsEnabled && height < Checkpoints::GetTotalBlocksEstimate(Params().Checkpoints()))
return false;
// Gate the RandomX skip on the last checkpoint actually LOCKED INTO this node's block index
// (GetLastCheckpoint), NOT the static top checkpoint (GetTotalBlocksEstimate). The fork-rejection
// guard uses this same in-index boundary, so a block below it is provably on the checkpoint-pinned
// chain and cannot be a forged fork. Using the static boundary would, once checkpoints extend above
// the RandomX activation height, leave a gap (in-index checkpoint .. static top) during IBD/eclipse
// where a no-hashpower peer could get SHA256-grinded, RandomX-forged blocks accepted. Safe to walk
// mapBlockIndex here: called only under cs_main (ActivateBestChainStep + inline CheckRandomXSolution).
if (fCheckpointsEnabled) {
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(Params().Checkpoints());
if (pcheckpoint != NULL && height < pcheckpoint->GetHeight())
return false;
}
return true;
}