From 54b7cdf20d93cab6f0c1459b7f9a5a7eef2f9deb Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 28 May 2026 15:35:53 +0700 Subject: [PATCH 1/6] refactor: introduce new enum MnRewardEra to use in masternode/payment helpers It helps to minimize dependencies of masternode/payments on validation.h and move calls such as is-deployment-active outside to callers --- src/masternode/payments.cpp | 22 +++++++++++----------- src/masternode/payments.h | 24 +++++++++++++++++++----- src/node/miner.cpp | 3 ++- src/rpc/masternode.cpp | 3 ++- src/validation.cpp | 11 ++++++++++- src/validation.h | 5 +++++ 6 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index be5635d15a27..db4e925f013f 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -31,18 +31,18 @@ CAmount PlatformShare(const CAmount reward) } [[nodiscard]] bool CMNPaymentsProcessor::GetBlockTxOuts(const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, - std::vector& voutMasternodePaymentsRet) + MnRewardEra era, std::vector& voutMasternodePaymentsRet) { voutMasternodePaymentsRet.clear(); const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; - bool fV20Active = DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_V20); + const bool fV20Active = era != MnRewardEra::Classic; CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockSubsidy + feeReward, fV20Active); // Credit Pool doesn't exist before V20. If any part of reward will re-allocated to credit pool before v20 // activation these fund will be just permanently lost. Applicable for devnets, regtest, testnet - if (fV20Active && DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_MN_RR)) { + if (era == MnRewardEra::EvoReward) { CAmount masternodeSubsidyReward = GetMasternodePayment(nBlockHeight, blockSubsidy, fV20Active); const CAmount platformReward = PlatformShare(masternodeSubsidyReward); masternodeReward -= platformReward; @@ -87,12 +87,12 @@ CAmount PlatformShare(const CAmount reward) * Get masternode payment tx outputs */ [[nodiscard]] bool CMNPaymentsProcessor::GetMasternodeTxOuts(const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, - std::vector& voutMasternodePaymentsRet) + MnRewardEra era, std::vector& voutMasternodePaymentsRet) { // make sure it's not filled yet voutMasternodePaymentsRet.clear(); - if(!GetBlockTxOuts(pindexPrev, blockSubsidy, feeReward, voutMasternodePaymentsRet)) { + if(!GetBlockTxOuts(pindexPrev, blockSubsidy, feeReward, era, voutMasternodePaymentsRet)) { LogPrintf("CMNPaymentsProcessor::%s -- ERROR Failed to get payee\n", __func__); return false; } @@ -108,7 +108,7 @@ CAmount PlatformShare(const CAmount reward) } [[nodiscard]] bool CMNPaymentsProcessor::IsTransactionValid(const CTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, - const CAmount feeReward) + const CAmount feeReward, MnRewardEra era) { const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; if (!DeploymentDIP0003Enforced(nBlockHeight, m_consensus_params)) { @@ -117,7 +117,7 @@ CAmount PlatformShare(const CAmount reward) } std::vector voutMasternodePayments; - if (!GetBlockTxOuts(pindexPrev, blockSubsidy, feeReward, voutMasternodePayments)) { + if (!GetBlockTxOuts(pindexPrev, blockSubsidy, feeReward, era, voutMasternodePayments)) { LogPrintf("CMNPaymentsProcessor::%s -- ERROR! Failed to get payees for block at height %s\n", __func__, nBlockHeight); return true; } @@ -261,12 +261,12 @@ bool CMNPaymentsProcessor::IsBlockValueValid(const CBlock& block, const int nBlo return true; } -bool CMNPaymentsProcessor::IsBlockPayeeValid(const CTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, const bool check_superblock) +bool CMNPaymentsProcessor::IsBlockPayeeValid(const CTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, MnRewardEra era, const bool check_superblock) { const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; // Check for correct masternode payment - if (IsTransactionValid(txNew, pindexPrev, blockSubsidy, feeReward)) { + if (IsTransactionValid(txNew, pindexPrev, blockSubsidy, feeReward, era)) { LogPrint(BCLog::MNPAYMENTS, "CMNPaymentsProcessor::%s -- Valid masternode payment at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */ } else { LogPrintf("CMNPaymentsProcessor::%s -- ERROR! Invalid masternode payment detected at height %d: %s", __func__, nBlockHeight, txNew.ToString()); /* Continued */ @@ -313,7 +313,7 @@ bool CMNPaymentsProcessor::IsBlockPayeeValid(const CTransaction& txNew, const CB } void CMNPaymentsProcessor::FillBlockPayments(CMutableTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, - std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet) + MnRewardEra era, std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet) { int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; @@ -324,7 +324,7 @@ void CMNPaymentsProcessor::FillBlockPayments(CMutableTransaction& txNew, const C m_superblocks.GetSuperblockPayments(tip_mn_list, nBlockHeight, voutSuperblockPaymentsRet); } - if (!GetMasternodeTxOuts(pindexPrev, blockSubsidy, feeReward, voutMasternodePaymentsRet)) { + if (!GetMasternodeTxOuts(pindexPrev, blockSubsidy, feeReward, era, voutMasternodePaymentsRet)) { LogPrint(BCLog::MNPAYMENTS, "CMNPaymentsProcessor::%s -- No masternode to pay (MN list probably empty)\n", __func__); } diff --git a/src/masternode/payments.h b/src/masternode/payments.h index d11da4dd3e87..75ae0786edc5 100644 --- a/src/masternode/payments.h +++ b/src/masternode/payments.h @@ -30,6 +30,20 @@ namespace Consensus { struct Params; } */ CAmount PlatformShare(const CAmount masternodeReward); +/** + * Masternode reward era for a given block, gating how the reward is computed. + * Each era implies the previous one: EvoReward is only reachable once CreditPool + * (V20) is active, so the ordering encodes that invariant. The era is computed by + * the caller (which owns the block context) and passed in, keeping this module free + * of deployment dependencies. Note this is orthogonal to DIP0003 enforcement, which + * gates whether payees are validated at all and is handled separately. + */ +enum class MnRewardEra { + Classic, // historical reward schedule, no credit pool + CreditPool, // V20: credit pool active, no platform reallocation yet + EvoReward, // MN_RR: platform share is reallocated from the masternode reward +}; + class CMNPaymentsProcessor { private: @@ -40,11 +54,11 @@ class CMNPaymentsProcessor private: [[nodiscard]] bool GetBlockTxOuts(const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, - std::vector& voutMasternodePaymentsRet); + MnRewardEra era, std::vector& voutMasternodePaymentsRet); [[nodiscard]] bool GetMasternodeTxOuts(const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, - std::vector& voutMasternodePaymentsRet); + MnRewardEra era, std::vector& voutMasternodePaymentsRet); [[nodiscard]] bool IsTransactionValid(const CTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, - const CAmount feeReward); + const CAmount feeReward, MnRewardEra era); [[nodiscard]] bool IsOldBudgetBlockValueValid(const CBlock& block, const int nBlockHeight, const CAmount blockReward, std::string& strErrorRet); public: @@ -58,9 +72,9 @@ class CMNPaymentsProcessor } bool IsBlockValueValid(const CBlock& block, const int nBlockHeight, const CAmount blockReward, std::string& strErrorRet, const bool check_superblock); - bool IsBlockPayeeValid(const CTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, const bool check_superblock); + bool IsBlockPayeeValid(const CTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, MnRewardEra era, const bool check_superblock); void FillBlockPayments(CMutableTransaction& txNew, const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, - std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet); + MnRewardEra era, std::vector& voutMasternodePaymentsRet, std::vector& voutSuperblockPaymentsRet); }; #endif // BITCOIN_MASTERNODE_PAYMENTS_H diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 6b267353cd5f..315a64608677 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -319,7 +319,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc // Update coinbase transaction with additional info about masternode and governance payments, // get some info back to pass to getblocktemplate - m_chain_helper.mn_payments->FillBlockPayments(coinbaseTx, pindexPrev, blockSubsidy, nFees, pblocktemplate->voutMasternodePayments, pblocktemplate->voutSuperblockPayments); + const MnRewardEra mn_reward_era{GetMnRewardEraAfter(pindexPrev, m_chainstate.m_chainman)}; + m_chain_helper.mn_payments->FillBlockPayments(coinbaseTx, pindexPrev, blockSubsidy, nFees, mn_reward_era, pblocktemplate->voutMasternodePayments, pblocktemplate->voutSuperblockPayments); pblock->vtx[0] = MakeTransactionRef(std::move(coinbaseTx)); pblocktemplate->vTxFees[0] = -nFees; diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index 14e940b61136..4553f7748004 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -421,7 +421,8 @@ static RPCHelpMan masternode_payments() std::vector voutMasternodePayments, voutDummy; CMutableTransaction dummyTx; CAmount blockSubsidy = GetBlockSubsidy(pindex, Params().GetConsensus()); - node.chain_helper->mn_payments->FillBlockPayments(dummyTx, pindex->pprev, blockSubsidy, nBlockFees, voutMasternodePayments, voutDummy); + const MnRewardEra mn_reward_era{GetMnRewardEraAfter(pindex->pprev, chainman)}; + node.chain_helper->mn_payments->FillBlockPayments(dummyTx, pindex->pprev, blockSubsidy, nBlockFees, mn_reward_era, voutMasternodePayments, voutDummy); UniValue blockObj(UniValue::VOBJ); CAmount payedPerBlock{0}; diff --git a/src/validation.cpp b/src/validation.cpp index 9b99d0168343..da2b112d176a 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2496,7 +2496,8 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, int64_t nTime5_3 = GetTimeMicros(); nTimeValueValid += nTime5_3 - nTime5_2; LogPrint(BCLog::BENCHMARK, " - IsBlockValueValid: %.2fms [%.2fs (%.2fms/blk)]\n", MILLI * (nTime5_3 - nTime5_2), nTimeValueValid * MICRO, nTimeValueValid * MILLI / nBlocksTotal); - if (!m_chain_helper->mn_payments->IsBlockPayeeValid(*block.vtx[0], pindex->pprev, blockSubsidy, feeReward, check_superblock)) { + const MnRewardEra mn_reward_era{GetMnRewardEraAfter(pindex->pprev, m_chainman)}; + if (!m_chain_helper->mn_payments->IsBlockPayeeValid(*block.vtx[0], pindex->pprev, blockSubsidy, feeReward, mn_reward_era, check_superblock)) { // NOTE: Do not punish, the node might be missing governance data LogPrintf("ERROR: ConnectBlock(DASH): couldn't find masternode or superblock payments\n"); return state.Invalid(BlockValidationResult::BLOCK_RESULT_UNSET, "bad-cb-payee"); @@ -5253,6 +5254,14 @@ bool CChainState::ResizeCoinsCaches(size_t coinstip_size, size_t coinsdb_size) return ret; } +MnRewardEra GetMnRewardEraAfter(const CBlockIndex* pindexPrev, const ChainstateManager& chainman) +{ + const Consensus::Params& params{chainman.GetConsensus()}; + if (!DeploymentActiveAfter(pindexPrev, params, Consensus::DEPLOYMENT_V20)) return MnRewardEra::Classic; + if (!DeploymentActiveAfter(pindexPrev, params, Consensus::DEPLOYMENT_MN_RR)) return MnRewardEra::CreditPool; + return MnRewardEra::EvoReward; +} + static const uint64_t MEMPOOL_DUMP_VERSION = 1; bool LoadMempool(CTxMemPool& pool, CChainState& active_chainstate, FopenFn mockable_fopen_function) diff --git a/src/validation.h b/src/validation.h index 5d9d09061d6c..d8dfeeeae474 100644 --- a/src/validation.h +++ b/src/validation.h @@ -68,6 +68,8 @@ class SnapshotMetadata; } // namespace node namespace chainlock { class Chainlocks; } +enum class MnRewardEra; // defined in masternode/payments.h + /** Default for -mempoolexpiry, expiration time for mempool transactions in hours */ static const unsigned int DEFAULT_MEMPOOL_EXPIRY = 336; /** Maximum number of dedicated script-checking threads allowed */ @@ -1103,6 +1105,9 @@ bool DeploymentEnabled(const ChainstateManager& chainman, DEP dep) return DeploymentEnabled(chainman.GetConsensus(), dep); } +/** Determine the masternode reward era for the block following pindexPrev. */ +MnRewardEra GetMnRewardEraAfter(const CBlockIndex* pindexPrev, const ChainstateManager& chainman); + using FopenFn = std::function; /** Dump the mempool to disk. */ From 8a6e7dc8fe1cb79f2a06c1b4598e0f5d6df8f86d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 29 May 2026 15:37:44 +0700 Subject: [PATCH 2/6] refactor: move helper GetMasternodePayment to masternode/payments --- src/masternode/payments.cpp | 69 +++++++++++++++++++++++++++++++++++++ src/masternode/payments.h | 2 ++ src/validation.cpp | 69 ------------------------------------- src/validation.h | 1 - 4 files changed, 71 insertions(+), 70 deletions(-) diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index db4e925f013f..a8a174d2501d 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -30,6 +30,75 @@ CAmount PlatformShare(const CAmount reward) return platformReward; } +CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active) +{ + CAmount ret = blockValue/5; // start at 20% + + const int nMNPIBlock = Params().GetConsensus().nMasternodePaymentsIncreaseBlock; + const int nMNPIPeriod = Params().GetConsensus().nMasternodePaymentsIncreasePeriod; + const int nReallocActivationHeight = Params().GetConsensus().BRRHeight; + + // mainnet: + if(nHeight > nMNPIBlock) ret += blockValue / 20; // 158000 - 25.0% - 2014-10-24 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 1)) ret += blockValue / 20; // 175280 - 30.0% - 2014-11-25 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 2)) ret += blockValue / 20; // 192560 - 35.0% - 2014-12-26 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 3)) ret += blockValue / 40; // 209840 - 37.5% - 2015-01-26 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 4)) ret += blockValue / 40; // 227120 - 40.0% - 2015-02-27 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 5)) ret += blockValue / 40; // 244400 - 42.5% - 2015-03-30 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 6)) ret += blockValue / 40; // 261680 - 45.0% - 2015-05-01 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 7)) ret += blockValue / 40; // 278960 - 47.5% - 2015-06-01 + if(nHeight > nMNPIBlock+(nMNPIPeriod* 9)) ret += blockValue / 40; // 313520 - 50.0% - 2015-08-03 + + if (nHeight < nReallocActivationHeight) { + // Block Reward Realocation is not activated yet, nothing to do + return ret; + } + + int nSuperblockCycle = Params().GetConsensus().nSuperblockCycle; + // Actual realocation starts in the cycle next to one activation happens in + int nReallocStart = nReallocActivationHeight - nReallocActivationHeight % nSuperblockCycle + nSuperblockCycle; + + if (nHeight < nReallocStart) { + // Activated but we have to wait for the next cycle to start realocation, nothing to do + return ret; + } + + if (fV20Active) { + // Once MNRewardReallocated activates, block reward is 80% of block subsidy (+ tx fees) since treasury is 20% + // Since the MN reward needs to be equal to 60% of the block subsidy (according to the proposal), MN reward is set to 75% of the block reward. + // Previous reallocation periods are dropped. + return blockValue * 3 / 4; + } + + // Periods used to reallocate the masternode reward from 50% to 60% + static std::vector vecPeriods{ + 513, // Period 1: 51.3% + 526, // Period 2: 52.6% + 533, // Period 3: 53.3% + 540, // Period 4: 54% + 546, // Period 5: 54.6% + 552, // Period 6: 55.2% + 557, // Period 7: 55.7% + 562, // Period 8: 56.2% + 567, // Period 9: 56.7% + 572, // Period 10: 57.2% + 577, // Period 11: 57.7% + 582, // Period 12: 58.2% + 585, // Period 13: 58.5% + 588, // Period 14: 58.8% + 591, // Period 15: 59.1% + 594, // Period 16: 59.4% + 597, // Period 17: 59.7% + 599, // Period 18: 59.9% + 600 // Period 19: 60% + }; + + int nReallocCycle = nSuperblockCycle * 3; + int nCurrentPeriod = std::min((nHeight - nReallocStart) / nReallocCycle, vecPeriods.size() - 1); + + return static_cast(blockValue * vecPeriods[nCurrentPeriod] / 1000); +} + [[nodiscard]] bool CMNPaymentsProcessor::GetBlockTxOuts(const CBlockIndex* pindexPrev, const CAmount blockSubsidy, const CAmount feeReward, MnRewardEra era, std::vector& voutMasternodePaymentsRet) { diff --git a/src/masternode/payments.h b/src/masternode/payments.h index 75ae0786edc5..8d7c8dc9edfa 100644 --- a/src/masternode/payments.h +++ b/src/masternode/payments.h @@ -44,6 +44,8 @@ enum class MnRewardEra { EvoReward, // MN_RR: platform share is reallocated from the masternode reward }; +CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active); + class CMNPaymentsProcessor { private: diff --git a/src/validation.cpp b/src/validation.cpp index da2b112d176a..8cfe070307f5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1560,75 +1560,6 @@ CAmount GetBlockSubsidy(const CBlockIndex* const pindex, const Consensus::Params return GetBlockSubsidyInner(pindex->pprev->nBits, pindex->pprev->nHeight, consensusParams, isV20Active); } -CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active) -{ - CAmount ret = blockValue/5; // start at 20% - - const int nMNPIBlock = Params().GetConsensus().nMasternodePaymentsIncreaseBlock; - const int nMNPIPeriod = Params().GetConsensus().nMasternodePaymentsIncreasePeriod; - const int nReallocActivationHeight = Params().GetConsensus().BRRHeight; - - // mainnet: - if(nHeight > nMNPIBlock) ret += blockValue / 20; // 158000 - 25.0% - 2014-10-24 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 1)) ret += blockValue / 20; // 175280 - 30.0% - 2014-11-25 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 2)) ret += blockValue / 20; // 192560 - 35.0% - 2014-12-26 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 3)) ret += blockValue / 40; // 209840 - 37.5% - 2015-01-26 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 4)) ret += blockValue / 40; // 227120 - 40.0% - 2015-02-27 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 5)) ret += blockValue / 40; // 244400 - 42.5% - 2015-03-30 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 6)) ret += blockValue / 40; // 261680 - 45.0% - 2015-05-01 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 7)) ret += blockValue / 40; // 278960 - 47.5% - 2015-06-01 - if(nHeight > nMNPIBlock+(nMNPIPeriod* 9)) ret += blockValue / 40; // 313520 - 50.0% - 2015-08-03 - - if (nHeight < nReallocActivationHeight) { - // Block Reward Realocation is not activated yet, nothing to do - return ret; - } - - int nSuperblockCycle = Params().GetConsensus().nSuperblockCycle; - // Actual realocation starts in the cycle next to one activation happens in - int nReallocStart = nReallocActivationHeight - nReallocActivationHeight % nSuperblockCycle + nSuperblockCycle; - - if (nHeight < nReallocStart) { - // Activated but we have to wait for the next cycle to start realocation, nothing to do - return ret; - } - - if (fV20Active) { - // Once MNRewardReallocated activates, block reward is 80% of block subsidy (+ tx fees) since treasury is 20% - // Since the MN reward needs to be equal to 60% of the block subsidy (according to the proposal), MN reward is set to 75% of the block reward. - // Previous reallocation periods are dropped. - return blockValue * 3 / 4; - } - - // Periods used to reallocate the masternode reward from 50% to 60% - static std::vector vecPeriods{ - 513, // Period 1: 51.3% - 526, // Period 2: 52.6% - 533, // Period 3: 53.3% - 540, // Period 4: 54% - 546, // Period 5: 54.6% - 552, // Period 6: 55.2% - 557, // Period 7: 55.7% - 562, // Period 8: 56.2% - 567, // Period 9: 56.7% - 572, // Period 10: 57.2% - 577, // Period 11: 57.7% - 582, // Period 12: 58.2% - 585, // Period 13: 58.5% - 588, // Period 14: 58.8% - 591, // Period 15: 59.1% - 594, // Period 16: 59.4% - 597, // Period 17: 59.7% - 599, // Period 18: 59.9% - 600 // Period 19: 60% - }; - - int nReallocCycle = nSuperblockCycle * 3; - int nCurrentPeriod = std::min((nHeight - nReallocStart) / nReallocCycle, vecPeriods.size() - 1); - - return static_cast(blockValue * vecPeriods[nCurrentPeriod] / 1000); -} - CoinsViews::CoinsViews( fs::path ldb_name, size_t cache_size_bytes, diff --git a/src/validation.h b/src/validation.h index d8dfeeeae474..35a489fe6272 100644 --- a/src/validation.h +++ b/src/validation.h @@ -155,7 +155,6 @@ double ConvertBitsToDouble(unsigned int nBits); CAmount GetBlockSubsidyInner(int nPrevBits, int nPrevHeight, const Consensus::Params& consensusParams, bool fV20Active); CAmount GetSuperblockSubsidyInner(int nPrevBits, int nPrevHeight, const Consensus::Params& consensusParams, bool fV20Active); CAmount GetBlockSubsidy(const CBlockIndex* const pindex, const Consensus::Params& consensusParams); -CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active); bool AbortNode(BlockValidationState& state, const std::string& strMessage, const bilingual_str& userMessage = bilingual_str{}); From 48f0c9b66ff200726e2961e1c2a2df5a7ebad7d8 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 29 May 2026 15:38:39 +0700 Subject: [PATCH 3/6] refactor: remove forward declaration of PlatformShare from creditpool --- src/evo/creditpool.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/evo/creditpool.cpp b/src/evo/creditpool.cpp index 6faa197d9e59..ae38e3f7d901 100644 --- a/src/evo/creditpool.cpp +++ b/src/evo/creditpool.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -24,9 +25,6 @@ using node::ReadBlockFromDisk; -// Forward declaration to prevent a new circular dependencies through masternode/payments.h -CAmount PlatformShare(const CAmount masternodeReward); - static const std::string DB_CREDITPOOL_SNAPSHOT = "cpm_S"; static bool GetDataFromUnlockTx(const CTransaction& tx, CAmount& toUnlock, uint64_t& index, TxValidationState& state) From c2c8f399ae40bd87b1fc053fdd0237c3c0c9cab0 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sat, 30 May 2026 09:31:26 +0700 Subject: [PATCH 4/6] refactor: use helper MnRewardEra for GetMasternodePayment --- src/evo/creditpool.cpp | 2 +- src/masternode/payments.cpp | 9 +++--- src/masternode/payments.h | 2 +- src/test/block_reward_reallocation_tests.cpp | 32 ++++++++++++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/src/evo/creditpool.cpp b/src/evo/creditpool.cpp index ae38e3f7d901..f6dd8f9a1114 100644 --- a/src/evo/creditpool.cpp +++ b/src/evo/creditpool.cpp @@ -251,7 +251,7 @@ CCreditPoolDiff::CCreditPoolDiff(CCreditPool starter, const CBlockIndex* pindexP if (DeploymentActiveAfter(pindexPrev, consensusParams, Consensus::DEPLOYMENT_MN_RR)) { // If credit pool exists, it means v20 is activated - platformReward = PlatformShare(GetMasternodePayment(pindexPrev->nHeight + 1, blockSubsidy, /*fV20Active=*/ true)); + platformReward = PlatformShare(GetMasternodePayment(pindexPrev->nHeight + 1, blockSubsidy, MnRewardEra::EvoReward)); } } diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index a8a174d2501d..58e3b0d9b18f 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -30,7 +30,7 @@ CAmount PlatformShare(const CAmount reward) return platformReward; } -CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active) +CAmount GetMasternodePayment(int nHeight, CAmount blockValue, MnRewardEra era) { CAmount ret = blockValue/5; // start at 20% @@ -63,7 +63,7 @@ CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active) return ret; } - if (fV20Active) { + if (era != MnRewardEra::Classic) { // Once MNRewardReallocated activates, block reward is 80% of block subsidy (+ tx fees) since treasury is 20% // Since the MN reward needs to be equal to 60% of the block subsidy (according to the proposal), MN reward is set to 75% of the block reward. // Previous reallocation periods are dropped. @@ -106,13 +106,12 @@ CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active) const int nBlockHeight = pindexPrev == nullptr ? 0 : pindexPrev->nHeight + 1; - const bool fV20Active = era != MnRewardEra::Classic; - CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockSubsidy + feeReward, fV20Active); + CAmount masternodeReward = GetMasternodePayment(nBlockHeight, blockSubsidy + feeReward, era); // Credit Pool doesn't exist before V20. If any part of reward will re-allocated to credit pool before v20 // activation these fund will be just permanently lost. Applicable for devnets, regtest, testnet if (era == MnRewardEra::EvoReward) { - CAmount masternodeSubsidyReward = GetMasternodePayment(nBlockHeight, blockSubsidy, fV20Active); + CAmount masternodeSubsidyReward = GetMasternodePayment(nBlockHeight, blockSubsidy, era); const CAmount platformReward = PlatformShare(masternodeSubsidyReward); masternodeReward -= platformReward; diff --git a/src/masternode/payments.h b/src/masternode/payments.h index 8d7c8dc9edfa..baf8a8eccec5 100644 --- a/src/masternode/payments.h +++ b/src/masternode/payments.h @@ -44,7 +44,7 @@ enum class MnRewardEra { EvoReward, // MN_RR: platform share is reallocated from the masternode reward }; -CAmount GetMasternodePayment(int nHeight, CAmount blockValue, bool fV20Active); +CAmount GetMasternodePayment(int nHeight, CAmount blockValue, MnRewardEra era); class CMNPaymentsProcessor { diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index 97c29c62c9d0..d181a61ec69b 100644 --- a/src/test/block_reward_reallocation_tests.cpp +++ b/src/test/block_reward_reallocation_tests.cpp @@ -209,11 +209,12 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // next block should be signaling by default LOCK(cs_main); const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()}; - const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)}; + const MnRewardEra era{GetMnRewardEraAfter(tip, *m_node.chainman)}; + const bool isV20Active{era != MnRewardEra::Classic}; dmnman.UpdatedBlockTip(tip); BOOST_REQUIRE(dmnman.GetListAtChainTip().HasMN(tx.GetHash())); const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); - const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active); + const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, era); const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey); BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment); } @@ -225,9 +226,10 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS { LOCK(cs_main); const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()}; - const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)}; + const MnRewardEra era{GetMnRewardEraAfter(tip, *m_node.chainman)}; + const bool isV20Active{era != MnRewardEra::Classic}; const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); - const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active); + const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, era); const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), 28847249686); BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment); @@ -242,9 +244,10 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS } LOCK(cs_main); const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()}; - const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)}; + const MnRewardEra era{GetMnRewardEraAfter(tip, *m_node.chainman)}; + const bool isV20Active{era != MnRewardEra::Classic}; const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); - const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active); + const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, era); const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey); BOOST_CHECK_EQUAL(pblocktemplate->voutMasternodePayments[0].nValue, masternode_payment); } @@ -255,14 +258,15 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // Reward split should reach ~75/25 after reallocation is done LOCK(cs_main); const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()}; - const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)}; + const MnRewardEra era{GetMnRewardEraAfter(tip, *m_node.chainman)}; + const bool isV20Active{era != MnRewardEra::Classic}; const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); const CAmount block_subsidy_sb = GetSuperblockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); CAmount block_subsidy_potential = block_subsidy + block_subsidy_sb; BOOST_CHECK_EQUAL(block_subsidy_potential, 177167660); CAmount expected_block_reward = block_subsidy_potential - block_subsidy_potential / 5; - const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active); + const CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, era); const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), expected_block_reward); BOOST_CHECK_EQUAL(pblocktemplate->block.vtx[0]->GetValueOut(), 141734128); @@ -279,10 +283,11 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS } LOCK(cs_main); const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()}; - const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)}; - const bool isMNRewardReallocated{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_MN_RR)}; + const MnRewardEra era{GetMnRewardEraAfter(tip, *m_node.chainman)}; + const bool isV20Active{era != MnRewardEra::Classic}; + const bool isMNRewardReallocated{era == MnRewardEra::EvoReward}; const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); - CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active); + CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, era); const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey); if (isMNRewardReallocated) { @@ -299,10 +304,11 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS // Allocation of block subsidy is 60% MN, 20% miners and 20% treasury LOCK(cs_main); const CBlockIndex* const tip{m_node.chainman->ActiveChain().Tip()}; - const bool isV20Active{DeploymentActiveAfter(tip, consensus_params, Consensus::DEPLOYMENT_V20)}; + const MnRewardEra era{GetMnRewardEraAfter(tip, *m_node.chainman)}; + const bool isV20Active{era != MnRewardEra::Classic}; const CAmount block_subsidy = GetBlockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); const CAmount block_subsidy_sb = GetSuperblockSubsidyInner(tip->nBits, tip->nHeight, consensus_params, isV20Active); - CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, isV20Active); + CAmount masternode_payment = GetMasternodePayment(tip->nHeight, block_subsidy, era); const CAmount platform_payment = PlatformShare(masternode_payment); masternode_payment -= platform_payment; const auto pblocktemplate = BlockAssembler(m_node.chainman->ActiveChainstate(), m_node, m_node.mempool.get(), Params()).CreateNewBlock(coinbasePubKey); From 7f43be87556c93e7f7f1c695e3dc8bbd3ffd954d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 2 Jun 2026 14:20:15 +0700 Subject: [PATCH 5/6] refactor: drop dependency of masternode/payment on Chainstate [validation.h] --- src/evo/chainhelper.cpp | 2 +- src/masternode/payments.cpp | 12 ++++++------ src/masternode/payments.h | 10 ++++------ src/validation.cpp | 4 ++-- test/lint/lint-circular-dependencies.py | 3 ++- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index 16f4782aa717..f059d78a7427 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -27,7 +27,7 @@ CChainstateHelper::CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmn m_chainlocks{chainlocks}, ehf_manager{std::make_unique(evodb, chainman, qman)}, superblocks{std::make_unique()}, - mn_payments{std::make_unique(dmnman, *superblocks, chainman, consensus_params)}, + 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)} {} diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index 58e3b0d9b18f..23f727ffeaf4 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -16,7 +17,6 @@ #include #include