From c3430b712b9baa970d473590ace3927857e66810 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 20 Oct 2026 13:14:32 +0700 Subject: [PATCH 1/5] perf: diff masternode lists through immer's structural diff BuildDiff walked the whole new list and looked every masternode up in the old one to find the few entries a block changes, and walked the old list once more whenever something was removed: two hash lookups per masternode, ~4000 per block, for a handful of changes. The new list is the old one plus those changes and the two share all other nodes, so immer::diff visits only the subtrees that differ. The added, removed and updated sets are the same; the early exit of the removal walk is no longer needed. --- src/evo/deterministicmns.cpp | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 8241cdcab704..b46356761f4d 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -337,27 +338,18 @@ CDeterministicMNListDiff CDeterministicMNList::BuildDiff(const CDeterministicMNL { CDeterministicMNListDiff diffRet; - for (const auto& p : to.mnMap) { - const auto& toPtr = p.second; - auto fromPtr = GetMN(toPtr->proTxHash); - if (fromPtr == nullptr) { - diffRet.addedMNs.emplace_back(toPtr); - } else if (fromPtr != toPtr || fromPtr->pdmnState != toPtr->pdmnState) { - CDeterministicMNStateDiff stateDiff(*fromPtr->pdmnState, *toPtr->pdmnState); - if (stateDiff.fields) { - diffRet.updatedMNs.emplace(toPtr->GetInternalId(), std::move(stateDiff)); - } - } - } - if (mnMap.size() + diffRet.addedMNs.size() != to.mnMap.size()) { - for (auto& fromPtr : mnMap) { - const auto toPtr = to.GetMN(fromPtr.second->proTxHash); - if (toPtr == nullptr) { - diffRet.removedMns.emplace(fromPtr.second->GetInternalId()); - if (mnMap.size() + diffRet.addedMNs.size() - diffRet.removedMns.size() == to.mnMap.size()) break; - } - }; - } + // Visits only the subtrees the two maps do not share, so a list derived from this one by a + // few updates is diffed in time proportional to those updates. + immer::diff(mnMap, to.mnMap, + immer::make_differ( + [&diffRet](const auto& added) { diffRet.addedMNs.emplace_back(added.second); }, + [&diffRet](const auto& removed) { diffRet.removedMns.emplace(removed.second->GetInternalId()); }, + [&diffRet](const auto& from, const auto& to_entry) { + CDeterministicMNStateDiff stateDiff(*from.second->pdmnState, *to_entry.second->pdmnState); + if (stateDiff.fields) { + diffRet.updatedMNs.emplace(to_entry.second->GetInternalId(), std::move(stateDiff)); + } + })); // added MNs need to be sorted by internalId so that these are added in correct order when the diff is applied later // otherwise internalIds will not match with the original list From a821c07e7cd90d3c5679e6068dcc2ff78b3c44ea Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 20 Oct 2026 12:17:21 +0700 Subject: [PATCH 2/5] perf: make ForEachMN and ForEachMNShared templates to help compilers do its job Both took the callback as std::function, so every per-block walk paid a type-erased call per masternode and the lambda could not be inlined into the loop. Take the callable as a template parameter. --- src/evo/deterministicmns.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index f8ab114c6312..72d789f5c726 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -295,7 +295,8 @@ class CDeterministicMNList * @param onlyValid Run on all masternodes, or only "valid" (not banned) masternodes * @param cb callback to execute */ - void ForEachMN(bool onlyValid, std::function cb) const + template + void ForEachMN(bool onlyValid, Callback&& cb) const { for (const auto& p : mnMap) { if (!onlyValid || !p.second->pdmnState->IsBanned()) { @@ -311,7 +312,8 @@ class CDeterministicMNList * @param onlyValid Run on all masternodes, or only "valid" (not banned) masternodes * @param cb callback to execute */ - void ForEachMNShared(bool onlyValid, std::function cb) const + template + void ForEachMNShared(bool onlyValid, Callback&& cb) const { for (const auto& p : mnMap) { if (!onlyValid || !p.second->pdmnState->IsBanned()) { From fdba30b417219b8c222d29f205a0f61946b2c181 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 20 Oct 2026 12:17:21 +0700 Subject: [PATCH 3/5] perf: fetch the previous block's masternode list once per block ProcessSpecialTxsInBlock fetched the list at the previous block for the payment check, BuildNewListFromBlock fetched it again to rebuild from, and CDeterministicMNManager::ProcessBlock a third time to build the diff. Each fetch copies the list and runs the cache bookkeeping. Pass the one already fetched into the rebuild and into ProcessBlock. --- src/evo/deterministicmns.cpp | 6 ++---- src/evo/deterministicmns.h | 2 +- src/evo/specialtxman.cpp | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index b46356761f4d..376571f2d938 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -679,8 +679,8 @@ CDeterministicMNManager::CDeterministicMNManager(CEvoDB& evoDb, CMasternodeMetaM CDeterministicMNManager::~CDeterministicMNManager() = default; bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_null pindex, - BlockValidationState& state, const CDeterministicMNList& newList, - MNListUpdates& updatesRet) + BlockValidationState& state, const CDeterministicMNList& oldList, + const CDeterministicMNList& newList, MNListUpdates& updatesRet) { AssertLockHeld(::cs_main); @@ -689,7 +689,6 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullnHeight; @@ -699,7 +698,6 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullpprev); diff = oldList.BuildDiff(newList); // A WriteDerived mismatch is local EvoDB corruption, not a statement diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 72d789f5c726..84c885ad8524 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -785,7 +785,7 @@ class CDeterministicMNManager ~CDeterministicMNManager(); bool ProcessBlock(const CBlock& block, gsl::not_null pindex, BlockValidationState& state, - const CDeterministicMNList& newList, MNListUpdates& updatesRet) + const CDeterministicMNList& oldList, const CDeterministicMNList& newList, MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(!cs, ::cs_main); bool UndoBlock(gsl::not_null pindex, MNListUpdates& updatesRet) EXCLUSIVE_LOCKS_REQUIRED(!cs); diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 4085b40d454e..5b4c4baf0c7c 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -992,13 +992,13 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const // ConnectBlock validates masternode payments against the list at the // previous block, also under fJustCheck where ProcessBlock is skipped. updatesRet.old_list = m_dmnman.GetListForBlock(pindex->pprev); - if (!BuildNewListFromBlock(block, pindex->pprev, rules.v24, view, true, state, mn_list)) { + if (!RebuildListFromBlock(block, pindex->pprev, rules.v24, updatesRet.old_list, view, true, state, mn_list)) { // pass the state returned by the function above return false; } mn_list.SetBlockHash(pindex->GetBlockHash()); - if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list, updatesRet)) { + if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, updatesRet.old_list, mn_list, updatesRet)) { // pass the state returned by the function above return false; } From ca14f98bbbb57d98965b16b1a84b25d18b83f8b4 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 20 Oct 2026 12:17:21 +0700 Subject: [PATCH 4/5] perf: select the block's payee once during block validation FinishListForBlock selected the payee from the previous block's list to update its last-paid height, and IsBlockPayeeValid selected it again from the same list to check the coinbase, each a full walk (two before MN_RR). GetMasternodePayments also walked the list once more in GetCounts() just to see whether it is empty. RebuildListFromBlock hands the payee it selected to its caller, ProcessSpecialTxsInBlock records it in MNListUpdates next to the lists it was chosen from, and the payment check uses it. Block templates and the RPC select the payee themselves as before. --- src/evo/deterministicmns.cpp | 8 ++++++-- src/evo/deterministicmns.h | 4 ++++ src/evo/specialtxman.cpp | 20 +++++++++++++------- src/evo/specialtxman.h | 10 +++++++--- src/masternode/payments.cpp | 6 +++--- src/masternode/payments.h | 3 ++- src/node/miner.cpp | 2 +- src/rpc/masternode.cpp | 3 ++- src/validation.cpp | 6 +++--- src/validation.h | 3 ++- 10 files changed, 43 insertions(+), 22 deletions(-) diff --git a/src/evo/deterministicmns.cpp b/src/evo/deterministicmns.cpp index 376571f2d938..2d07f290ae6b 100644 --- a/src/evo/deterministicmns.cpp +++ b/src/evo/deterministicmns.cpp @@ -746,7 +746,9 @@ bool CDeterministicMNManager::ProcessBlock(const CBlock& block, gsl::not_nullactive()) { const auto counts{newList.GetCounts()}; @@ -795,7 +797,9 @@ bool CDeterministicMNManager::UndoBlock(gsl::not_null pindex curList.ApplyDiff(pindex, diff); auto inversedDiff{curList.BuildDiff(prevList)}; - updatesRet = {.old_list = curList, .new_list = prevList, .diff = inversedDiff}; + updatesRet.old_list = curList; + updatesRet.new_list = prevList; + updatesRet.diff = inversedDiff; } const auto& consensusParams = Params().GetConsensus(); diff --git a/src/evo/deterministicmns.h b/src/evo/deterministicmns.h index 84c885ad8524..b1e1104f0cdf 100644 --- a/src/evo/deterministicmns.h +++ b/src/evo/deterministicmns.h @@ -263,6 +263,8 @@ class CDeterministicMNList InvalidateSMLCache(); } + [[nodiscard]] size_t GetAllMNsCount() const { return mnMap.size(); } + [[nodiscard]] Counts GetCounts() const { Counts ret; @@ -730,6 +732,8 @@ struct MNListUpdates CDeterministicMNList old_list; CDeterministicMNList new_list; CDeterministicMNListDiff diff; + //! The masternode the block pays, as selected from old_list + CDeterministicMNCPtr payee; }; /** Sentinel suffix carried by serving-failure messages that report locally diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 5b4c4baf0c7c..cae7d4ca4d04 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -750,12 +750,11 @@ bool CSpecialTxProcessor::ApplyTxToList(const CTransaction& tx, gsl::not_null pindexPrev, - const CDeterministicMNList& prevList, bool debugLogs, - CDeterministicMNList newList, BlockValidationState& state, + const CDeterministicMNList& prevList, const CDeterministicMNCPtr& payee, + bool debugLogs, CDeterministicMNList newList, BlockValidationState& state, CDeterministicMNList& mnListRet) const { const int nHeight = pindexPrev->nHeight + 1; - auto payee = prevList.GetMNPayee(pindexPrev); const bool isMNRewardReallocation{DeploymentActiveAfter(pindexPrev, m_consensus_params, Consensus::DEPLOYMENT_MN_RR)}; // we skip the coinbase @@ -827,9 +826,13 @@ bool CSpecialTxProcessor::FinishListForBlock(const CBlock& block, gsl::not_null< bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_null pindexPrev, bool is_v24_active, const CDeterministicMNList& prevList, const CCoinsViewCache& view, bool debugLogs, BlockValidationState& state, - CDeterministicMNList& mnListRet) + CDeterministicMNList& mnListRet, CDeterministicMNCPtr* payeeRet) { CDeterministicMNList newList = BeginListForBlock(pindexPrev, prevList); + const CDeterministicMNCPtr payee{prevList.GetMNPayee(pindexPrev)}; + if (payeeRet != nullptr) { + *payeeRet = payee; + } // we skip the coinbase for (int i = 1; i < static_cast(block.vtx.size()); i++) { @@ -838,7 +841,7 @@ bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_nul } } - return FinishListForBlock(block, pindexPrev, prevList, debugLogs, newList, state, mnListRet); + return FinishListForBlock(block, pindexPrev, prevList, payee, debugLogs, newList, state, mnListRet); } CMNListBuilder::CMNListBuilder(const CSpecialTxProcessor& processor, const CDeterministicMNList& prevList, @@ -850,6 +853,7 @@ CMNListBuilder::CMNListBuilder(const CSpecialTxProcessor& processor, const CDete m_is_v24_active{is_v24_active}, m_view{&view}, m_debug_logs{debugLogs}, + m_payee{prevList.GetMNPayee(pindexPrev)}, m_list{processor.BeginListForBlock(pindexPrev, prevList)} { } @@ -869,7 +873,8 @@ bool CMNListBuilder::ApplyTx(const CTransaction& tx, BlockValidationState& state bool CMNListBuilder::Finish(const CBlock& block, BlockValidationState& state, CDeterministicMNList& mnListRet) const { - return m_processor->FinishListForBlock(block, m_pindex_prev, m_prev_list, m_debug_logs, m_list, state, mnListRet); + return m_processor->FinishListForBlock(block, m_pindex_prev, m_prev_list, m_payee, m_debug_logs, m_list, state, + mnListRet); } uint256 CSpecialTxProcessor::GetDeterministicMNListHash(gsl::not_null pindex) const @@ -992,7 +997,8 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(Chainstate& chainstate, const // ConnectBlock validates masternode payments against the list at the // previous block, also under fJustCheck where ProcessBlock is skipped. updatesRet.old_list = m_dmnman.GetListForBlock(pindex->pprev); - if (!RebuildListFromBlock(block, pindex->pprev, rules.v24, updatesRet.old_list, view, true, state, mn_list)) { + if (!RebuildListFromBlock(block, pindex->pprev, rules.v24, updatesRet.old_list, view, true, state, mn_list, + &updatesRet.payee)) { // pass the state returned by the function above return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index f9e3fcaf15e8..05c66ce753df 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -100,9 +100,11 @@ class CSpecialTxProcessor // Variant that takes an explicit starting list instead of loading from GetListForBlock // Used for rebuilding diffs from trusted snapshots + // payeeRet, when given, receives the masternode the block pays as selected from prevList 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); + BlockValidationState& state, CDeterministicMNList& mnListRet, + CDeterministicMNCPtr* payeeRet = nullptr); /** Return a canonical hash of the deterministic MN list derived at a block. */ uint256 GetDeterministicMNListHash(gsl::not_null pindex) const; @@ -121,8 +123,9 @@ class CSpecialTxProcessor const CCoinsViewCache& view, bool debugLogs, CDeterministicMNList& newList, BlockValidationState& state) const; bool FinishListForBlock(const CBlock& block, gsl::not_null pindexPrev, - const CDeterministicMNList& prevList, bool debugLogs, CDeterministicMNList newList, - BlockValidationState& state, CDeterministicMNList& mnListRet) const; + const CDeterministicMNList& prevList, const CDeterministicMNCPtr& payee, bool debugLogs, + CDeterministicMNList newList, BlockValidationState& state, + CDeterministicMNList& mnListRet) const; bool CheckSpecialTxInner(const CChain* chain, const CTransaction& tx, const CBlockIndex* pindexPrev, SpecialTxRules rules, const CCoinsViewCache& view, const std::optional& indexes, @@ -160,6 +163,7 @@ class CMNListBuilder bool m_is_v24_active; const CCoinsViewCache* m_view; bool m_debug_logs; + CDeterministicMNCPtr m_payee; CDeterministicMNList m_list; }; diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index 546971a286de..7bf1605403af 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -208,7 +208,8 @@ bool IsSuperblockValid(const SuperblockStatus& superblock, const CTransaction& t } bool GetMasternodePayments(const CDeterministicMNList& mn_list, const CBlockIndex* pindexPrev, - const CAmount blockSubsidy, const CAmount feeReward, MnRewardEra era, + const CDeterministicMNCPtr& dmnPayee, const CAmount blockSubsidy, const CAmount feeReward, + MnRewardEra era, const Consensus::Params& consensus_params, std::vector& voutMasternodePaymentsRet) { @@ -230,11 +231,10 @@ bool GetMasternodePayments(const CDeterministicMNList& mn_list, const CBlockInde LogPrint(BCLog::MNPAYMENTS, "%s -- MN reward %lld reallocated to credit pool\n", __func__, platformReward); voutMasternodePaymentsRet.emplace_back(platformReward, CScript() << OP_RETURN); } - if (mn_list.GetCounts().total() == 0) { + if (mn_list.GetAllMNsCount() == 0) { LogPrint(BCLog::MNPAYMENTS, "%s -- no masternode registered to receive a payment\n", __func__); return true; } - const auto dmnPayee = mn_list.GetMNPayee(pindexPrev); if (!dmnPayee) { return false; } diff --git a/src/masternode/payments.h b/src/masternode/payments.h index 6c651e2a6bd6..03927d2a352b 100644 --- a/src/masternode/payments.h +++ b/src/masternode/payments.h @@ -6,6 +6,7 @@ #define BITCOIN_MASTERNODE_PAYMENTS_H #include +#include #include #include @@ -65,7 +66,7 @@ CAmount GetMasternodePayment(int nHeight, CAmount blockValue, const Consensus::P * output in that case. */ bool GetMasternodePayments(const CDeterministicMNList& mn_list, const CBlockIndex* pindexPrev, - CAmount blockSubsidy, CAmount feeReward, MnRewardEra era, + const CDeterministicMNCPtr& dmnPayee, CAmount blockSubsidy, CAmount feeReward, MnRewardEra era, const Consensus::Params& consensus_params, std::vector& voutMasternodePaymentsRet); diff --git a/src/node/miner.cpp b/src/node/miner.cpp index 4e06d4d84bb4..cfb26d951ec8 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -190,7 +190,7 @@ void BlockAssembler::FillBlockPayments(CMutableTransaction& txNew, const CBlockI } const MnRewardEra era{GetMnRewardEraAfter(pindexPrev, m_chainstate.m_chainman)}; - if (!GetMasternodePayments(mn_list, pindexPrev, blockSubsidy, feeReward, era, chainparams.GetConsensus(), voutMasternodePaymentsRet)) { + if (!GetMasternodePayments(mn_list, pindexPrev, mn_list.GetMNPayee(pindexPrev), blockSubsidy, feeReward, era, chainparams.GetConsensus(), voutMasternodePaymentsRet)) { LogPrintf("%s -- ERROR Failed to get payee\n", __func__); LogPrint(BCLog::MNPAYMENTS, "%s -- No masternode to pay (MN list probably empty)\n", __func__); } else { diff --git a/src/rpc/masternode.cpp b/src/rpc/masternode.cpp index a731165fd772..4d9192d38f23 100644 --- a/src/rpc/masternode.cpp +++ b/src/rpc/masternode.cpp @@ -456,7 +456,8 @@ static RPCHelpMan masternode_payments() std::vector voutMasternodePayments; CAmount blockSubsidy = GetBlockSubsidy(pindex, Params().GetConsensus()); const MnRewardEra mn_reward_era{GetMnRewardEraAfter(pindex->pprev, chainman)}; - if (!GetMasternodePayments(node.dmnman->GetListForBlock(pindex->pprev), pindex->pprev, blockSubsidy, nBlockFees, + const CDeterministicMNList mn_list{node.dmnman->GetListForBlock(pindex->pprev)}; + if (!GetMasternodePayments(mn_list, pindex->pprev, mn_list.GetMNPayee(pindex->pprev), blockSubsidy, nBlockFees, mn_reward_era, Params().GetConsensus(), voutMasternodePayments)) { LogPrint(BCLog::MNPAYMENTS, "%s -- No masternode to pay (MN list probably empty)\n", self.m_name); } diff --git a/src/validation.cpp b/src/validation.cpp index 470dc215b5dc..c1ddccaca04b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2645,7 +2645,7 @@ bool Chainstate::IsBlockValueValid(const CBlock& block, const CBlockIndex* pinde return true; } -bool Chainstate::IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindex, const CDeterministicMNList& mn_list, const CAmount blockSubsidy, const CAmount feeReward) const +bool Chainstate::IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindex, const CDeterministicMNList& mn_list, const CDeterministicMNCPtr& payee, const CAmount blockSubsidy, const CAmount feeReward) const { const Consensus::Params& consensus_params{m_chainman.GetConsensus()}; const CTransaction& txNew{*block.vtx[0]}; @@ -2657,7 +2657,7 @@ bool Chainstate::IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pinde std::vector voutMasternodePayments; const MnRewardEra era{GetMnRewardEraAfter(pindex->pprev, m_chainman)}; - if (!GetMasternodePayments(mn_list, pindex->pprev, blockSubsidy, feeReward, era, consensus_params, voutMasternodePayments)) { + if (!GetMasternodePayments(mn_list, pindex->pprev, payee, blockSubsidy, feeReward, era, consensus_params, voutMasternodePayments)) { LogPrintf("%s -- ERROR! Failed to get payees for block at height %s\n", __func__, nBlockHeight); return true; } @@ -3071,7 +3071,7 @@ bool Chainstate::ConnectBlock(const CBlock& block, BlockValidationState& state, Ticks(time_value_valid), Ticks(time_value_valid) / num_blocks_total); - if (!IsBlockPayeeValid(block, pindex, mnlist_updates.old_list, blockSubsidy, feeReward)) { + if (!IsBlockPayeeValid(block, pindex, mnlist_updates.old_list, mnlist_updates.payee, blockSubsidy, feeReward)) { // 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"); diff --git a/src/validation.h b/src/validation.h index fb672d7621ac..549c4e8275ad 100644 --- a/src/validation.h +++ b/src/validation.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include // IWYU pragma: export @@ -714,7 +715,7 @@ class Chainstate * triggered superblock, the payments governance expects at that height. */ bool IsBlockValueValid(const CBlock& block, const CBlockIndex* pindex, const CDeterministicMNList& mn_list, CAmount blockReward, std::string& strErrorRet) const; /** Dash: the coinbase must carry every payment owed to the masternode list at the previous block. */ - bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindex, const CDeterministicMNList& mn_list, CAmount blockSubsidy, CAmount feeReward) const; + bool IsBlockPayeeValid(const CBlock& block, const CBlockIndex* pindex, const CDeterministicMNList& mn_list, const CDeterministicMNCPtr& payee, CAmount blockSubsidy, CAmount feeReward) const; // Apply the effects of a block disconnection on the UTXO set. bool DisconnectTip(BlockValidationState& state, DisconnectedBlockTransactions* disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); From 1602784da61a5bae41e40672f83ec3ed6b3fd445 Mon Sep 17 00:00:00 2001 From: pasta Date: Sun, 25 Oct 2026 22:09:40 -0500 Subject: [PATCH 5/5] fix(build): capture miner pointer location at call site --- src/node/miner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/miner.cpp b/src/node/miner.cpp index cfb26d951ec8..06b3a7ca35ad 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -267,7 +267,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc if (fDIP0003Active_context) { // No list-transition logs: a package's trial may still be discarded, and block connection // logs the transitions that actually happen. - mn_list_builder.emplace(m_chainstate.m_special_tx, pindexPrev, fV24Active_context, m_chainstate.CoinsTip(), + mn_list_builder.emplace(m_chainstate.m_special_tx, gsl::not_null{pindexPrev}, fV24Active_context, m_chainstate.CoinsTip(), /*debugLogs=*/false); for (const Consensus::LLMQParams& params : llmq::GetEnabledQuorumParams(m_chainstate.m_chainman, pindexPrev)) { std::vector vqcTx;