From 00158bc7805673d022eabb9abb02d62257417b49 Mon Sep 17 00:00:00 2001 From: Capri Date: Tue, 15 Sep 2026 20:02:12 +0000 Subject: [PATCH] Make RandomX block templates activation-aware --- src/chainparams.cpp | 2 + src/miner.cpp | 6 +-- src/miner.h | 2 +- src/rpc/mining.cpp | 9 +++- src/validation.cpp | 27 ++++++++--- test/functional/mining_randomx_gbt.py | 69 +++++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 7 files changed, 104 insertions(+), 12 deletions(-) create mode 100755 test/functional/mining_randomx_gbt.py diff --git a/src/chainparams.cpp b/src/chainparams.cpp index c95f136..6b450cf 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -592,6 +592,8 @@ static void MaybeUpdateHeights(const ArgsManager& args, Consensus::Params& conse consensus.BIP65Height = int{height}; } else if (name == "csv") { consensus.CSVHeight = int{height}; + } else if (name == "randomx") { + consensus.randomXActivationHeight = int{height}; } else { throw std::runtime_error(strprintf("Invalid name (%s) for -testactivationheight=name@height.", arg)); } diff --git a/src/miner.cpp b/src/miner.cpp index e6be985..dddd8f7 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -26,7 +26,7 @@ #include #include -int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) +int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev, int algo) { int64_t nOldTime = pblock->nTime; int64_t nNewTime = std::max(pindexPrev->GetMedianTimePast()+1, GetAdjustedTime()); @@ -36,7 +36,7 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam // Updating time can change work required on testnet: if (consensusParams.fPowAllowMinDifficultyBlocks) - pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams, ALGO_SHA256D); + pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, consensusParams, algo); return nNewTime - nOldTime; } @@ -172,7 +172,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc // Fill in header pblock->hashPrevBlock = pindexPrev->GetBlockHash(); pblock->SetAlgo(algo); - UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev); + UpdateTime(pblock, chainparams.GetConsensus(), pindexPrev, algo); pblock->nBits = GetNextWorkRequired(pindexPrev, pblock, chainparams.GetConsensus(), algo); pblock->nNonce = 0; pblocktemplate->vTxSigOpsCost[0] = WITNESS_SCALE_FACTOR * GetLegacySigOpCount(*pblock->vtx[0]); diff --git a/src/miner.h b/src/miner.h index 465ce2b..591d94e 100644 --- a/src/miner.h +++ b/src/miner.h @@ -204,7 +204,7 @@ void RegenerateCommitments(CBlock& block, const CBlockIndex* pindexPrev, const C /** Modify the extranonce in a block */ void IncrementExtraNonce(CBlock* pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce); -int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); +int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev, int algo); /** Update an old GenerateCoinbaseCommitment from CreateNewBlock after the block txs have changed */ void RegenerateCommitments(CBlock& block, ChainstateManager& chainman); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 2b0cdad..2f722e2 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -639,7 +639,12 @@ static RPCHelpMan getblocktemplate() if (strAlgo == "sha256d") { algo = ALGO_SHA256D; } else if (strAlgo == "randomx") { - throw JSONRPCError(RPC_INVALID_PARAMETER, "RandomX is not active yet"); + const CBlockIndex* tip = chainman.ActiveChain().Tip(); + if (tip == nullptr || + tip->nHeight + 1 < Params().GetConsensus().randomXActivationHeight) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "RandomX is not active yet"); + } + algo = ALGO_RANDOMX; } else { throw JSONRPCError(RPC_INVALID_PARAMETER, "Unknown proof-of-work algorithm"); } @@ -809,7 +814,7 @@ static RPCHelpMan getblocktemplate() CBlock* pblock = &pblocktemplate->block; // pointer for convenience // Update nTime - UpdateTime(pblock, consensusParams, pindexPrev); + UpdateTime(pblock, consensusParams, pindexPrev, algo); pblock->nNonce = 0; // NOTE: If at some point we support pre-segwit miners post-segwit-activation, this needs to take segwit support into consideration diff --git a/src/validation.cpp b/src/validation.cpp index 18a3020..c763ffb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1751,7 +1751,8 @@ static bool CheckContextualRandomXProofOfWork( const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, - const CBlockIndex* pindexPrev); + const CBlockIndex* pindexPrev, + bool fCheckPOW); /** Apply the effects of this block (with given index) on the UTXO set represented by coins. * Validity checks that depend on the UTXO set are also done; ConnectBlock() @@ -1793,7 +1794,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, if (!fJustCheck && block.GetHash() != m_params.GetConsensus().hashGenesisBlock && block.GetAlgo() == ALGO_RANDOMX && - !CheckContextualRandomXProofOfWork(block, state, m_params.GetConsensus(), pindex->pprev)) { + !CheckContextualRandomXProofOfWork(block, state, m_params.GetConsensus(), pindex->pprev, true)) { return error("%s: contextual RandomX proof of work check failed: %s", __func__, state.ToString()); } @@ -3160,7 +3161,8 @@ static bool CheckContextualRandomXProofOfWork( const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, - const CBlockIndex* pindexPrev) + const CBlockIndex* pindexPrev, + bool fCheckPOW) { assert(pindexPrev != nullptr); assert(block.GetAlgo() == ALGO_RANDOMX); @@ -3185,6 +3187,9 @@ static bool CheckContextualRandomXProofOfWork( "incorrect RandomX proof of work difficulty"); } + if (!fCheckPOW) + return true; + return CheckRandomXProofOfWork( block, state, @@ -3197,6 +3202,16 @@ static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& st if (!fCheckPOW) return true; + // Genesis predates the multi-algo version-bit encoding. Preserve its + // historical SHA256D proof-of-work validation without treating unknown + // algorithm version bits as valid for any other block. + if (block.GetHash() == consensusParams.hashGenesisBlock) { + if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-hash", "proof of work failed"); + + return true; + } + const int algo = block.GetAlgo(); // SHA256D proof of work is context-free. @@ -3357,7 +3372,7 @@ CBlockIndex* BlockManager::GetLastCheckpoint(const CCheckpointData& data) * in ConnectBlock(). * Note that -reindex-chainstate skips the validation that happens here! */ -static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, BlockManager& blockman, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime, bool fCheckPOW = true) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { assert(pindexPrev != nullptr); const int nHeight = pindexPrev->nHeight + 1; @@ -3367,7 +3382,7 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio const int algo = block.GetAlgo(); if (algo == ALGO_RANDOMX) { - if (!CheckContextualRandomXProofOfWork(block, state, consensusParams, pindexPrev)) + if (!CheckContextualRandomXProofOfWork(block, state, consensusParams, pindexPrev, fCheckPOW)) return false; } else if (algo == ALGO_SHA256D) { if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams, ALGO_SHA256D)) @@ -3751,7 +3766,7 @@ bool TestBlockValidity(BlockValidationState& state, indexDummy.phashBlock = &block_hash; // NOTE: CheckBlockHeader is called by CheckBlock - if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime())) + if (!ContextualCheckBlockHeader(block, state, chainstate.m_blockman, chainparams, pindexPrev, GetAdjustedTime(), fCheckPOW)) return error("%s: Consensus::ContextualCheckBlockHeader: %s", __func__, state.ToString()); if (!CheckBlock(block, state, chainparams.GetConsensus(), fCheckPOW, fCheckMerkleRoot)) return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); diff --git a/test/functional/mining_randomx_gbt.py b/test/functional/mining_randomx_gbt.py new file mode 100755 index 0000000..33d7eb2 --- /dev/null +++ b/test/functional/mining_randomx_gbt.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 The Vexta Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +"""Test activation-aware RandomX getblocktemplate handling.""" + +from test_framework.blocktools import NORMAL_GBT_REQUEST_PARAMS +from test_framework.test_framework import DigiByteTestFramework +from test_framework.util import assert_equal, assert_raises_rpc_error + + +BLOCK_VERSION_ALGO = 15 << 8 +BLOCK_VERSION_SHA256D = 2 << 8 +BLOCK_VERSION_RANDOMX = 4 << 8 + + +class RandomXGetBlockTemplateTest(DigiByteTestFramework): + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.supports_cli = False + + def run_test(self): + node = self.nodes[0] + + self.log.info("RandomX GBT is rejected before activation") + assert_raises_rpc_error( + -8, + "RandomX is not active yet", + node.getblocktemplate, + NORMAL_GBT_REQUEST_PARAMS, + "randomx", + ) + + self.log.info("Default GBT remains SHA256D before activation") + sha_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + assert_equal(sha_template["height"], 1) + assert_equal( + sha_template["version"] & BLOCK_VERSION_ALGO, + BLOCK_VERSION_SHA256D, + ) + + self.log.info("Activate RandomX at block 1 on regtest only") + self.restart_node(0, extra_args=["-testactivationheight=randomx@1"]) + node = self.nodes[0] + + self.log.info("RandomX GBT is available exactly at activation height") + randomx_template = node.getblocktemplate( + NORMAL_GBT_REQUEST_PARAMS, + "randomx", + ) + assert_equal(randomx_template["height"], 1) + assert_equal( + randomx_template["version"] & BLOCK_VERSION_ALGO, + BLOCK_VERSION_RANDOMX, + ) + + self.log.info("Default GBT remains SHA256D after RandomX activation") + sha_template = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS) + assert_equal(sha_template["height"], 1) + assert_equal( + sha_template["version"] & BLOCK_VERSION_ALGO, + BLOCK_VERSION_SHA256D, + ) + + +if __name__ == "__main__": + RandomXGetBlockTemplateTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index ca7e2b9..d65baaa 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -94,6 +94,7 @@ BASE_SCRIPTS = [ 'wallet_backup.py --descriptors', # vv Tests less than 5m vv 'mining_getblocktemplate_longpoll.py', + 'mining_randomx_gbt.py', 'feature_maxuploadtarget.py', 'feature_block.py', 'rpc_fundrawtransaction.py --legacy-wallet',