From 57bc991cc9a8d365eb8f9f320be816d8e13b1b6f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 7 Sep 2026 14:17:06 +0700 Subject: [PATCH 01/15] refactor: make CheckSpecialTxInner member of CSpecialTxProcessor It doesn't change logic but simplify further refactoring and reduces conflicts with other PRs in the future --- src/evo/specialtxman.cpp | 39 +++++++++++++++++---------------------- src/evo/specialtxman.h | 4 ++++ 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index d8b89b43a89b..a2be9aa000b3 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -198,32 +198,29 @@ bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const return true; } -static bool CheckSpecialTxInner(CDeterministicMNManager& dmnman, llmq::CQuorumSnapshotManager& qsnapman, - const ChainstateManager& chainman, const llmq::CQuorumManager& qman, - const CChain* chain, - const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, - const std::optional& indexes, bool check_sigs, TxValidationState& state) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, const CBlockIndex* pindexPrev, + const CCoinsViewCache& view, const std::optional& indexes, + bool check_sigs, TxValidationState& state) { AssertLockHeld(::cs_main); if (!tx.HasExtraPayloadField()) return true; - if (!DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) { + if (!DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-dip3-inactive"); } try { switch (tx.nType) { case TRANSACTION_PROVIDER_REGISTER: - return CheckProRegTx(tx, pindexPrev, dmnman, view, chainman, state, check_sigs); + return CheckProRegTx(tx, pindexPrev, m_dmnman, view, m_chainman, state, check_sigs); case TRANSACTION_PROVIDER_UPDATE_SERVICE: - return CheckProUpServTx(tx, pindexPrev, dmnman, chainman, state, check_sigs); + return CheckProUpServTx(tx, pindexPrev, m_dmnman, m_chainman, state, check_sigs); case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: - return CheckProUpRegTx(tx, pindexPrev, dmnman, view, chainman, state, check_sigs); + return CheckProUpRegTx(tx, pindexPrev, m_dmnman, view, m_chainman, state, check_sigs); case TRANSACTION_PROVIDER_UPDATE_REVOKE: - return CheckProUpRevTx(tx, pindexPrev, dmnman, chainman, state, check_sigs); + return CheckProUpRevTx(tx, pindexPrev, m_dmnman, m_chainman, state, check_sigs); case TRANSACTION_COINBASE: { if (!tx.IsCoinBase()) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-invalid"); @@ -235,15 +232,15 @@ static bool CheckSpecialTxInner(CDeterministicMNManager& dmnman, llmq::CQuorumSn } } case TRANSACTION_QUORUM_COMMITMENT: - return llmq::CheckLLMQCommitment({dmnman, qsnapman, chainman, pindexPrev}, tx, state); + return llmq::CheckLLMQCommitment({m_dmnman, m_qsnapman, m_chainman, pindexPrev}, tx, state); case TRANSACTION_MNHF_SIGNAL: - return chain ? CheckMNHFTx(chainman, qman, *chain, tx, pindexPrev, state) : - CheckMNHFTx(chainman, qman, tx, pindexPrev, state); + return chain ? CheckMNHFTx(m_chainman, m_qman, *chain, tx, pindexPrev, state) : + CheckMNHFTx(m_chainman, m_qman, tx, pindexPrev, state); case TRANSACTION_ASSET_LOCK: - return CheckAssetLockTx(tx, state, DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)); + return CheckAssetLockTx(tx, state, DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)); case TRANSACTION_ASSET_UNLOCK: - return chain ? CheckAssetUnlockTx(chainman.m_blockman, qman, *chain, tx, pindexPrev, indexes, state) : - CheckAssetUnlockTx(chainman.m_blockman, qman, tx, pindexPrev, indexes, state); + return chain ? CheckAssetUnlockTx(m_chainman.m_blockman, m_qman, *chain, tx, pindexPrev, indexes, state) : + CheckAssetUnlockTx(m_chainman.m_blockman, m_qman, tx, pindexPrev, indexes, state); } } catch (const std::exception& e) { LogPrintf("%s -- failed: %s\n", __func__, e.what()); @@ -256,8 +253,7 @@ static bool CheckSpecialTxInner(CDeterministicMNManager& dmnman, llmq::CQuorumSn bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) { AssertLockHeld(::cs_main); - return CheckSpecialTxInner(m_dmnman, m_qsnapman, m_chainman, m_qman, nullptr, tx, pindexPrev, view, std::nullopt, check_sigs, - state); + return CheckSpecialTxInner(nullptr, tx, pindexPrev, view, std::nullopt, check_sigs, state); } static void HandleQuorumCommitment(const llmq::CFinalCommitment& qc, const std::vector& members, @@ -752,9 +748,8 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const TxValidationState tx_state; // At this moment CheckSpecialTx() may fail by 2 possible ways: // consensus failures and "TX_BAD_SPECIAL" - if (!CheckSpecialTxInner(m_dmnman, m_qsnapman, m_chainman, m_qman, &chainstate.m_chain, - *ptr_tx, pindex->pprev, view, indexes, - fCheckCbTxMerkleRoots, tx_state)) { + if (!CheckSpecialTxInner(&chainstate.m_chain, *ptr_tx, pindex->pprev, view, indexes, fCheckCbTxMerkleRoots, + tx_state)) { assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS || tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), strprintf("Special Transaction check failed (tx hash %s) %s", ptr_tx->GetHash().ToString(), tx_state.GetDebugMessage())); diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index b2e7a33ec62e..b22edbef6b09 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -20,6 +20,7 @@ class CCoinsViewCache; class CCreditPoolManager; class CDeterministicMNList; class CDeterministicMNManager; +class CRangesSet; class CTransaction; class ChainstateManager; class Chainstate; @@ -90,6 +91,9 @@ class CSpecialTxProcessor BlockValidationState& state, CDeterministicMNList& mnListRet); private: + bool CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, const CBlockIndex* pindexPrev, + const CCoinsViewCache& view, const std::optional& indexes, bool check_sigs, + TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); }; From e08a4372f032a650d43c6c65af928dc3ccc14a1f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 7 Sep 2026 14:21:55 +0700 Subject: [PATCH 02/15] refactor: use m_consensus_params instead of m_chainman.GetConsensus() in CSpecialTxProcessor --- src/evo/specialtxman.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index a2be9aa000b3..d5837fa20996 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -207,7 +207,7 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa if (!tx.HasExtraPayloadField()) return true; - if (!DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) { + if (!DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-dip3-inactive"); } @@ -320,8 +320,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul newList.DecreaseScores(); - const bool isMNRewardReallocation{ - DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_MN_RR)}; + const bool isMNRewardReallocation{DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_MN_RR)}; const bool is_v24_deployed{DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)}; // we skip the coinbase From 0439db01bbd7d16dbab39f41870da426166b83c5 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 7 Sep 2026 14:26:43 +0700 Subject: [PATCH 03/15] refactor: pass GetBlockSubsidy to CSpecialTxProcessor and avoid its recalculation twice GetBlockSubsidy is leightful helper and appears in perf as responsible for only 0.01% of calculation, so, commit is not threated as any noticeable performance improvement --- src/evo/specialtxman.cpp | 7 +++---- src/evo/specialtxman.h | 5 +++-- src/validation.cpp | 8 +++++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index d5837fa20996..f70924ad8f6b 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -686,7 +686,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul return true; } -bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, bool fJustCheck, +bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional& updatesRet) { AssertLockHeld(::cs_main); @@ -760,7 +760,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const LogPrint(BCLog::BENCHMARK, " - Loop: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeLoop * 0.000001); if (opt_cbTx.has_value()) { - if (!CheckCreditPoolDiffForBlock(block, pindex, *opt_cbTx, state)) { + if (!CheckCreditPoolDiffForBlock(block, pindex, *opt_cbTx, blockSubsidy, state)) { return error("CSpecialTxProcessor: CheckCreditPoolDiffForBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -922,7 +922,7 @@ bool CSpecialTxProcessor::UndoSpecialTxsInBlock(const Chainstate& chainstate, co } bool CSpecialTxProcessor::CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx, - BlockValidationState& state) + CAmount blockSubsidy, BlockValidationState& state) { AssertLockHeld(::cs_main); @@ -930,7 +930,6 @@ bool CSpecialTxProcessor::CheckCreditPoolDiffForBlock(const CBlock& block, const if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20)) return true; try { - const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_consensus_params); const auto creditPoolDiff = GetCreditPoolDiffForBlock(m_cpoolman, block, pindex->pprev, m_consensus_params, blockSubsidy, state); if (!creditPoolDiff.has_value()) return false; diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index b22edbef6b09..f16eaf486c42 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -5,6 +5,7 @@ #ifndef BITCOIN_EVO_SPECIALTXMAN_H #define BITCOIN_EVO_SPECIALTXMAN_H +#include #include #include #include @@ -72,7 +73,7 @@ class CSpecialTxProcessor bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, bool fJustCheck, + bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, std::optional& updatesRet) @@ -95,7 +96,7 @@ class CSpecialTxProcessor const CCoinsViewCache& view, const std::optional& indexes, bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx, - BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + CAmount blockSubsidy, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); }; /** diff --git a/src/validation.cpp b/src/validation.cpp index 54322912d356..758d255659be 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2381,9 +2381,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, blockundo.vtxundo.reserve(block.vtx.size() - 1); bool fDIP0001Active_context = DeploymentActiveAt(*pindex, params.GetConsensus(), Consensus::DEPLOYMENT_DIP0001); + const CAmount blockSubsidy = GetBlockSubsidy(pindex, params.GetConsensus()); + // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, view, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -2526,7 +2528,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // DASH : MODIFIED TO CHECK MASTERNODE PAYMENTS AND SUPERBLOCKS // TODO: resync data (both ways?) and try to reprocess this block later. - CAmount blockSubsidy = GetBlockSubsidy(pindex, params.GetConsensus()); CAmount feeReward = nFees; std::string strError; @@ -4846,8 +4847,9 @@ bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& in // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing BlockValidationState state; + const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_chainman.GetConsensus()); std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, inputs, false /*fJustCheck*/, false /*fScriptChecks*/, state, mnlist_updates_opt)) { + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, inputs, blockSubsidy, false /*fJustCheck*/, false /*fScriptChecks*/, state, mnlist_updates_opt)) { return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } From da11cdbab72ea11cbbed33b6091c63597073d5a1 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 02:01:19 +0700 Subject: [PATCH 04/15] refactor: hold BlockManager in CSpecialTxProcessor instead of chainman.m_blockman --- src/evo/chainhelper.cpp | 6 +++--- src/evo/chainhelper.h | 7 +++++-- src/evo/specialtxman.cpp | 4 ++-- src/evo/specialtxman.h | 10 ++++++++-- src/node/chainstate.cpp | 4 ++-- src/test/validation_chainstatemanager_tests.cpp | 4 ++-- 6 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index 7bcd66d40c6e..57ef71c63fd8 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -22,8 +22,8 @@ CChainstateHelper::CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmnman, const CMasternodeSync& mn_sync, llmq::CInstantSendManager& isman, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, - const Consensus::Params& consensus_params, const chainlock::Chainlocks& chainlocks, - const llmq::CQuorumManager& qman) : + const node::BlockManager& blockman, const Consensus::Params& consensus_params, + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman) : isman{isman}, mn_sync{mn_sync}, m_dmnman{dmnman}, @@ -33,7 +33,7 @@ CChainstateHelper::CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmn superblocks{std::make_unique()}, mn_payments{std::make_unique(dmnman, *superblocks, consensus_params)}, special_tx{std::make_unique(*credit_pool_manager, dmnman, *ehf_manager, qblockman, qsnapman, - chainman, consensus_params, chainlocks, qman)} + chainman, blockman, consensus_params, chainlocks, qman)} {} CChainstateHelper::~CChainstateHelper() = default; diff --git a/src/evo/chainhelper.h b/src/evo/chainhelper.h index 0036475fd282..a501dd82ceaf 100644 --- a/src/evo/chainhelper.h +++ b/src/evo/chainhelper.h @@ -37,6 +37,9 @@ class CQuorumBlockProcessor; class CQuorumManager; class CQuorumSnapshotManager; } // namespace llmq +namespace node { +class BlockManager; +} // namespace node class CChainstateHelper { private: @@ -59,8 +62,8 @@ class CChainstateHelper explicit CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmnman, const CMasternodeSync& mn_sync, llmq::CInstantSendManager& isman, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, - const Consensus::Params& consensus_params, const chainlock::Chainlocks& chainlocks, - const llmq::CQuorumManager& qman); + const node::BlockManager& blockman, const Consensus::Params& consensus_params, + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman); ~CChainstateHelper(); bool IsSuperblockValidationRequired(const CBlockIndex* const pindex); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index f70924ad8f6b..f67c7e9651ab 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -239,8 +239,8 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa case TRANSACTION_ASSET_LOCK: return CheckAssetLockTx(tx, state, DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)); case TRANSACTION_ASSET_UNLOCK: - return chain ? CheckAssetUnlockTx(m_chainman.m_blockman, m_qman, *chain, tx, pindexPrev, indexes, state) : - CheckAssetUnlockTx(m_chainman.m_blockman, m_qman, tx, pindexPrev, indexes, state); + return chain ? CheckAssetUnlockTx(m_blockman, m_qman, *chain, tx, pindexPrev, indexes, state) : + CheckAssetUnlockTx(m_blockman, m_qman, tx, pindexPrev, indexes, state); } } catch (const std::exception& e) { LogPrintf("%s -- failed: %s\n", __func__, e.what()); diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index f16eaf486c42..271ea6008c6a 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -38,6 +38,9 @@ class CQuorumBlockProcessor; class CQuorumManager; class CQuorumSnapshotManager; } // namespace llmq +namespace node { +class BlockManager; +} // namespace node extern RecursiveMutex cs_main; // NOLINT(readability-redundant-declaration) @@ -50,6 +53,7 @@ class CSpecialTxProcessor llmq::CQuorumBlockProcessor& m_qblockman; llmq::CQuorumSnapshotManager& m_qsnapman; const ChainstateManager& m_chainman; + const node::BlockManager& m_blockman; const Consensus::Params& m_consensus_params; const chainlock::Chainlocks& m_chainlocks; const llmq::CQuorumManager& m_qman; @@ -57,14 +61,16 @@ class CSpecialTxProcessor public: explicit CSpecialTxProcessor(CCreditPoolManager& cpoolman, CDeterministicMNManager& dmnman, CMNHFManager& mnhfman, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, - const ChainstateManager& chainman, const Consensus::Params& consensus_params, - const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman) : + const ChainstateManager& chainman, const node::BlockManager& blockman, + const Consensus::Params& consensus_params, const chainlock::Chainlocks& chainlocks, + const llmq::CQuorumManager& qman) : m_cpoolman(cpoolman), m_dmnman{dmnman}, m_mnhfman{mnhfman}, m_qblockman{qblockman}, m_qsnapman{qsnapman}, m_chainman(chainman), + m_blockman{blockman}, m_consensus_params{consensus_params}, m_chainlocks{chainlocks}, m_qman{qman} diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index e9dbde777940..62de2be383f1 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -163,8 +163,8 @@ static ChainstateLoadResult CompleteChainstateInitialization(ChainstateManager& // Initialize chain_helper chain_helper.reset(); chain_helper = std::make_unique(evodb, dmnman, *options.mn_sync, *options.isman, *(llmq_ctx->quorum_block_processor), - *(llmq_ctx->qsnapman), chainman, chainman.GetConsensus(), *options.chainlocks, - *(llmq_ctx->qman)); + *(llmq_ctx->qsnapman), chainman, chainman.m_blockman, chainman.GetConsensus(), + *options.chainlocks, *(llmq_ctx->qman)); if (options.reindex) { pblocktree->WriteReindexing(true); diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 50a8f8dc1145..898e27aea66c 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -64,8 +64,8 @@ static void DashChainstateSetup(ChainstateManager& chainman, // Initialize chain_helper node.chain_helper.reset(); node.chain_helper = std::make_unique(*node.evodb, *node.dmnman, *Assert(node.mn_sync), *Assert(node.isman), *(node.llmq_ctx->quorum_block_processor), - *(node.llmq_ctx->qsnapman), chainman, chainman.GetConsensus(), *Assert(node.chainlocks), - *(node.llmq_ctx->qman)); + *(node.llmq_ctx->qsnapman), chainman, chainman.m_blockman, chainman.GetConsensus(), + *Assert(node.chainlocks), *(node.llmq_ctx->qman)); } static void DashChainstateSetupClose(node::NodeContext& node) From 35954676df81f81eee87cf88d00d77e906ba4a52 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 02:10:17 +0700 Subject: [PATCH 05/15] refactor: pass BlockManager to CheckMNHFTx instead of ChainstateManager --- src/evo/mnhftx.cpp | 12 ++++++------ src/evo/mnhftx.h | 7 +++++-- src/evo/specialtxman.cpp | 4 ++-- src/test/evo_mnhf_tests.cpp | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index f36e9b0a80ca..487bb3f21f62 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -105,7 +105,7 @@ bool MNHFTxPayload::IsTriviallyValid(TxValidationState& state) const } template -static bool CheckMNHFTxImpl(const ChainstateManager& chainman, GetQuorum&& get_quorum, +static bool CheckMNHFTxImpl(const node::BlockManager& blockman, GetQuorum&& get_quorum, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) { if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) { @@ -126,7 +126,7 @@ static bool CheckMNHFTxImpl(const ChainstateManager& chainman, GetQuorum&& get_q return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-non-ehf"); } - const CBlockIndex* pindexQuorum = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(mnhfTx.signal.quorumHash)); + const CBlockIndex* pindexQuorum = WITH_LOCK(::cs_main, return blockman.LookupBlockIndex(mnhfTx.signal.quorumHash)); if (!pindexQuorum) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-hash"); } @@ -157,19 +157,19 @@ static bool CheckMNHFTxImpl(const ChainstateManager& chainman, GetQuorum&& get_q return true; } -bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, +bool CheckMNHFTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) { - return CheckMNHFTxImpl(chainman, [&](Consensus::LLMQType llmq_type, const uint256& quorum_hash) { + return CheckMNHFTxImpl(blockman, [&](Consensus::LLMQType llmq_type, const uint256& quorum_hash) { return qman.GetQuorum(llmq_type, quorum_hash); }, tx, pindexPrev, state); } -bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CChain& chain, +bool CheckMNHFTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CChain& chain, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) { AssertLockHeld(::cs_main); - return CheckMNHFTxImpl(chainman, [&](Consensus::LLMQType llmq_type, const uint256& quorum_hash) NO_THREAD_SAFETY_ANALYSIS { + return CheckMNHFTxImpl(blockman, [&](Consensus::LLMQType llmq_type, const uint256& quorum_hash) NO_THREAD_SAFETY_ANALYSIS { return qman.GetQuorum(llmq_type, quorum_hash, chain); }, tx, pindexPrev, state); } diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index b5d721360d49..41ce34bad0c1 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -30,6 +30,9 @@ struct RPCResult; namespace llmq { class CQuorumManager; } +namespace node { +class BlockManager; +} // namespace node // mnhf signal special transaction class MNHFTx @@ -156,8 +159,8 @@ class CMNHFManager : public AbstractEHFManager }; std::optional extractEHFSignal(const CTransaction& tx); -bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state); -bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CChain& chain, +bool CheckMNHFTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state); +bool CheckMNHFTx(const node::BlockManager& blockman, const llmq::CQuorumManager& qman, const CChain& chain, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index f67c7e9651ab..f99e60f47fda 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -234,8 +234,8 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa case TRANSACTION_QUORUM_COMMITMENT: return llmq::CheckLLMQCommitment({m_dmnman, m_qsnapman, m_chainman, pindexPrev}, tx, state); case TRANSACTION_MNHF_SIGNAL: - return chain ? CheckMNHFTx(m_chainman, m_qman, *chain, tx, pindexPrev, state) : - CheckMNHFTx(m_chainman, m_qman, tx, pindexPrev, state); + return chain ? CheckMNHFTx(m_blockman, m_qman, *chain, tx, pindexPrev, state) : + CheckMNHFTx(m_blockman, m_qman, tx, pindexPrev, state); case TRANSACTION_ASSET_LOCK: return CheckAssetLockTx(tx, state, DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)); case TRANSACTION_ASSET_UNLOCK: diff --git a/src/test/evo_mnhf_tests.cpp b/src/test/evo_mnhf_tests.cpp index 3db1f8c7c78e..dde064eec1c4 100644 --- a/src/test/evo_mnhf_tests.cpp +++ b/src/test/evo_mnhf_tests.cpp @@ -74,13 +74,13 @@ BOOST_AUTO_TEST_CASE(verify_mnhf_specialtx_tests) { // wrong quorum (we don't have any indeed) const CTransaction tx{CTransaction(CreateMNHFTx(hash, sig, bit))}; - CheckMNHFTx(*chainman, qman, CTransaction(tx), pindex, state); + CheckMNHFTx(chainman->m_blockman, qman, CTransaction(tx), pindex, state); BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-quorum-hash"); } { // non EHF fork const CTransaction tx{CTransaction(CreateMNHFTx(hash, sig, 28))}; - CheckMNHFTx(*chainman, qman, CTransaction(tx), pindex, state); + CheckMNHFTx(chainman->m_blockman, qman, CTransaction(tx), pindex, state); BOOST_CHECK_EQUAL(state.ToString(), "bad-mnhf-non-ehf"); } } From 0f9534b3960836889ac5188eede0b97e511afaca Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 02:13:56 +0700 Subject: [PATCH 06/15] refactor: drop validation.h from mnhftx.cpp --- src/evo/chainhelper.cpp | 2 +- src/evo/mnhftx.cpp | 19 +++++++++---------- src/evo/mnhftx.h | 11 ++++------- src/node/chainstate.cpp | 2 +- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index 57ef71c63fd8..8e92000851e1 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -29,7 +29,7 @@ CChainstateHelper::CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmn m_dmnman{dmnman}, credit_pool_manager{std::make_unique(evodb, chainman)}, m_chainlocks{chainlocks}, - ehf_manager{std::make_unique(evodb, chainman)}, + ehf_manager{std::make_unique(evodb, consensus_params)}, superblocks{std::make_unique()}, mn_payments{std::make_unique(dmnman, *superblocks, consensus_params)}, special_tx{std::make_unique(*credit_pool_manager, dmnman, *ehf_manager, qblockman, qsnapman, diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 487bb3f21f62..7014a6924d0e 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -16,7 +16,6 @@ #include #include -#include #include #include @@ -45,9 +44,9 @@ CMutableTransaction MNHFTxPayload::PrepareTx() const return tx; } -CMNHFManager::CMNHFManager(CEvoDB& evoDb, const ChainstateManager& chainman) : +CMNHFManager::CMNHFManager(CEvoDB& evoDb, const Consensus::Params& consensus_params) : m_evoDb(evoDb), - m_chainman{chainman} + m_consensus_params{consensus_params} { assert(globalInstance == nullptr); globalInstance = this; @@ -61,7 +60,7 @@ CMNHFManager::~CMNHFManager() CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev) { - if (!DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) return {}; + if (!DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_V20)) return {}; Signals signals_tmp = GetForBlock(pindexPrev); @@ -301,7 +300,7 @@ CMNHFManager::Signals CMNHFManager::GetForBlock(const CBlockIndex* pindex) LogPrintf("re-index EHF signals at block %d\n", pindex_top->nHeight); } CBlock block; - if (!ReadBlockFromDisk(block, pindex_top, m_chainman.GetConsensus())) { + if (!ReadBlockFromDisk(block, pindex_top, m_consensus_params)) { throw std::runtime_error("failed-getehfforblock-read"); } BlockValidationState state; @@ -341,7 +340,7 @@ std::optional CMNHFManager::GetFromCache(const CBlockInde } { LOCK(cs_cache); - if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) { + if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20)) { mnhfCache.insert(blockHash, signals); return signals; } @@ -351,7 +350,7 @@ std::optional CMNHFManager::GetFromCache(const CBlockInde mnhfCache.insert(blockHash, signals); return signals; } - if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_MN_RR)) { + if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_MN_RR)) { // before mn_rr activation we are safe if (m_evoDb.Read(std::make_pair(DB_SIGNALS, blockHash), signals)) { LOCK(cs_cache); @@ -366,7 +365,7 @@ void CMNHFManager::AddToCache(const Signals& signals, const CBlockIndex* const p { assert(pindex != nullptr); const uint256& blockHash = pindex->GetBlockHash(); - if (DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20) && + if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20) && !m_evoDb.WriteDerived(std::make_pair(DB_SIGNALS_v2, blockHash), signals)) { // A mismatch is local EvoDB corruption, not a statement about the // block. Abort here: some callers (miner, RPC) never pass through a @@ -390,14 +389,14 @@ void CMNHFManager::AddSignal(const CBlockIndex* const pindex, int bit) AddToCache(signals, pindex); } -bool CMNHFManager::ForceSignalDBUpdate() +bool CMNHFManager::ForceSignalDBUpdate(const CBlockIndex* tip) { // force ehf signals db update auto dbTx = m_evoDb.BeginTransaction(); const bool last_legacy = bls::bls_legacy_scheme.load(); bls::bls_legacy_scheme.store(false); - GetSignalsStage(m_chainman.ActiveTip()); + GetSignalsStage(tip); bls::bls_legacy_scheme.store(last_legacy); dbTx->Commit(); diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index 41ce34bad0c1..ead66e3fa876 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -24,7 +24,6 @@ class CBlockIndex; class CChain; class CEvoDB; class CTransaction; -class ChainstateManager; class TxValidationState; struct RPCResult; namespace llmq { @@ -97,12 +96,10 @@ class CMNHFManager : public AbstractEHFManager { private: CEvoDB& m_evoDb; - // TODO: move its functionallity of ProcessBlock, UndoBlock to specialtxman; - // it will help to drop dependency on m_chainman here (and validation.h) - // Secondly, store in database active EHF signals not for each block; + // TODO: store in database active EHF signals not for each block; // but quite opposite: keep only hash of block where signal is added. // TODO: implement migration to a new format - const ChainstateManager& m_chainman; + const Consensus::Params& m_consensus_params; static constexpr size_t MNHFCacheSize = 1000; Mutex cs_cache; @@ -113,7 +110,7 @@ class CMNHFManager : public AbstractEHFManager CMNHFManager() = delete; CMNHFManager(const CMNHFManager&) = delete; CMNHFManager& operator=(const CMNHFManager&) = delete; - explicit CMNHFManager(CEvoDB& evoDb, const ChainstateManager& chainman); + explicit CMNHFManager(CEvoDB& evoDb, const Consensus::Params& consensus_params); ~CMNHFManager() override; /** @@ -138,7 +135,7 @@ class CMNHFManager : public AbstractEHFManager */ void AddSignal(const CBlockIndex* const pindex, int bit) EXCLUSIVE_LOCKS_REQUIRED(!cs_cache); - bool ForceSignalDBUpdate() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_cache); + bool ForceSignalDBUpdate(const CBlockIndex* tip) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, !cs_cache); private: void AddToCache(const Signals& signals, const CBlockIndex* const pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_cache); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 62de2be383f1..cff3ee60a735 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -278,7 +278,7 @@ static ChainstateLoadResult CompleteChainstateInitialization(ChainstateManager& } } - if (!chain_helper->ehf_manager->ForceSignalDBUpdate()) { + if (!chain_helper->ehf_manager->ForceSignalDBUpdate(chainman.ActiveTip())) { return {ChainstateLoadStatus::FAILURE, _("Error upgrading evo database for EHF")}; } From 61cdb1ef6a68190c345fe82dc43e7cc707ca3cf9 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 02:26:16 +0700 Subject: [PATCH 07/15] refactor: pass consensus params and is_v24_active to CheckPro-Txs CheckSpecialTxInner still evaluates the flag from m_chainman for now and the next step is moving that to its callers --- src/evo/specialtxman.cpp | 65 +++++++++--------- src/evo/specialtxman.h | 21 +++--- src/test/evo_deterministicmns_tests.cpp | 89 +++++++++++++++++-------- src/test/evo_trivialvalidation.cpp | 4 +- 4 files changed, 109 insertions(+), 70 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index f99e60f47fda..cb493cb5e5dd 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -211,16 +211,18 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-dip3-inactive"); } + const bool is_v24_active{DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)}; + try { switch (tx.nType) { case TRANSACTION_PROVIDER_REGISTER: - return CheckProRegTx(tx, pindexPrev, m_dmnman, view, m_chainman, state, check_sigs); + return CheckProRegTx(tx, pindexPrev, m_dmnman, view, m_consensus_params, is_v24_active, state, check_sigs); case TRANSACTION_PROVIDER_UPDATE_SERVICE: - return CheckProUpServTx(tx, pindexPrev, m_dmnman, m_chainman, state, check_sigs); + return CheckProUpServTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state, check_sigs); case TRANSACTION_PROVIDER_UPDATE_REGISTRAR: - return CheckProUpRegTx(tx, pindexPrev, m_dmnman, view, m_chainman, state, check_sigs); + return CheckProUpRegTx(tx, pindexPrev, m_dmnman, view, m_consensus_params, is_v24_active, state, check_sigs); case TRANSACTION_PROVIDER_UPDATE_REVOKE: - return CheckProUpRevTx(tx, pindexPrev, m_dmnman, m_chainman, state, check_sigs); + return CheckProUpRevTx(tx, pindexPrev, m_dmnman, m_consensus_params, is_v24_active, state, check_sigs); case TRANSACTION_COINBASE: { if (!tx.IsCoinBase()) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-invalid"); @@ -237,7 +239,7 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa return chain ? CheckMNHFTx(m_blockman, m_qman, *chain, tx, pindexPrev, state) : CheckMNHFTx(m_blockman, m_qman, tx, pindexPrev, state); case TRANSACTION_ASSET_LOCK: - return CheckAssetLockTx(tx, state, DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)); + return CheckAssetLockTx(tx, state, is_v24_active); case TRANSACTION_ASSET_UNLOCK: return chain ? CheckAssetUnlockTx(m_blockman, m_qman, *chain, tx, pindexPrev, indexes, state) : CheckAssetUnlockTx(m_blockman, m_qman, tx, pindexPrev, indexes, state); @@ -984,7 +986,8 @@ static bool CheckHashSig(const ProTx& proTx, const CBLSPublicKey& pubKey, TxVali template std::optional GetValidatedPayload(const CTransaction& tx, gsl::not_null pindexPrev, - const ChainstateManager& chainman, TxValidationState& state) + const Consensus::Params& consensus_params, bool is_v24_active, + TxValidationState& state) { if (tx.nType != ProTx::SPECIALTX_TYPE) { state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type"); @@ -996,7 +999,8 @@ std::optional GetValidatedPayload(const CTransaction& tx, gsl::not_nullnVersion > DeploymentToProtxVersion(pindexPrev, chainman)) { + const bool is_v19_active{DeploymentActiveAfter(pindexPrev, consensus_params, Consensus::DEPLOYMENT_V19)}; + if (opt_ptx->nVersion > ProTxVersion::GetMax(is_v19_active, is_v24_active)) { state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version"); return std::nullopt; } @@ -1009,16 +1013,16 @@ std::optional GetValidatedPayload(const CTransaction& tx, gsl::not_null pindexPrev, const uint16_t state_version, - const uint16_t tx_version, const ChainstateManager& chainman, TxValidationState& state) +static bool IsVersionChangeValid(const uint16_t state_version, const uint16_t tx_version, bool is_v24_active, + TxValidationState& state) { - if (!DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)) { + if (!is_v24_active) { // New restrictions only apply after v24 deployment return true; } @@ -1031,18 +1035,16 @@ static bool IsVersionChangeValid(gsl::not_null pindexPrev, c return true; } -bool CheckProRegTx(const CTransaction& tx, gsl::not_null pindexPrev, - CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman, +bool CheckProRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, + const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active, TxValidationState& state, bool check_sigs) { - const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, chainman, state); + const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, is_v24_active, state); if (!opt_ptx) { // pass the state returned by the function above return false; } - const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)}; - // No longer allow legacy scheme masternode registration if (is_v24_active && opt_ptx->nVersion < ProTxVersion::BasicBLS) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-disallowed"); @@ -1169,10 +1171,11 @@ bool CheckProRegTx(const CTransaction& tx, gsl::not_null pin return true; } -bool CheckProUpServTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, - const ChainstateManager& chainman, TxValidationState& state, bool check_sigs) +bool CheckProUpServTx(const CTransaction& tx, gsl::not_null pindexPrev, + CDeterministicMNManager& dmnman, const Consensus::Params& consensus_params, bool is_v24_active, + TxValidationState& state, bool check_sigs) { - const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, chainman, state); + const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, is_v24_active, state); if (!opt_ptx) { // pass the state returned by the function above return false; @@ -1197,7 +1200,7 @@ bool CheckProUpServTx(const CTransaction& tx, gsl::not_null return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type-mismatch"); } - if (!IsVersionChangeValid(pindexPrev, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) { + if (!IsVersionChangeValid(dmn->pdmnState->nVersion, opt_ptx->nVersion, is_v24_active, state)) { // pass the state returned by the function above return false; } @@ -1206,8 +1209,7 @@ bool CheckProUpServTx(const CTransaction& tx, gsl::not_null // re-encodes its stored key, moving it to the basic-scheme unique-property slot. If another // masternode already holds that key under either encoding, the re-key in UpdateMN() would throw // out of block assembly, so reject the migration cleanly here. - if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24) && - IsSchemeMigration(dmn->pdmnState->nVersion, opt_ptx->nVersion) && + if (is_v24_active && IsSchemeMigration(dmn->pdmnState->nVersion, opt_ptx->nVersion) && mnList.HasOperatorKeyUnderAnyScheme(dmn->pdmnState->pubKeyOperator.Get(), /*self=*/opt_ptx->proTxHash)) { return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-key"); } @@ -1260,11 +1262,11 @@ bool CheckProUpServTx(const CTransaction& tx, gsl::not_null return true; } -bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null pindexPrev, - CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman, +bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, + const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active, TxValidationState& state, bool check_sigs) { - const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, chainman, state); + const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, is_v24_active, state); if (!opt_ptx) { // pass the state returned by the function above return false; @@ -1276,7 +1278,7 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash"); } - if (!IsVersionChangeValid(pindexPrev, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) { + if (!IsVersionChangeValid(dmn->pdmnState->nVersion, opt_ptx->nVersion, is_v24_active, state)) { // pass the state returned by the function above return false; } @@ -1286,7 +1288,7 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p // if that target slot is already held by another masternode -- under either encoding -- so the // re-key in UpdateMN() cannot collide and throw out of block assembly. Scoped to those two cases // so a pre-existing cross-scheme pair's non-migrating routine update is not blocked. - if (DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)) { + if (is_v24_active) { const bool key_changed{!(opt_ptx->pubKeyOperator == dmn->pdmnState->pubKeyOperator)}; const bool migrating{IsSchemeMigration(dmn->pdmnState->nVersion, opt_ptx->nVersion)}; if ((key_changed || migrating) && @@ -1341,10 +1343,11 @@ bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null p return true; } -bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, - const ChainstateManager& chainman, TxValidationState& state, bool check_sigs) +bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null pindexPrev, + CDeterministicMNManager& dmnman, const Consensus::Params& consensus_params, bool is_v24_active, + TxValidationState& state, bool check_sigs) { - const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, chainman, state); + const auto opt_ptx = GetValidatedPayload(tx, pindexPrev, consensus_params, is_v24_active, state); if (!opt_ptx) { // pass the state returned by the function above return false; @@ -1356,7 +1359,7 @@ bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null p return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash"); } - if (!IsVersionChangeValid(pindexPrev, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) { + if (!IsVersionChangeValid(dmn->pdmnState->nVersion, opt_ptx->nVersion, is_v24_active, state)) { // pass the state returned by the function above return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index 271ea6008c6a..f82c8b90a708 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -112,23 +112,26 @@ class CSpecialTxProcessor */ template std::optional GetValidatedPayload(const CTransaction& tx, gsl::not_null pindexPrev, - const ChainstateManager& chainman, TxValidationState& state); + const Consensus::Params& consensus_params, bool is_v24_active, + TxValidationState& state); /** Validates the bestCLSignature / bestCLHeightDiff fields embedded in a CbTx payload. */ bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const Consensus::Params& consensus_params, const CChain& chain, const llmq::CQuorumManager& qman, const chainlock::Chainlocks& chainlocks, BlockValidationState& state); -bool CheckProRegTx(const CTransaction& tx, gsl::not_null pindexPrev, - CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman, +bool CheckProRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, + const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active, TxValidationState& state, bool check_sigs); -bool CheckProUpServTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, - const ChainstateManager& chainman, TxValidationState& state, bool check_sigs); -bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null pindexPrev, - CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman, +bool CheckProUpServTx(const CTransaction& tx, gsl::not_null pindexPrev, + CDeterministicMNManager& dmnman, const Consensus::Params& consensus_params, bool is_v24_active, + TxValidationState& state, bool check_sigs); +bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, + const CCoinsViewCache& view, const Consensus::Params& consensus_params, bool is_v24_active, + TxValidationState& state, bool check_sigs); +bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null pindexPrev, + CDeterministicMNManager& dmnman, const Consensus::Params& consensus_params, bool is_v24_active, TxValidationState& state, bool check_sigs); -bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null pindexPrev, CDeterministicMNManager& dmnman, - const ChainstateManager& chainman, TxValidationState& state, bool check_sigs); /** diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index e76b07e6059c..b5b7ce5dd2fe 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -40,6 +40,11 @@ #include #include +static bool IsV24Active(const ChainstateManager& chainman) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) +{ + return DeploymentActiveAfter(chainman.ActiveChain().Tip(), chainman, Consensus::DEPLOYMENT_V24); +} + static CMutableTransaction CreateSpendTx(const ChainstateManager& chainman, SimpleUTXOMap& utxos, const CScript& scriptPayout, CAmount amount, const CKey& coinbaseKey) { CMutableTransaction tx; @@ -530,7 +535,8 @@ void FuncProUpRegTxV3OnLegacyValid(TestChainSetup& setup) { LOCK(cs_main); BOOST_CHECK(CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, val_state, /*check_sigs=*/true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), val_state, /*check_sigs=*/true)); } BOOST_CHECK(val_state.IsValid()); }; @@ -605,7 +611,8 @@ void FuncProUpRegTxV2CannotBypassV3PayoutCollateralReuse(TestChainSetup& setup) { LOCK(cs_main); BOOST_CHECK(!CheckProUpRegTx(CTransaction(tx_upreg), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, val_state, /*check_sigs=*/true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), val_state, /*check_sigs=*/true)); } BOOST_CHECK_EQUAL(val_state.GetRejectReason(), "bad-protx-payee-reuse"); } @@ -880,7 +887,8 @@ void FuncProRegTxRejectsInvalidDeserializedExtNetInfo(TestChainSetup& setup) { LOCK(cs_main); BOOST_CHECK(!CheckProRegTx(BuildExtNetInfoProRegTx(std::move(net_info)), chainman.ActiveChain().Tip(), - dmnman, chainman.ActiveChainstate().CoinsTip(), chainman, state, + dmnman, chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), state, /*check_sigs=*/false)); } BOOST_CHECK_EQUAL(state.GetResult(), TxValidationResult::TX_BAD_SPECIAL); @@ -946,9 +954,11 @@ void FuncDIP3Protx(TestChainSetup& setup) { LOCK(cs_main); BOOST_REQUIRE(CheckProRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), dummy_state, true)); BOOST_REQUIRE(CheckProRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), dummy_state, true)); } // But the signature should not verify anymore BOOST_REQUIRE(CheckTransactionSignature(tx, coins)); @@ -1055,9 +1065,11 @@ void FuncDIP3Protx(TestChainSetup& setup) { LOCK(cs_main); BOOST_REQUIRE(CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), dummy_state, true)); BOOST_REQUIRE(!CheckProUpRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, dummy_state, true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), dummy_state, true)); } BOOST_REQUIRE(CheckTransactionSignature(tx, coins)); BOOST_REQUIRE(!CheckTransactionSignature(tx2, coins)); @@ -1169,7 +1181,8 @@ void FuncProUpServInvalidNType(TestChainSetup& setup) TxValidationState check_state; { LOCK(cs_main); - BOOST_CHECK(!CheckProUpServTx(CTransaction(tx), tip_index(), dmnman, chainman, check_state, /*check_sigs=*/true)); + BOOST_CHECK(!CheckProUpServTx(CTransaction(tx), tip_index(), dmnman, chainman.GetConsensus(), + IsV24Active(chainman), check_state, /*check_sigs=*/true)); } BOOST_CHECK_EQUAL(check_state.GetRejectReason(), "bad-protx-type"); @@ -1194,7 +1207,8 @@ void FuncProUpServInvalidNType(TestChainSetup& setup) TxValidationState check_state; { LOCK(cs_main); - BOOST_CHECK(!CheckProUpServTx(CTransaction(tx), tip_index(), dmnman, chainman, check_state, /*check_sigs=*/true)); + BOOST_CHECK(!CheckProUpServTx(CTransaction(tx), tip_index(), dmnman, chainman.GetConsensus(), + IsV24Active(chainman), check_state, /*check_sigs=*/true)); } BOOST_CHECK_EQUAL(check_state.GetRejectReason(), "bad-protx-type-mismatch"); BOOST_CHECK(check_state.GetResult() == TxValidationResult::TX_CONSENSUS); @@ -1234,7 +1248,8 @@ void FuncProUpServInvalidNType(TestChainSetup& setup) TxValidationState check_state; { LOCK(cs_main); - BOOST_CHECK_MESSAGE(CheckProUpServTx(CTransaction(tx), tip_index(), dmnman, chainman, check_state, + BOOST_CHECK_MESSAGE(CheckProUpServTx(CTransaction(tx), tip_index(), dmnman, chainman.GetConsensus(), + IsV24Active(chainman), check_state, /*check_sigs=*/true), "unexpected rejection: " << check_state.GetRejectReason()); } @@ -1903,7 +1918,8 @@ void FuncMigrationRejectedWhenKeySquatted(TestChainV24SignalBeforeV19Setup& setu SignTransaction(tx, spent, setup.coinbaseKey); TxValidationState st; LOCK(cs_main); - BOOST_CHECK(!CheckProUpServTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, chainman, st, true)); + BOOST_CHECK(!CheckProUpServTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, chainman.GetConsensus(), + IsV24Active(chainman), st, true)); BOOST_CHECK_EQUAL(st.GetRejectReason(), "bad-protx-dup-key"); } @@ -1927,7 +1943,8 @@ void FuncMigrationRejectedWhenKeySquatted(TestChainV24SignalBeforeV19Setup& setu TxValidationState st; LOCK(cs_main); BOOST_CHECK(!CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, st, true)); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), st, true)); BOOST_CHECK_EQUAL(st.GetRejectReason(), "bad-protx-dup-key"); } }; @@ -1957,8 +1974,9 @@ void FuncProUpServTxMigratesLegacy(TestChainV24SignalBeforeV19Setup& setup) { TxValidationState val_state; LOCK(cs_main); - BOOST_REQUIRE_MESSAGE(CheckProUpServTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, chainman, - val_state, /*check_sigs=*/true), + BOOST_REQUIRE_MESSAGE(CheckProUpServTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, + chainman.GetConsensus(), IsV24Active(chainman), val_state, + /*check_sigs=*/true), "migration ProUpServTx rejected: " << val_state.GetRejectReason()); } setup.ProcessBlock({tx}); @@ -2185,10 +2203,12 @@ void FuncSameMnSameBlockVersionCrossingKeyRotation(TestChainV24SignalBeforeV19Se LOCK(cs_main); TxValidationState s1, s2; BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx1), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, s1, true), + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), s1, true), "tx1 rejected standalone: " << s1.GetRejectReason()); BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, s2, true), + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), s2, true), "tx2 rejected standalone: " << s2.GetRejectReason()); } @@ -2291,10 +2311,12 @@ void FuncSameMnSameBlockMigrationConsistent(TestChainV24SignalBeforeV19Setup& se LOCK(cs_main); TxValidationState s1, s2; BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx1), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, s1, true), + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), s1, true), "tx1 rejected standalone: " << s1.GetRejectReason()); BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, s2, true), + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), s2, true), "tx2 rejected standalone: " << s2.GetRejectReason()); } @@ -2493,10 +2515,12 @@ void FuncSameBlockCrossSchemeKeyPairRejected(TestChainV24SignalBeforeV19Setup& s LOCK(cs_main); TxValidationState s1, s2; BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx1), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, s1, true), + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), s1, true), "tx1 rejected standalone: " << s1.GetRejectReason()); BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx2), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, s2, true), + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), s2, true), "tx2 rejected standalone: " << s2.GetRejectReason()); } @@ -2602,7 +2626,8 @@ void FuncMempoolRejectsCrossSchemeKeyRace(TestChainV24SignalBeforeV19Setup& setu TxValidationState st; LOCK(cs_main); BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx_second), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, st, + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), st, /*check_sigs=*/true), "second claim should still pass its consensus check: " << st.GetRejectReason()); } @@ -2676,7 +2701,8 @@ void FuncProUpRegTxRejectsCrossSchemeKeyReuse(TestChainV24SignalBeforeV19Setup& auto check_upreg = [&](const CMutableTransaction& tx, TxValidationState& st) { LOCK(cs_main); return CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, st, /*check_sigs=*/true); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), IsV24Active(chainman), + st, /*check_sigs=*/true); }; // MN-A tries to take MN-B's key under the OTHER encoding, via a v1 payload. This is the reverse @@ -2751,7 +2777,8 @@ void FuncProRegTxRejectsCrossSchemeKeyReuse(TestChainV24SignalBeforeV19Setup& se auto check_proreg = [&](const CMutableTransaction& tx, TxValidationState& st) { LOCK(cs_main); return CheckProRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, st, /*check_sigs=*/true); + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), IsV24Active(chainman), st, + /*check_sigs=*/true); }; setup.MineToV19(); @@ -2873,8 +2900,8 @@ void FuncPreV24BehaviourUnchanged(TestChainSetup& setup) { TxValidationState val_state; LOCK(cs_main); - BOOST_CHECK_MESSAGE(CheckProUpServTx(CTransaction(tx_ups), chainman.ActiveChain().Tip(), dmnman, chainman, - val_state, /*check_sigs=*/true), + BOOST_CHECK_MESSAGE(CheckProUpServTx(CTransaction(tx_ups), chainman.ActiveChain().Tip(), dmnman, + chainman.GetConsensus(), IsV24Active(chainman), val_state, /*check_sigs=*/true), "pre-v24 v2 ProUpServTx wrongly rejected: " << val_state.GetRejectReason()); } setup.CreateAndProcessBlock({tx_ups}, coinbase_pk); @@ -2901,7 +2928,8 @@ void FuncPreV24BehaviourUnchanged(TestChainSetup& setup) TxValidationState val_state; LOCK(cs_main); BOOST_CHECK_MESSAGE(CheckProUpRegTx(CTransaction(tx_upreg), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, val_state, + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), val_state, /*check_sigs=*/true), "pre-v24 same-key ProUpRegTx wrongly rejected: " << val_state.GetRejectReason()); } @@ -2968,7 +2996,8 @@ void FuncStaleSpecialTxDoesNotPoisonTemplate(TestChainV24SignalBeforeV19Setup& s TxValidationState val_state; LOCK(cs_main); BOOST_REQUIRE(!CheckProRegTx(CTransaction(tx_stale), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, val_state, + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), val_state, /*check_sigs=*/true)); BOOST_REQUIRE_EQUAL(val_state.GetRejectReason(), "bad-protx-dup-key"); } @@ -3023,7 +3052,8 @@ void FuncProUpRegTxMigratesLegacySameKey(TestChainV24SignalBeforeV19Setup& setup { LOCK(cs_main); BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, val_state, + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), val_state, /*check_sigs=*/true), "same-key migration rejected: " << val_state.GetRejectReason()); } @@ -3052,7 +3082,8 @@ void FuncProUpRegTxMigratesLegacySameKey(TestChainV24SignalBeforeV19Setup& setup { LOCK(cs_main); BOOST_REQUIRE_MESSAGE(CheckProUpRegTx(CTransaction(tx), chainman.ActiveChain().Tip(), dmnman, - chainman.ActiveChainstate().CoinsTip(), chainman, val_state, + chainman.ActiveChainstate().CoinsTip(), chainman.GetConsensus(), + IsV24Active(chainman), val_state, /*check_sigs=*/true), "new-key rotation rejected: " << val_state.GetRejectReason()); } diff --git a/src/test/evo_trivialvalidation.cpp b/src/test/evo_trivialvalidation.cpp index 16184e0a584a..0f6772806a20 100644 --- a/src/test/evo_trivialvalidation.cpp +++ b/src/test/evo_trivialvalidation.cpp @@ -38,7 +38,9 @@ void TestTxHelper(const CMutableTransaction& tx, gsl::not_null& expected_error, const ChainstateManager& chainman) { TxValidationState dummy_state; - auto opt_payload = GetValidatedPayload(CTransaction{tx}, pindexPrev, chainman, dummy_state); + auto opt_payload = GetValidatedPayload(CTransaction{tx}, pindexPrev, chainman.GetConsensus(), + DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24), + dummy_state); BOOST_CHECK_EQUAL(opt_payload.has_value(), !expected_error.has_value()); From daaaad8a3cb4fd08eab77f7d3d8f44559ee95c3d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 02:36:45 +0700 Subject: [PATCH 08/15] refactor: pass is_v24_active into CheckSpecialTx --- src/evo/providertx_service.cpp | 4 +++- src/evo/specialtxman.cpp | 15 ++++++++------- src/evo/specialtxman.h | 7 ++++--- src/node/miner.cpp | 4 +++- src/validation.cpp | 4 +++- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/evo/providertx_service.cpp b/src/evo/providertx_service.cpp index 47961aba49d5..85a7a204a94a 100644 --- a/src/evo/providertx_service.cpp +++ b/src/evo/providertx_service.cpp @@ -347,7 +347,9 @@ std::optional Preflight(node::NodeContext& node, const CTransac } TxValidationState state; - if (!chain_helper.special_tx->CheckSpecialTx(tx, tip, chainman.ActiveChainstate().CoinsTip(), true, state)) { + const bool is_v24_active{DeploymentActiveAfter(tip, chainman, Consensus::DEPLOYMENT_V24)}; + if (!chain_helper.special_tx->CheckSpecialTx(tx, tip, is_v24_active, chainman.ActiveChainstate().CoinsTip(), true, + state)) { return Error(ProviderTxErrorCode::CONSENSUS_REJECTED, state.ToString(), state.GetRejectReason()); } return std::nullopt; diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index cb493cb5e5dd..b054a2af9f39 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -198,7 +198,8 @@ bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const return true; } -bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, const CBlockIndex* pindexPrev, +bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, + const CBlockIndex* pindexPrev, bool is_v24_active, const CCoinsViewCache& view, const std::optional& indexes, bool check_sigs, TxValidationState& state) { @@ -211,8 +212,6 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-dip3-inactive"); } - const bool is_v24_active{DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)}; - try { switch (tx.nType) { case TRANSACTION_PROVIDER_REGISTER: @@ -252,10 +251,11 @@ bool CSpecialTxProcessor::CheckSpecialTxInner(const CChain* chain, const CTransa return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-check"); } -bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) +bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, bool is_v24_active, + const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) { AssertLockHeld(::cs_main); - return CheckSpecialTxInner(nullptr, tx, pindexPrev, view, std::nullopt, check_sigs, state); + return CheckSpecialTxInner(nullptr, tx, pindexPrev, is_v24_active, view, std::nullopt, check_sigs, state); } static void HandleQuorumCommitment(const llmq::CFinalCommitment& qc, const std::vector& members, @@ -741,6 +741,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const indexes = std::move(creditPool.indexes); } + const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; for (size_t i = 0; i < block.vtx.size(); ++i) { // we validated CCbTx above, starts from the 2nd transaction if (i == 0 && block.vtx[i]->nType == TRANSACTION_COINBASE) continue; @@ -749,8 +750,8 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const TxValidationState tx_state; // At this moment CheckSpecialTx() may fail by 2 possible ways: // consensus failures and "TX_BAD_SPECIAL" - if (!CheckSpecialTxInner(&chainstate.m_chain, *ptr_tx, pindex->pprev, view, indexes, fCheckCbTxMerkleRoots, - tx_state)) { + if (!CheckSpecialTxInner(&chainstate.m_chain, *ptr_tx, pindex->pprev, is_v24_active, view, indexes, + fCheckCbTxMerkleRoots, tx_state)) { assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS || tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), strprintf("Special Transaction check failed (tx hash %s) %s", ptr_tx->GetHash().ToString(), tx_state.GetDebugMessage())); diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index f82c8b90a708..ac33ce8a3c23 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -77,7 +77,8 @@ class CSpecialTxProcessor { } - bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) + bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, bool is_v24_active, + const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional& updatesRet) @@ -99,8 +100,8 @@ class CSpecialTxProcessor private: bool CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, const CBlockIndex* pindexPrev, - const CCoinsViewCache& view, const std::optional& indexes, bool check_sigs, - TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool is_v24_active, const CCoinsViewCache& view, const std::optional& indexes, + bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx, CAmount blockSubsidy, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); }; diff --git a/src/node/miner.cpp b/src/node/miner.cpp index f40d761d46a3..5c3aaed9f944 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -369,6 +369,8 @@ bool BlockAssembler::TestPackage(uint64_t packageSize, unsigned int packageSigOp // - safe TXs in regard to ChainLocks bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& package) const { + const bool is_v24_active{ + DeploymentActiveAfter(m_chainstate.m_chain.Tip(), m_chainstate.m_chainman, Consensus::DEPLOYMENT_V24)}; for (CTxMemPool::txiter it : package) { if (!IsFinalTx(it->GetTx(), nHeight, m_lock_time_cutoff)) { return false; @@ -385,7 +387,7 @@ bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& packa // signature regardless, which is cheap enough here given how rare MNHF signals are. if (it->GetTx().IsSpecialTxVersion()) { TxValidationState tx_state; - if (!m_chain_helper.special_tx->CheckSpecialTx(it->GetTx(), m_chainstate.m_chain.Tip(), + if (!m_chain_helper.special_tx->CheckSpecialTx(it->GetTx(), m_chainstate.m_chain.Tip(), is_v24_active, m_chainstate.CoinsTip(), /*check_sigs=*/false, tx_state)) { return false; } diff --git a/src/validation.cpp b/src/validation.cpp index 758d255659be..03d5e3b91b4f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -996,7 +996,9 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // DoS scoring a node for non-critical errors, e.g. duplicate keys because a TX is received that was already // mined // NOTE: we use UTXO here and do NOT allow mempool txes as masternode collaterals - if (!m_chain_helper.special_tx->CheckSpecialTx(tx, m_active_chainstate.m_chain.Tip(), m_active_chainstate.CoinsTip(), true, state)) + const CBlockIndex* tip{m_active_chainstate.m_chain.Tip()}; + const bool is_v24_active{DeploymentActiveAfter(tip, m_active_chainstate.m_chainman, Consensus::DEPLOYMENT_V24)}; + if (!m_chain_helper.special_tx->CheckSpecialTx(tx, tip, is_v24_active, m_active_chainstate.CoinsTip(), true, state)) return false; if (m_pool.existsProviderTxConflict(tx)) { From e85ce57689bf8cceaeca966d231e4d611a29a99a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 18:06:41 +0700 Subject: [PATCH 09/15] refactor: pass is_v24_active into BuildNewListFromBlock/RebuildListFromBlock --- src/evo/specialtxman.cpp | 32 ++++++++++++------------- src/evo/specialtxman.h | 4 ++-- src/init.cpp | 12 ++++++---- src/node/miner.cpp | 4 +++- src/rpc/evo.cpp | 12 ++++++---- src/test/evo_deterministicmns_tests.cpp | 12 ++++++---- src/test/util/setup_common.cpp | 5 +++- 7 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index b054a2af9f39..998eac45e37a 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -276,18 +276,18 @@ static void HandleQuorumCommitment(const llmq::CFinalCommitment& qc, const std:: } bool CSpecialTxProcessor::BuildNewListFromBlock(const CBlock& block, gsl::not_null pindexPrev, - const CCoinsViewCache& view, bool debugLogs, + bool is_v24_active, const CCoinsViewCache& view, bool debugLogs, BlockValidationState& state, CDeterministicMNList& mnListRet) { AssertLockHeld(cs_main); CDeterministicMNList oldList = m_dmnman.GetListForBlock(pindexPrev); - return RebuildListFromBlock(block, pindexPrev, oldList, view, debugLogs, state, mnListRet); + return RebuildListFromBlock(block, pindexPrev, is_v24_active, oldList, view, debugLogs, state, mnListRet); } bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_null pindexPrev, - const CDeterministicMNList& prevList, const CCoinsViewCache& view, - bool debugLogs, BlockValidationState& state, - CDeterministicMNList& mnListRet) + bool is_v24_active, const CDeterministicMNList& prevList, + const CCoinsViewCache& view, bool debugLogs, BlockValidationState& state, + CDeterministicMNList& mnListRet) { // Verify that prevList either represents an empty/initial state (default-constructed), // or it matches the previous block's hash. @@ -323,7 +323,6 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul newList.DecreaseScores(); const bool isMNRewardReallocation{DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_MN_RR)}; - const bool is_v24_deployed{DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)}; // we skip the coinbase for (int i = 1; i < static_cast(block.vtx.size()); i++) { @@ -410,7 +409,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul // to each other and two of them could claim one operator key under different encodings. // Re-probe the list as rebuilt so far. AddMN() reports a duplicate by throwing, which // would escape block assembly, so reject cleanly here instead. - if (is_v24_deployed && + if (is_v24_active && newList.HasOperatorKeyUnderAnyScheme(dmn->pdmnState->pubKeyOperator.Get(), /*self=*/uint256())) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-key"); } @@ -456,8 +455,9 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul auto newState = std::make_shared(*dmn->pdmnState); const uint16_t current_version{static_cast(newState->nVersion)}; - const uint16_t target_version{is_v24_deployed ? std::max(current_version, opt_proTx->nVersion) : current_version}; - if (is_v24_deployed) { + const uint16_t target_version{is_v24_active ? std::max(current_version, opt_proTx->nVersion) + : current_version}; + if (is_v24_active) { // Extended addresses support in v24 means that the version can be updated newState->nVersion = opt_proTx->nVersion; } @@ -478,7 +478,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul newState->platformHTTPPort = 0; } } - if (is_v24_deployed && !SetStateVersion(*newState, target_version, dmn->nType, state)) { + if (is_v24_active && !SetStateVersion(*newState, target_version, dmn->nType, state)) { return false; } if (newState->IsBanned()) { @@ -497,7 +497,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul // against pindexPrev, so re-check against the list as rebuilt so far: if another // masternode holds this key under either encoding, the re-key in UpdateMN() would throw // out of block assembly. - if (is_v24_deployed && IsSchemeMigration(current_version, target_version) && + if (is_v24_active && IsSchemeMigration(current_version, target_version) && newList.HasOperatorKeyUnderAnyScheme(dmn->pdmnState->pubKeyOperator.Get(), /*self=*/opt_proTx->proTxHash)) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-key"); @@ -521,8 +521,8 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul auto newState = std::make_shared(*dmn->pdmnState); const uint16_t old_version{static_cast(newState->nVersion)}; const bool operator_changed{newState->pubKeyOperator != opt_proTx->pubKeyOperator}; - const uint16_t target_version{is_v24_deployed ? std::max(old_version, opt_proTx->nVersion) - : (operator_changed ? opt_proTx->nVersion : old_version)}; + const uint16_t target_version{is_v24_active ? std::max(old_version, opt_proTx->nVersion) + : (operator_changed ? opt_proTx->nVersion : old_version)}; // Per-transaction checks ran against pindexPrev, so an earlier transaction in this same // block is invisible to them. Re-evaluate against the list as rebuilt so far: this update @@ -533,7 +533,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul // routine update is not blocked. { const bool migrating{IsSchemeMigration(old_version, target_version)}; - if (is_v24_deployed && (operator_changed || migrating) && + if (is_v24_active && (operator_changed || migrating) && newList.HasOperatorKeyUnderAnyScheme(opt_proTx->pubKeyOperator.Get(), /*self=*/opt_proTx->proTxHash)) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-key"); @@ -585,7 +585,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul // preserved (never silently downgraded), so pick the target the same max-based way the registrar // path does and restore it below. Pre-v24 keep the historical reset-to-legacy behaviour. uint16_t target_version{ProTxVersion::LegacyBLS}; - if (is_v24_deployed) { + if (is_v24_active) { target_version = std::max(old_version, opt_proTx->nVersion); } newState->ResetOperatorFields(); @@ -786,7 +786,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CDeterministicMNList mn_list; if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) { - if (!BuildNewListFromBlock(block, pindex->pprev, view, true, state, mn_list)) { + if (!BuildNewListFromBlock(block, pindex->pprev, is_v24_active, view, true, state, mn_list)) { // pass the state returned by the function above return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index ac33ce8a3c23..26ef22df8951 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -88,13 +88,13 @@ class CSpecialTxProcessor // the returned list will not contain the correct block hash (we can't know it yet as the coinbase TX is not updated yet) - bool BuildNewListFromBlock(const CBlock& block, gsl::not_null pindexPrev, + bool BuildNewListFromBlock(const CBlock& block, gsl::not_null pindexPrev, bool is_v24_active, const CCoinsViewCache& view, bool debugLogs, BlockValidationState& state, CDeterministicMNList& mnListRet) EXCLUSIVE_LOCKS_REQUIRED(cs_main); // Variant that takes an explicit starting list instead of loading from GetListForBlock // Used for rebuilding diffs from trusted snapshots - bool RebuildListFromBlock(const CBlock& block, gsl::not_null pindexPrev, + bool RebuildListFromBlock(const CBlock& block, gsl::not_null pindexPrev, bool is_v24_active, const CDeterministicMNList& prevList, const CCoinsViewCache& view, bool debugLogs, BlockValidationState& state, CDeterministicMNList& mnListRet); diff --git a/src/init.cpp b/src/init.cpp index 63318d925702..31beb40db1ed 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2455,11 +2455,13 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) LogPrintf("Verifying and repairing masternode list diffs...\n"); const auto start{SteadyClock::now()}; // Create a callback that wraps CSpecialTxProcessor::BuildNewListFromBlock - auto build_list_func = [&node](const CBlock& block, const CBlockIndex* const pindexPrev, - const CDeterministicMNList& prevList, const CCoinsViewCache& view, - bool debugLogs, BlockValidationState& state, - CDeterministicMNList& mnListRet) -> bool { - return node.chain_helper->special_tx->RebuildListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet); + auto build_list_func = + [&node, &chainman](const CBlock& block, const CBlockIndex* const pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, bool debugLogs, + BlockValidationState& state, CDeterministicMNList& mnListRet) -> bool { + const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)}; + return node.chain_helper->special_tx->RebuildListFromBlock(block, pindexPrev, is_v24_active, prevList, + view, debugLogs, state, mnListRet); }; auto result = node.dmnman->RecalculateAndRepairDiffs(start_index, stop_index, build_list_func, true); diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 5c3aaed9f944..e3ced8ca331d 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -280,7 +280,9 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc BlockValidationState state; CDeterministicMNList mn_list; - if (!m_chain_helper.special_tx->BuildNewListFromBlock(*pblock, pindexPrev, m_chainstate.CoinsTip(), true, state, mn_list)) { + const bool is_v24_active{DeploymentActiveAfter(pindexPrev, m_chainstate.m_chainman, Consensus::DEPLOYMENT_V24)}; + if (!m_chain_helper.special_tx->BuildNewListFromBlock(*pblock, pindexPrev, is_v24_active, + m_chainstate.CoinsTip(), true, state, mn_list)) { throw std::runtime_error(strprintf("%s: BuildNewListFromBlock failed: %s", __func__, state.ToString())); } if (!CalcCbTxMerkleRootMNList(cbTx.merkleRootMNList, mn_list.to_sml(), state)) { diff --git a/src/rpc/evo.cpp b/src/rpc/evo.cpp index fbe5d0e046b6..3e267ee14a43 100644 --- a/src/rpc/evo.cpp +++ b/src/rpc/evo.cpp @@ -1610,11 +1610,13 @@ static UniValue evodb_verify_or_repair_impl(const JSONRPCRequest& request, bool } // Create a callback that wraps CSpecialTxProcessor::RebuildListFromBlock - auto build_list_func = [&chain_helper](const CBlock& block, const CBlockIndex* const pindexPrev, - const CDeterministicMNList& prevList, const CCoinsViewCache& view, - bool debugLogs, BlockValidationState& state, - CDeterministicMNList& mnListRet) -> bool { - return chain_helper.special_tx->RebuildListFromBlock(block, pindexPrev, prevList, view, debugLogs, state, mnListRet); + auto build_list_func = [&chain_helper, &chainman](const CBlock& block, const CBlockIndex* const pindexPrev, + const CDeterministicMNList& prevList, const CCoinsViewCache& view, + bool debugLogs, BlockValidationState& state, + CDeterministicMNList& mnListRet) -> bool { + const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)}; + return chain_helper.special_tx->RebuildListFromBlock(block, pindexPrev, is_v24_active, prevList, view, + debugLogs, state, mnListRet); }; // Call the dmnman method to do the work diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index b5b7ce5dd2fe..7d43bcfd9b4c 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -1238,7 +1238,8 @@ void FuncProUpServInvalidNType(TestChainSetup& setup) CDeterministicMNList mn_list; LOCK(cs_main); BOOST_CHECK(!chainman.ActiveChainstate().ChainHelper().special_tx->BuildNewListFromBlock( - block, tip_index(), chainman.ActiveChainstate().CoinsTip(), /*debugLogs=*/false, state, mn_list)); + block, tip_index(), IsV24Active(chainman), chainman.ActiveChainstate().CoinsTip(), /*debugLogs=*/false, + state, mn_list)); BOOST_CHECK_EQUAL(state.GetRejectReason(), expected_reason); } @@ -1507,7 +1508,8 @@ void FuncTestMempoolProRegReplacementUpdateConflict(TestChainSetup& setup) BlockValidationState state; CDeterministicMNList mn_list; BOOST_CHECK(!chainman.ActiveChainstate().ChainHelper().special_tx->BuildNewListFromBlock( - hazard_block, tip_index(), chainman.ActiveChainstate().CoinsTip(), /*debugLogs=*/false, state, mn_list)); + hazard_block, tip_index(), IsV24Active(chainman), chainman.ActiveChainstate().CoinsTip(), + /*debugLogs=*/false, state, mn_list)); BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-hash"); } @@ -2226,7 +2228,7 @@ void FuncSameMnSameBlockVersionCrossingKeyRotation(TestChainV24SignalBeforeV19Se auto& chain_helper = *Assert(setup.m_node.chain_helper.get()); try { rebuilt = chain_helper.special_tx->RebuildListFromBlock( - block, chainman.ActiveChain().Tip(), dmnman.GetListAtChainTip(), + block, chainman.ActiveChain().Tip(), IsV24Active(chainman), dmnman.GetListAtChainTip(), chainman.ActiveChainstate().CoinsTip(), /*debugLogs=*/false, block_state, mn_list_ret); } catch (const std::exception& e) { thrown = e.what(); @@ -2335,7 +2337,7 @@ void FuncSameMnSameBlockMigrationConsistent(TestChainV24SignalBeforeV19Setup& se auto& chain_helper = *Assert(setup.m_node.chain_helper.get()); BOOST_CHECK_NO_THROW( rebuilt = chain_helper.special_tx->RebuildListFromBlock( - block, chainman.ActiveChain().Tip(), dmnman.GetListAtChainTip(), + block, chainman.ActiveChain().Tip(), IsV24Active(chainman), dmnman.GetListAtChainTip(), chainman.ActiveChainstate().CoinsTip(), /*debugLogs=*/false, block_state, mn_list_ret)); } BOOST_REQUIRE_MESSAGE(rebuilt, "same-key migration across one block was rejected: " << block_state.GetRejectReason()); @@ -2542,7 +2544,7 @@ void FuncSameBlockCrossSchemeKeyPairRejected(TestChainV24SignalBeforeV19Setup& s auto& chain_helper = *Assert(setup.m_node.chain_helper.get()); BOOST_CHECK_NO_THROW( rebuilt = chain_helper.special_tx->RebuildListFromBlock( - block, chainman.ActiveChain().Tip(), dmnman.GetListAtChainTip(), + block, chainman.ActiveChain().Tip(), IsV24Active(chainman), dmnman.GetListAtChainTip(), chainman.ActiveChainstate().CoinsTip(), /*debugLogs=*/false, block_state, mn_list_ret)); } BOOST_CHECK_MESSAGE(!rebuilt, "a block claiming one operator key under two encodings was accepted"); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 060cb6517384..93eb7e1bb788 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -605,7 +605,10 @@ CBlock TestChainSetup::CreateBlock( Assert(cbTx.has_value()); BlockValidationState state; CDeterministicMNList mn_list; - if (!chainstate.ChainHelper().special_tx->BuildNewListFromBlock(block, chainstate.m_chain.Tip(), chainstate.CoinsTip(), true, state, mn_list)) { + const CBlockIndex* pindexPrev{chainstate.m_chain.Tip()}; + const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainstate.m_chainman, Consensus::DEPLOYMENT_V24)}; + if (!chainstate.ChainHelper().special_tx->BuildNewListFromBlock(block, pindexPrev, is_v24_active, + chainstate.CoinsTip(), true, state, mn_list)) { Assert(false); } if (!CalcCbTxMerkleRootMNList(cbTx->merkleRootMNList, mn_list.to_sml(), state)) { From d74a8d67d95e7cea5e5b6346d28ffca25457cf54 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 18:27:57 +0700 Subject: [PATCH 10/15] refactor: pass is_v24_active into ProcessSpecialTxsInBlock --- src/evo/specialtxman.cpp | 7 ++++--- src/evo/specialtxman.h | 5 +++-- src/validation.cpp | 7 ++++--- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 998eac45e37a..3905e8924d32 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -688,8 +688,10 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul return true; } -bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, - bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional& updatesRet) +bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, + bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, + bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, + std::optional& updatesRet) { AssertLockHeld(::cs_main); @@ -741,7 +743,6 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const indexes = std::move(creditPool.indexes); } - const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; for (size_t i = 0; i < block.vtx.size(); ++i) { // we validated CCbTx above, starts from the 2nd transaction if (i == 0 && block.vtx[i]->nType == TRANSACTION_COINBASE) continue; diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index 26ef22df8951..af3353931317 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -80,8 +80,9 @@ class CSpecialTxProcessor bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, bool is_v24_active, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, - bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional& updatesRet) + bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, bool is_v24_active, + const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, + BlockValidationState& state, std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/validation.cpp b/src/validation.cpp index 03d5e3b91b4f..7f03f2a63ac3 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2384,10 +2384,11 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, bool fDIP0001Active_context = DeploymentActiveAt(*pindex, params.GetConsensus(), Consensus::DEPLOYMENT_DIP0001); const CAmount blockSubsidy = GetBlockSubsidy(pindex, params.GetConsensus()); + const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -2540,7 +2541,6 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, Ticks(time_subsidy), Ticks(time_subsidy) / num_blocks_total); - const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; const SuperBlockCheckType check_superblock = !m_chain_helper->IsSuperblockValidationRequired(pindex) ? SuperBlockCheckType::NoCheck : is_v24_active ? SuperBlockCheckType::DisallowDuplicates : SuperBlockCheckType::AllowDuplicates; @@ -4850,8 +4850,9 @@ bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& in // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing BlockValidationState state; const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_chainman.GetConsensus()); + const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, inputs, blockSubsidy, false /*fJustCheck*/, false /*fScriptChecks*/, state, mnlist_updates_opt)) { + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, is_v24_active, inputs, blockSubsidy, false /*fJustCheck*/, false /*fScriptChecks*/, state, mnlist_updates_opt)) { return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } From 627e7483ade5837f70341a0f6453d93a326705b0 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 19:28:29 +0700 Subject: [PATCH 11/15] refactor: pass CChain into ProcessSpecialTxsInBlock --- src/evo/specialtxman.cpp | 14 +++++++------- src/evo/specialtxman.h | 8 ++++---- src/validation.cpp | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 3905e8924d32..3944e7e07e32 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -688,9 +688,10 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul return true; } -bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, - bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, - bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, +bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block, + const CBlockIndex* pindex, bool is_v24_active, + const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, + bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional& updatesRet) { AssertLockHeld(::cs_main); @@ -751,8 +752,8 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const TxValidationState tx_state; // At this moment CheckSpecialTx() may fail by 2 possible ways: // consensus failures and "TX_BAD_SPECIAL" - if (!CheckSpecialTxInner(&chainstate.m_chain, *ptr_tx, pindex->pprev, is_v24_active, view, indexes, - fCheckCbTxMerkleRoots, tx_state)) { + if (!CheckSpecialTxInner(&chain, *ptr_tx, pindex->pprev, is_v24_active, view, indexes, fCheckCbTxMerkleRoots, + tx_state)) { assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS || tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), strprintf("Special Transaction check failed (tx hash %s) %s", ptr_tx->GetHash().ToString(), tx_state.GetDebugMessage())); @@ -849,8 +850,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootQuorums: %.2fms [%.2fs]\n", 0.001 * (nTime6_2 - nTime6_1), nTimeMerkleQuorums * 0.000001); - if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_consensus_params, chainstate.m_chain, m_qman, - m_chainlocks, state)) { + if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_consensus_params, chain, m_qman, m_chainlocks, state)) { // pass the state returned by the function above return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index af3353931317..562015f6193c 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -80,10 +80,10 @@ class CSpecialTxProcessor bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, bool is_v24_active, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, bool is_v24_active, - const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, - BlockValidationState& state, std::optional& updatesRet) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block, const CBlockIndex* pindex, + bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, + bool fCheckCbTxMerkleRoots, BlockValidationState& state, + std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/validation.cpp b/src/validation.cpp index 7f03f2a63ac3..d09b96011c18 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2388,7 +2388,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -4852,7 +4852,7 @@ bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& in const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_chainman.GetConsensus()); const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, block, pindex, is_v24_active, inputs, blockSubsidy, false /*fJustCheck*/, false /*fScriptChecks*/, state, mnlist_updates_opt)) { + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, inputs, blockSubsidy, /*fJustCheck=*/false, /*fCheckCbTxMerkleRoots=*/false, state, mnlist_updates_opt)) { return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } From e961d9c1803d9b109d092322306891c0c9b3e954 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 19:44:58 +0700 Subject: [PATCH 12/15] refactor: record the background MN list hash from ConnectBlock ProcessSpecialTxsInBlock called Chainstate::RecordBackgroundMNListHash, the last reason it needed the complete Chainstate type. --- src/evo/specialtxman.cpp | 21 ++++++--------------- src/evo/specialtxman.h | 3 ++- src/validation.cpp | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 3944e7e07e32..6e6eb1fcadad 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -692,7 +692,8 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const const CBlockIndex* pindex, bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, - std::optional& updatesRet) + std::optional& updatesRet, + CDeterministicMNList& mn_list_ret) { AssertLockHeld(::cs_main); @@ -786,28 +787,18 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const LogPrint(BCLog::BENCHMARK, " - m_qblockman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime5 - nTime4), nTimeQuorum * 0.000001); - CDeterministicMNList mn_list; if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) { - if (!BuildNewListFromBlock(block, pindex->pprev, is_v24_active, view, true, state, mn_list)) { + if (!BuildNewListFromBlock(block, pindex->pprev, is_v24_active, view, true, state, mn_list_ret)) { // pass the state returned by the function above return false; } - mn_list.SetBlockHash(pindex->GetBlockHash()); + mn_list_ret.SetBlockHash(pindex->GetBlockHash()); - if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list, updatesRet)) { + if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list_ret, updatesRet)) { // pass the state returned by the function above return false; } } - if (!fJustCheck) { - // Persist the list produced by this chainstate's own connection of - // the snapshot base block (no-op for every other block). Snapshot - // activation may populate the shared MN-list cache with seeded - // state, so completion must not reconstruct this value through that - // cache. Before DIP3 activates, mn_list is the independently - // computed empty list. - chainstate.RecordBackgroundMNListHash(pindex, mn_list); - } int64_t nTime6 = GetTimeMicros(); nTimeDMN += nTime6 - nTime5; @@ -820,7 +811,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const static int64_t nTimeCbTxCL = 0; uint256 calculatedMerkleRootMNL; - if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list.to_sml(), state)) { + if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list_ret.to_sml(), state)) { // pass the state returned by the function above return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index 562015f6193c..3e64f7253b0a 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -83,7 +83,8 @@ class CSpecialTxProcessor bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block, const CBlockIndex* pindex, bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, - std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + std::optional& updatesRet, CDeterministicMNList& mn_list_ret) + EXCLUSIVE_LOCKS_REQUIRED(::cs_main); bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/validation.cpp b/src/validation.cpp index d09b96011c18..712d44f7fb23 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2388,10 +2388,20 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt)) { + CDeterministicMNList mn_list; + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt, mn_list)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } + if (!fJustCheck) { + // Persist the list produced by this chainstate's own connection of + // the snapshot base block (no-op for every other block). Snapshot + // activation may populate the shared MN-list cache with seeded + // state, so completion must not reconstruct this value through that + // cache. Before DIP3 activates, mn_list is the independently + // computed empty list. + RecordBackgroundMNListHash(pindex, mn_list); + } const auto time_2_1{SteadyClock::now()}; time_process_special += time_2_1 - time_2; @@ -4852,10 +4862,12 @@ bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& in const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_chainman.GetConsensus()); const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, inputs, blockSubsidy, /*fJustCheck=*/false, /*fCheckCbTxMerkleRoots=*/false, state, mnlist_updates_opt)) { + CDeterministicMNList mn_list; + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, inputs, blockSubsidy, /*fJustCheck=*/false, /*fCheckCbTxMerkleRoots=*/false, state, mnlist_updates_opt, mn_list)) return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } + RecordBackgroundMNListHash(pindex, mn_list); for (size_t i = 0; i < block.vtx.size(); i++) { const CTransactionRef& tx = block.vtx[i]; From e1f7c1f991bbcf78941ba59799157f7502964fc2 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 20:00:21 +0700 Subject: [PATCH 13/15] refactor: return the MN list once through MNListUpdates Make MNListUpdates the single non-optional result: ProcessBlock always fill it and consumers checks if there's actually diff non-empty by call of diff.HasChanges() instead of the checking optional. Copying an unchanged list is leightful operation due to using immer maps data-structure. --- src/evo/deterministicmns.cpp | 9 +++------ src/evo/deterministicmns.h | 8 ++++++-- src/evo/specialtxman.cpp | 14 +++++++------- src/evo/specialtxman.h | 5 ++--- src/validation.cpp | 34 +++++++++++++++------------------- 5 files changed, 33 insertions(+), 37 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index d652efaec732..42ebacf76707 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -25,7 +25,6 @@ #include #include -#include #include #include @@ -624,7 +623,7 @@ CDeterministicMNManager::~CDeterministicMNManager() = default; bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null pindex, BlockValidationState& state, const CDeterministicMNList& newList, - std::optional& updatesRet) + MNListUpdates& updatesRet) { AssertLockHeld(::cs_main); @@ -692,9 +691,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullactive()) { const auto counts{newList.GetCounts()}; @@ -723,7 +720,7 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null pindex, std::optional& updatesRet) +bool CDeterministicMNManager::UndoBlock(gsl::not_null pindex, MNListUpdates& updatesRet) { int nHeight = pindex->nHeight; uint256 blockHash = pindex->GetBlockHash(); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 4fb5dee91aef..5cf4fbc581b4 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -717,6 +717,10 @@ constexpr int llmq_max_blocks() { return max_blocks; } +/** Outcome of applying a block to the deterministic masternode list: the list + * before and after the block and the diff between them. A block that leaves + * the list untouched yields an empty diff (see diff.HasChanges()); for an + * undone block the lists are only filled when the diff has changes. */ struct MNListUpdates { CDeterministicMNList old_list; @@ -777,9 +781,9 @@ class CDeterministicMNManager ~CDeterministicMNManager(); bool ProcessBlock(const CBlock& block, gsl::not_null pindex, BlockValidationState& state, - const CDeterministicMNList& newList, std::optional& updatesRet) + const CDeterministicMNList& newList, MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); - bool UndoBlock(gsl::not_null pindex, std::optional& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(!cs); + bool UndoBlock(gsl::not_null pindex, MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(!cs); void UpdatedBlockTip(gsl::not_null pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 6e6eb1fcadad..830d4159d771 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -692,8 +692,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const const CBlockIndex* pindex, bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, - std::optional& updatesRet, - CDeterministicMNList& mn_list_ret) + MNListUpdates& updatesRet) { AssertLockHeld(::cs_main); @@ -787,14 +786,15 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const LogPrint(BCLog::BENCHMARK, " - m_qblockman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime5 - nTime4), nTimeQuorum * 0.000001); + CDeterministicMNList mn_list; if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) { - if (!BuildNewListFromBlock(block, pindex->pprev, is_v24_active, view, true, state, mn_list_ret)) { + if (!BuildNewListFromBlock(block, pindex->pprev, is_v24_active, view, true, state, mn_list)) { // pass the state returned by the function above return false; } - mn_list_ret.SetBlockHash(pindex->GetBlockHash()); + mn_list.SetBlockHash(pindex->GetBlockHash()); - if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list_ret, updatesRet)) { + if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list, updatesRet)) { // pass the state returned by the function above return false; } @@ -811,7 +811,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const static int64_t nTimeCbTxCL = 0; uint256 calculatedMerkleRootMNL; - if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list_ret.to_sml(), state)) { + if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list.to_sml(), state)) { // pass the state returned by the function above return false; } @@ -882,7 +882,7 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const return true; } -bool CSpecialTxProcessor::UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, std::optional& updatesRet) +bool CSpecialTxProcessor::UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, MNListUpdates& updatesRet) { AssertLockHeld(::cs_main); diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index 3e64f7253b0a..3a7f4a3f40b4 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -83,9 +83,8 @@ class CSpecialTxProcessor bool ProcessSpecialTxsInBlock(Chainstate& chainstate, const CChain& chain, const CBlock& block, const CBlockIndex* pindex, bool is_v24_active, const CCoinsViewCache& view, CAmount blockSubsidy, bool fJustCheck, bool fCheckCbTxMerkleRoots, BlockValidationState& state, - std::optional& updatesRet, CDeterministicMNList& mn_list_ret) - EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, std::optional& updatesRet) + MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + bool UndoSpecialTxsInBlock(const Chainstate& chainstate, const CBlock& block, const CBlockIndex* pindex, MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); diff --git a/src/validation.cpp b/src/validation.cpp index 712d44f7fb23..053fe8f4bcc1 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2034,8 +2034,8 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn return DISCONNECT_FAILED; } - std::optional mnlist_updates_opt{std::nullopt}; - if (!m_chain_helper->special_tx->UndoSpecialTxsInBlock(*this, block, pindex, mnlist_updates_opt)) { + MNListUpdates mnlist_updates; + if (!m_chain_helper->special_tx->UndoSpecialTxsInBlock(*this, block, pindex, mnlist_updates)) { error("DisconnectBlock(): UndoSpecialTxsInBlock failed"); return DISCONNECT_FAILED; } @@ -2093,10 +2093,9 @@ DisconnectResult Chainstate::DisconnectBlock(const CBlock& block, const CBlockIn view.SetBestBlock(pindex->pprev->GetBlockHash()); m_evoDb.WriteBestBlock(EvoDbIdentity(), pindex->pprev->GetBlockHash()); - if (this == &m_chainman.ActiveChainstate() && mnlist_updates_opt.has_value()) { - auto& mnlu = mnlist_updates_opt.value(); - GetMainSignals().NotifyMasternodeListChanged(true, mnlu.old_list, mnlu.diff); - uiInterface.NotifyMasternodeListChanged(mnlu.new_list, pindex->pprev); + if (this == &m_chainman.ActiveChainstate() && mnlist_updates.diff.HasChanges()) { + GetMainSignals().NotifyMasternodeListChanged(true, mnlist_updates.old_list, mnlist_updates.diff); + uiInterface.NotifyMasternodeListChanged(mnlist_updates.new_list, pindex->pprev); } auto finish = Now(); @@ -2387,9 +2386,8 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing - std::optional mnlist_updates_opt{std::nullopt}; - CDeterministicMNList mn_list; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates_opt, mn_list)) { + MNListUpdates mnlist_updates; + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, view, blockSubsidy, fJustCheck, fScriptChecks, state, mnlist_updates)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -2398,9 +2396,9 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // the snapshot base block (no-op for every other block). Snapshot // activation may populate the shared MN-list cache with seeded // state, so completion must not reconstruct this value through that - // cache. Before DIP3 activates, mn_list is the independently + // cache. Before DIP3 activates, new_list is the independently // computed empty list. - RecordBackgroundMNListHash(pindex, mn_list); + RecordBackgroundMNListHash(pindex, mnlist_updates.new_list); } const auto time_2_1{SteadyClock::now()}; @@ -2627,10 +2625,9 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, // Block is committed: keep the scheme it switched to (fJustCheck dry runs returned above). bls_scheme_guard.Commit(); - if (this == &m_chainman.ActiveChainstate() && mnlist_updates_opt.has_value()) { - const auto& mnlu = mnlist_updates_opt.value(); - GetMainSignals().NotifyMasternodeListChanged(false, mnlu.old_list, mnlu.diff); - uiInterface.NotifyMasternodeListChanged(mnlu.new_list, pindex); + if (this == &m_chainman.ActiveChainstate() && mnlist_updates.diff.HasChanges()) { + GetMainSignals().NotifyMasternodeListChanged(false, mnlist_updates.old_list, mnlist_updates.diff); + uiInterface.NotifyMasternodeListChanged(mnlist_updates.new_list, pindex); } ::g_stats_client->timing("ConnectBlock_ms", Ticks(time_8 - time_start), 1.0f); @@ -4861,13 +4858,12 @@ bool Chainstate::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& in BlockValidationState state; const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_chainman.GetConsensus()); const bool is_v24_active{DeploymentActiveAfter(pindex->pprev, m_chainman, Consensus::DEPLOYMENT_V24)}; - std::optional mnlist_updates_opt{std::nullopt}; - CDeterministicMNList mn_list; - if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, inputs, blockSubsidy, /*fJustCheck=*/false, /*fCheckCbTxMerkleRoots=*/false, state, mnlist_updates_opt, mn_list)) + MNListUpdates mnlist_updates; + if (!m_chain_helper->special_tx->ProcessSpecialTxsInBlock(*this, m_chain, block, pindex, is_v24_active, inputs, blockSubsidy, /*fJustCheck=*/false, /*fCheckCbTxMerkleRoots=*/false, state, mnlist_updates)) { return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } - RecordBackgroundMNListHash(pindex, mn_list); + RecordBackgroundMNListHash(pindex, mnlist_updates.new_list); for (size_t i = 0; i < block.vtx.size(); i++) { const CTransactionRef& tx = block.vtx[i]; From bccc3194cebc3cff2e853ccc8a6898864bf936ea Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 20:19:15 +0700 Subject: [PATCH 14/15] refactor: break circular dependency over specialtxman and validation.h There's several new exception has been added to the list of existing circular dependencies: all of them had been pre-existing but now they are discovered by removing the shorter loop specialtxman <-> validation --- src/evo/specialtxman.cpp | 2 +- test/lint/lint-circular-dependencies.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 830d4159d771..00111ca8c6b8 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -23,13 +23,13 @@ #include #include +#include #include #include #include #include #include #include -#include static bool AddNetInfoEntries(const std::shared_ptr& net_info, NetInfoPurpose purpose, const NetInfoList& entries, BlockValidationState& state) diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 6aa244c901a5..d231cad9a4c4 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -32,6 +32,7 @@ "index/base -> node/context -> index/spentindex -> index/base", "index/base -> node/context -> index/timestampindex -> index/base", "banman -> common/bloom -> evo/assetlocktx -> llmq/quorumsman -> llmq/blockprocessor -> net -> banman", + "chainlock/handler -> validation -> evo/specialtxman -> chainlock/handler", "coinjoin/client -> coinjoin/util -> wallet/wallet -> psbt -> node/transaction -> net_processing -> coinjoin/walletman -> coinjoin/client", "common/bloom -> evo/assetlocktx -> llmq/commitment -> evo/deterministicmns -> evo/simplifiedmns -> merkleblock -> common/bloom", "common/bloom -> evo/assetlocktx -> llmq/quorumsman -> llmq/blockprocessor -> net -> common/bloom", @@ -39,11 +40,12 @@ "consensus/tx_verify -> evo/assetlocktx -> llmq/commitment -> validation -> txmempool -> consensus/tx_verify", "evo/assetlocktx -> llmq/commitment -> validation -> txmempool -> evo/assetlocktx", "evo/chainhelper -> evo/creditpool -> validation -> evo/chainhelper", + "evo/creditpool -> validation -> evo/specialtxman -> evo/creditpool", "evo/deterministicmns -> node/blockstorage -> validation -> evo/deterministicmns", "evo/deterministicmns -> node/blockstorage -> validation -> masternode/payments -> evo/deterministicmns", "evo/deterministicmns -> node/blockstorage -> validation -> txmempool -> evo/deterministicmns", "evo/smldiff -> llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> evo/smldiff", - "evo/specialtxman -> validation -> evo/specialtxman", + "evo/specialtxman -> llmq/blockprocessor -> validation -> evo/specialtxman", "governance/superblock -> validation -> masternode/payments -> governance/superblock", "instantsend/instantsend -> node/blockstorage -> validation -> txmempool -> instantsend/instantsend", "llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> llmq/blockprocessor", From f93232473f8c63e1aa81f82c995846af29c23f46 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 8 Sep 2026 20:23:45 +0700 Subject: [PATCH 15/15] refactor: remove unused includes from specialtxman.cpp and related files --- src/evo/creditpool.h | 3 +-- src/evo/deterministicmns.h | 2 -- src/evo/specialtxman.cpp | 2 -- test/lint/lint-circular-dependencies.py | 1 - 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/evo/creditpool.h b/src/evo/creditpool.h index 610c193eac74..891449f24f1f 100644 --- a/src/evo/creditpool.h +++ b/src/evo/creditpool.h @@ -6,6 +6,7 @@ #define BITCOIN_EVO_CREDITPOOL_H #include +#include #include #include #include @@ -14,8 +15,6 @@ #include #include -#include - #include #include diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 5cf4fbc581b4..2fd5cf9433ee 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -15,7 +14,6 @@ #include #include #include -#include #include #include diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 00111ca8c6b8..cc25a24eaa9e 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -20,7 +19,6 @@ #include #include #include -#include #include #include diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index d231cad9a4c4..21560ed916ab 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -32,7 +32,6 @@ "index/base -> node/context -> index/spentindex -> index/base", "index/base -> node/context -> index/timestampindex -> index/base", "banman -> common/bloom -> evo/assetlocktx -> llmq/quorumsman -> llmq/blockprocessor -> net -> banman", - "chainlock/handler -> validation -> evo/specialtxman -> chainlock/handler", "coinjoin/client -> coinjoin/util -> wallet/wallet -> psbt -> node/transaction -> net_processing -> coinjoin/walletman -> coinjoin/client", "common/bloom -> evo/assetlocktx -> llmq/commitment -> evo/deterministicmns -> evo/simplifiedmns -> merkleblock -> common/bloom", "common/bloom -> evo/assetlocktx -> llmq/quorumsman -> llmq/blockprocessor -> net -> common/bloom",