diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index f7d784d..89e812e 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -157,4 +157,31 @@ BOOST_AUTO_TEST_CASE(test_assumeutxo) BOOST_CHECK_EQUAL(out210.nChainTx, 200U); } + +BOOST_AUTO_TEST_CASE(RandomX_pre_activation_rejected) +{ + const CChainParams& params = Params(); + const CBlock& genesis = params.GenesisBlock(); + + CBlockHeader header; + header.nVersion = BLOCK_VERSION_DEFAULT; + header.SetAlgo(ALGO_RANDOMX); + header.hashPrevBlock = genesis.GetHash(); + header.hashMerkleRoot.SetNull(); + header.nTime = genesis.nTime + 1; + header.nBits = genesis.nBits; + header.nNonce = 0; + + BlockValidationState state; + + BOOST_CHECK(!Assert(m_node.chainman)->ProcessNewBlockHeaders( + {header}, + state, + params)); + + BOOST_CHECK(state.IsInvalid()); + BOOST_CHECK(state.GetResult() == BlockValidationResult::BLOCK_INVALID_HEADER); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "algo-inactive"); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/validation.cpp b/src/validation.cpp index 3a89c25..ddfa035 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1747,6 +1747,12 @@ static int64_t nTimeCallbacks = 0; static int64_t nTimeTotal = 0; static int64_t nBlocksTotal = 0; +static bool CheckContextualRandomXProofOfWork( + const CBlockHeader& block, + BlockValidationState& state, + const Consensus::Params& consensusParams, + const CBlockIndex* pindexPrev); + /** 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() * can fail if those validity checks fail (among other reasons). */ @@ -1781,6 +1787,16 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, return error("%s: Consensus::CheckBlock: %s", __func__, state.ToString()); } + // Re-check context-dependent proof of work when connecting blocks from + // disk as well. This is required because ContextualCheckBlockHeader() + // is not invoked by ConnectBlock(), including during -reindex-chainstate. + if (!fJustCheck && + block.GetHash() != m_params.GetConsensus().hashGenesisBlock && + block.GetAlgo() == ALGO_RANDOMX && + !CheckContextualRandomXProofOfWork(block, state, m_params.GetConsensus(), pindex->pprev)) { + return error("%s: contextual RandomX proof of work check failed: %s", __func__, state.ToString()); + } + // verify that the view's current state corresponds to the previous block uint256 hashPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->GetBlockHash(); assert(hashPrevBlock == view.GetBestBlock()); @@ -3108,15 +3124,98 @@ void CChainState::ReceivedBlockTransactions(const CBlock& block, CBlockIndex* pi } } -static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) +static bool CheckRandomXProofOfWork( + const CBlockHeader& block, + BlockValidationState& state, + const Consensus::Params& consensusParams, + const CBlockIndex* pindexPrev) { - // Check proof of work matches claimed amount. - if (fCheckPOW && !CheckProofOfWork(block.GetPoWAlgoHash(consensusParams), block.nBits, consensusParams)) - return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-hash", "proof of work failed"); + assert(pindexPrev != nullptr); + + const int blockHeight = pindexPrev->nHeight + 1; + + uint256 seed; + if (!GetRandomXSeed(pindexPrev, blockHeight, consensusParams, seed)) { + return state.Error("failed to resolve RandomX seed"); + } + + uint256 powHash; + if (!block.GetRandomXPoWHash(seed, powHash)) { + return state.Error("failed to calculate RandomX proof of work hash"); + } + + if (!CheckProofOfWork(powHash, block.nBits, consensusParams)) { + return state.Invalid( + BlockValidationResult::BLOCK_INVALID_HEADER, + "high-hash", + "RandomX proof of work failed"); + } return true; } +static bool CheckContextualRandomXProofOfWork( + const CBlockHeader& block, + BlockValidationState& state, + const Consensus::Params& consensusParams, + const CBlockIndex* pindexPrev) +{ + assert(pindexPrev != nullptr); + assert(block.GetAlgo() == ALGO_RANDOMX); + + const int blockHeight = pindexPrev->nHeight + 1; + + if (blockHeight < consensusParams.randomXActivationHeight) { + return state.Invalid( + BlockValidationResult::BLOCK_INVALID_HEADER, + "algo-inactive", + "RandomX proof of work is not active"); + } + + if (block.nBits != GetNextWorkRequired( + pindexPrev, + &block, + consensusParams, + ALGO_RANDOMX)) { + return state.Invalid( + BlockValidationResult::BLOCK_INVALID_HEADER, + "bad-diffbits", + "incorrect RandomX proof of work difficulty"); + } + + return CheckRandomXProofOfWork( + block, + state, + consensusParams, + pindexPrev); +} + +static bool CheckBlockHeader(const CBlockHeader& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW = true) +{ + if (!fCheckPOW) + return true; + + const int algo = block.GetAlgo(); + + // SHA256D proof of work is context-free. + if (algo == ALGO_SHA256D) { + if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "high-hash", "proof of work failed"); + + return true; + } + + // RandomX proof of work requires the previous block index in order to + // resolve the deterministic seed. It is checked by the contextual path. + if (algo == ALGO_RANDOMX) + return true; + + return state.Invalid( + BlockValidationResult::BLOCK_INVALID_HEADER, + "unknown-pow-algo", + "unknown proof of work algorithm"); +} + bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensus::Params& consensusParams, bool fCheckPOW, bool fCheckMerkleRoot) { // These are checks that are independent of context. @@ -3265,12 +3364,18 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, BlockValidatio const Consensus::Params& consensusParams = params.GetConsensus(); const int algo = block.GetAlgo(); - // RandomX is not active yet. Preserve the current SHA256D-only consensus. - if (algo != ALGO_SHA256D) - return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "algo-inactive", "PoW algorithm is not active"); - - if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams, algo)) - return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); + if (algo == ALGO_RANDOMX) { + if (!CheckContextualRandomXProofOfWork(block, state, consensusParams, pindexPrev)) + return false; + } else if (algo == ALGO_SHA256D) { + if (block.nBits != GetNextWorkRequired(pindexPrev, &block, consensusParams, ALGO_SHA256D)) + return state.Invalid(BlockValidationResult::BLOCK_INVALID_HEADER, "bad-diffbits", "incorrect proof of work"); + } else { + return state.Invalid( + BlockValidationResult::BLOCK_INVALID_HEADER, + "unknown-pow-algo", + "unknown proof of work algorithm"); + } // Check against checkpoints if (fCheckpointsEnabled) {