From f4079b67557780426b117426e6c89d5c769216ac Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 15:01:05 +0700 Subject: [PATCH 1/7] fix: make regression miner_tests works correctly with InstantSend enabled (part I) --- src/test/miner_tests.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 2ac2bc6d6698..ca35fa31841e 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -48,6 +49,8 @@ struct MinerTestingSetup : public TestingSetup { } BlockAssembler AssemblerForTest(const CChainParams& params); }; + +static constexpr int WAIT_FOR_ISLOCK_TIMEOUT{601}; } // namespace miner_tests BOOST_FIXTURE_TEST_SUITE(miner_tests, MinerTestingSetup) @@ -235,8 +238,11 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C { tx.vout[0].nValue -= LOWFEE; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); bool spendsCoinbase = i == 0; // only first tx spends coinbase // If we don't set the # of sig ops in the CTxMemPoolEntry, template creation fails + m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now()).SpendsCoinbase(spendsCoinbase).FromTx(tx)); tx.vin[0].prevout.hash = hash; } @@ -281,6 +287,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C // orphan in mempool, template creation fails hash = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now()).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); m_node.mempool->clear(); @@ -307,6 +315,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vin[0].scriptSig = CScript() << OP_0 << OP_1; tx.vout[0].nValue = 0; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); // give it a fee so it'll get mined m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now()).SpendsCoinbase(false).FromTx(tx)); // Should throw bad-cb-multiple @@ -322,6 +332,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(Now()).SpendsCoinbase(true).FromTx(tx)); tx.vout[0].scriptPubKey = CScript() << OP_2; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(Now()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("bad-txns-inputs-missingorspent")); m_node.mempool->clear(); @@ -361,11 +373,15 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C CScript script = CScript() << OP_0; tx.vout[0].scriptPubKey = GetScriptForDestination(ScriptHash(script)); hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now()).SpendsCoinbase(true).FromTx(tx)); tx.vin[0].prevout.hash = hash; tx.vin[0].scriptSig = CScript() << std::vector(script.begin(), script.end()); tx.vout[0].nValue -= LOWFEE; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now()).SpendsCoinbase(false).FromTx(tx)); // Should throw block-validation-failed BOOST_CHECK_EXCEPTION(AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey), std::runtime_error, HasReason("block-validation-failed")); From 7fca631ca504ee1db71556033e824a131b7b1381 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 15:37:04 +0700 Subject: [PATCH 2/7] fix: make regression miner_tests works correctly with InstantSend enabled (part II) --- src/test/miner_tests.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index ca35fa31841e..db5a12eb222e 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -398,6 +398,7 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C // non-final txs in mempool SetMockTime(m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() + 1); + int64_t mocked_time_for_is = m_node.chainman->ActiveChain().Tip()->GetMedianTimePast() - WAIT_FOR_ISLOCK_TIMEOUT; const int flags{LOCKTIME_VERIFY_SEQUENCE}; // height map std::vector prevheights; @@ -416,6 +417,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vout[0].scriptPubKey = CScript() << OP_1; tx.nLockTime = 0; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, mocked_time_for_is); m_node.mempool->addUnchecked(entry.Fee(HIGHFEE).Time(Now()).SpendsCoinbase(true).FromTx(tx)); BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail @@ -430,6 +433,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C tx.vin[0].nSequence = CTxIn::SEQUENCE_LOCKTIME_TYPE_FLAG | (((m_node.chainman->ActiveChain().Tip()->GetMedianTimePast()+1-m_node.chainman->ActiveChain()[1]->GetMedianTimePast()) >> CTxIn::SEQUENCE_LOCKTIME_GRANULARITY) + 1); // txFirst[1] is the 3rd block prevheights[0] = baseheight + 2; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, mocked_time_for_is); m_node.mempool->addUnchecked(entry.Time(Now()).FromTx(tx)); BOOST_CHECK(CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime passes BOOST_CHECK(!TestSequenceLocks(CTransaction{tx})); // Sequence locks fail @@ -453,6 +458,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights[0] = baseheight + 3; tx.nLockTime = m_node.chainman->ActiveChain().Tip()->nHeight + 1; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, mocked_time_for_is); m_node.mempool->addUnchecked(entry.Time(Now()).FromTx(tx)); BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass @@ -464,6 +471,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C prevheights.resize(1); prevheights[0] = baseheight + 4; hash = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, mocked_time_for_is); m_node.mempool->addUnchecked(entry.Time(Now()).FromTx(tx)); BOOST_CHECK(!CheckFinalTxAtTip(*Assert(m_node.chainman->ActiveChain().Tip()), CTransaction{tx})); // Locktime fails BOOST_CHECK(TestSequenceLocks(CTransaction{tx})); // Sequence locks pass From f958112d5f1188f3019e9dfab9a3b1d2e7ce3c0b Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 16:33:00 +0700 Subject: [PATCH 3/7] fix: make even more miner_tests friendly to InstantSend enabled (TestPackageSelection, final part) --- src/test/miner_tests.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index db5a12eb222e..1562c679c088 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -135,6 +135,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co uint256 hashHighFeeTx = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(50000).Time(Now()).SpendsCoinbase(false).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hashParentTx, hashMediumFeeTx, hashHighFeeTx}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); std::unique_ptr pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 4U); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashParentTx); @@ -145,6 +147,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.hash = hashHighFeeTx; tx.vout[0].nValue = 5000000000LL - 1000 - 50000; // 0 fee uint256 hashFreeTx = tx.GetHash(); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hashFreeTx}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); m_node.mempool->addUnchecked(entry.Fee(0).FromTx(tx)); size_t freeTxSize = GetVirtualTransactionSize(CTransaction(tx)); @@ -170,6 +174,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue -= 2; // Now we should be just over the min relay fee hashLowFeeTx = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(feeToUse+2).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hashLowFeeTx}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); BOOST_CHECK(pblocktemplate->block.vtx[4]->GetHash() == hashFreeTx); @@ -184,6 +190,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[1].nValue = 100000000; // 1BTC output uint256 hashFreeTx2 = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(true).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({tx.GetHash()}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); // This tx can't be mined by itself tx.vin[0].prevout.hash = hashFreeTx2; @@ -192,6 +200,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vout[0].nValue = 5000000000LL - 100000000 - feeToUse; uint256 hashLowFeeTx2 = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(feeToUse).SpendsCoinbase(false).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({tx.GetHash()}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); // Verify that this tx isn't selected. @@ -205,6 +215,8 @@ void MinerTestingSetup::TestPackageSelection(const CChainParams& chainparams, co tx.vin[0].prevout.n = 1; tx.vout[0].nValue = 100000000 - 10000; // 10k satoshi fee m_node.mempool->addUnchecked(entry.Fee(10000).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({tx.GetHash()}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 9U); BOOST_CHECK(pblocktemplate->block.vtx[8]->GetHash() == hashLowFeeTx2); @@ -258,6 +270,8 @@ void MinerTestingSetup::TestBasicMining(const CChainParams& chainparams, const C hash = tx.GetHash(); bool spendsCoinbase = i == 0; // only first tx spends coinbase // If we do set the # of sig ops in the CTxMemPoolEntry, template creation passes + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hash}, pblocktemplate->block.nTime - WAIT_FOR_ISLOCK_TIMEOUT); m_node.mempool->addUnchecked(entry.Fee(LOWFEE).Time(Now()).SpendsCoinbase(spendsCoinbase).SigOps(20).FromTx(tx)); tx.vin[0].prevout.hash = hash; } @@ -566,6 +580,8 @@ void MinerTestingSetup::TestPrioritisedMining(const CChainParams& chainparams, c uint256 hashFreeGrandchild = tx.GetHash(); m_node.mempool->addUnchecked(entry.Fee(0).SpendsCoinbase(false).FromTx(tx)); + // Age transaction for InstantSend + m_node.clhandler->UpdateTxFirstSeenMap({hashMediumFeeTx, hashPrioritsedChild, hashParentTx, hashFreeParent, hashFreeChild, hashFreeGrandchild, hashFreePrioritisedTx}, std::chrono::duration_cast(Now().time_since_epoch()).count() - WAIT_FOR_ISLOCK_TIMEOUT); auto pblocktemplate = AssemblerForTest(chainparams).CreateNewBlock(scriptPubKey); BOOST_REQUIRE_EQUAL(pblocktemplate->block.vtx.size(), 6U); BOOST_CHECK(pblocktemplate->block.vtx[1]->GetHash() == hashFreeParent); From e9ec8229daabc68d8201d1cb03adb630adc4a72f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 16:40:28 +0700 Subject: [PATCH 4/7] refactor: drop dependency of InstantSend on CMasternodeSync --- src/instantsend/instantsend.cpp | 10 ++-------- src/instantsend/instantsend.h | 5 +---- src/llmq/context.cpp | 7 +++---- src/llmq/context.h | 6 ++---- src/node/chainstate.cpp | 2 +- 5 files changed, 9 insertions(+), 21 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 26d0e3f90559..615669121ab0 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -15,11 +14,9 @@ using node::fImporting; using node::fReindex; namespace llmq { -CInstantSendManager::CInstantSendManager(CSporkManager& sporkman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params) : +CInstantSendManager::CInstantSendManager(CSporkManager& sporkman, const util::DbWrapperParams& db_params) : db{db_params}, - spork_manager{sporkman}, - m_mn_sync{mn_sync} + spork_manager{sporkman} { } @@ -471,9 +468,6 @@ bool CInstantSendManager::IsInstantSendEnabled() const bool CInstantSendManager::RejectConflictingBlocks() const { - if (!m_mn_sync.IsBlockchainSynced()) { - return false; - } if (!spork_manager.IsSporkActive(SPORK_3_INSTANTSEND_BLOCK_FILTERING)) { LogPrint(BCLog::INSTANTSEND, "%s: spork3 is off, skipping transaction locking checks\n", __func__); return false; diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index a9667ab4384c..4bdfc8d2d4a4 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -20,7 +20,6 @@ class CBlockIndex; class CDataStream; -class CMasternodeSync; class CSporkManager; namespace Consensus { struct LLMQParams; @@ -54,7 +53,6 @@ class CInstantSendManager private: instantsend::CInstantSendDb db; CSporkManager& spork_manager; - const CMasternodeSync& m_mn_sync; mutable Mutex cs_pendingLocks; // Incoming and not verified yet @@ -90,8 +88,7 @@ class CInstantSendManager CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; - explicit CInstantSendManager(CSporkManager& sporkman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params); + explicit CInstantSendManager(CSporkManager& sporkman, const util::DbWrapperParams& db_params); ~CInstantSendManager(); void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 3e24e34d01fa..3d28a364fa5b 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -13,9 +13,8 @@ #include LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, - ChainstateManager& chainman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params, int8_t bls_threads, int16_t worker_count, - int64_t max_recsigs_age) : + ChainstateManager& chainman, const util::DbWrapperParams& db_params, int8_t bls_threads, + int16_t worker_count, int64_t max_recsigs_age) : bls_worker{std::make_shared()}, qsnapman{std::make_unique(evo_db)}, quorum_block_processor{std::make_unique(chainman.ActiveChainstate(), dmnman, evo_db, @@ -23,7 +22,7 @@ LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSpork qman{std::make_unique(*bls_worker, dmnman, evo_db, *quorum_block_processor, *qsnapman, chainman, db_params)}, sigman{std::make_unique(*qman, db_params, max_recsigs_age)}, - isman{std::make_unique(sporkman, mn_sync, db_params)} + isman{std::make_unique(sporkman, db_params)} { // Have to start it early to let VerifyDB check ChainLock signatures in coinbase bls_worker->Start(worker_count); diff --git a/src/llmq/context.h b/src/llmq/context.h index 4f3e5e984e3f..8ffc38dfa529 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -13,7 +13,6 @@ class CBLSWorker; class ChainstateManager; class CDeterministicMNManager; class CEvoDB; -class CMasternodeSync; class CSporkManager; class PeerManager; @@ -34,9 +33,8 @@ struct LLMQContext { LLMQContext(const LLMQContext&) = delete; LLMQContext& operator=(const LLMQContext&) = delete; explicit LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, - ChainstateManager& chainman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params, int8_t bls_threads, int16_t worker_count, - int64_t max_recsigs_age); + ChainstateManager& chainman, const util::DbWrapperParams& db_params, int8_t bls_threads, + int16_t worker_count, int64_t max_recsigs_age); ~LLMQContext(); /** Guaranteed if LLMQContext is initialized then all members are valid too diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index 98d4dc21d199..d5211c6a36bb 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -230,7 +230,7 @@ void DashChainstateSetup(ChainstateManager& chainman, dmnman = std::make_unique(evodb, mn_metaman); llmq_ctx.reset(); - llmq_ctx = std::make_unique(*dmnman, evodb, sporkman, chainman, mn_sync, + llmq_ctx = std::make_unique(*dmnman, evodb, sporkman, chainman, util::DbWrapperParams{.path = data_dir, .memory = llmq_dbs_in_memory, .wipe = llmq_dbs_wipe}, bls_threads, worker_count, max_recsigs_age); mempool->ConnectManagers(dmnman.get(), llmq_ctx->isman.get()); From cb54afdcde1cddb0631309c0d4db6a1ab3c45d42 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 11 Mar 2026 17:38:46 +0700 Subject: [PATCH 5/7] refactor: drop dependency of CChainState on CMasternodeSync There's new helper IsValidAndSynced in CGovernanceManager that incapsulate usages of CMasternodeSync in CMNPayment --- src/evo/chainhelper.cpp | 7 +++---- src/evo/chainhelper.h | 6 ++---- src/governance/governance.cpp | 2 ++ src/governance/governance.h | 1 + src/masternode/payments.cpp | 6 +++--- src/masternode/payments.h | 16 ++++++++++------ src/node/chainstate.cpp | 2 +- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index 5c1f19a508d0..8794fceabbc7 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -17,14 +17,13 @@ CChainstateHelper::CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmnman, CGovernanceManager& govman, llmq::CInstantSendManager& isman, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, - const Consensus::Params& consensus_params, const CMasternodeSync& mn_sync, - const CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, - const llmq::CQuorumManager& qman) : + const Consensus::Params& consensus_params, const CSporkManager& sporkman, + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman) : isman{isman}, credit_pool_manager{std::make_unique(evodb, chainman)}, m_chainlocks{chainlocks}, ehf_manager{std::make_unique(evodb, chainman, qman)}, - mn_payments{std::make_unique(dmnman, govman, chainman, consensus_params, mn_sync, sporkman)}, + mn_payments{std::make_unique(dmnman, govman, chainman, consensus_params, sporkman)}, special_tx{std::make_unique(*credit_pool_manager, dmnman, *ehf_manager, qblockman, qsnapman, chainman, consensus_params, chainlocks, qman)} {} diff --git a/src/evo/chainhelper.h b/src/evo/chainhelper.h index 69a229d93cad..eb69e8cfd679 100644 --- a/src/evo/chainhelper.h +++ b/src/evo/chainhelper.h @@ -15,7 +15,6 @@ class CDeterministicMNManager; class CEvoDB; class CGovernanceManager; class ChainstateManager; -class CMasternodeSync; class CMNHFManager; class CMNPaymentsProcessor; class CSpecialTxProcessor; @@ -60,9 +59,8 @@ class CChainstateHelper explicit CChainstateHelper(CEvoDB& evodb, CDeterministicMNManager& dmnman, CGovernanceManager& govman, llmq::CInstantSendManager& isman, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, - const Consensus::Params& consensus_params, const CMasternodeSync& mn_sync, - const CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, - const llmq::CQuorumManager& qman); + const Consensus::Params& consensus_params, const CSporkManager& sporkman, + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman); ~CChainstateHelper(); /** Passthrough functions to chainlock::Chainlocks */ diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index f72c8d837846..530524188f1c 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -96,6 +96,8 @@ bool CGovernanceManager::LoadCache(bool load_cache) return is_valid; } +bool CGovernanceManager::IsValidAndSynced() const { return is_valid && m_mn_sync.IsSynced(); } + void CGovernanceManager::RelayObject(const CGovernanceObject& obj) { AssertLockNotHeld(cs_relay); diff --git a/src/governance/governance.h b/src/governance/governance.h index fa048b40d9d2..58addde58e7b 100644 --- a/src/governance/governance.h +++ b/src/governance/governance.h @@ -273,6 +273,7 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent // Basic initialization and querying bool IsValid() const override { return is_valid; } + bool IsValidAndSynced() const; bool LoadCache(bool load_cache) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); [[nodiscard]] static RPCResult GetJsonHelp(const std::string& key, bool optional); diff --git a/src/masternode/payments.cpp b/src/masternode/payments.cpp index 72fb0cff9b84..933a1295cfc1 100644 --- a/src/masternode/payments.cpp +++ b/src/masternode/payments.cpp @@ -160,7 +160,7 @@ CAmount PlatformShare(const CAmount reward) int nOffset = nBlockHeight % m_consensus_params.nBudgetPaymentsCycleBlocks; if (nOffset < m_consensus_params.nBudgetPaymentsWindowBlocks) { // NOTE: old budget system is disabled since 12.1 - if(m_mn_sync.IsSynced()) { + if (m_govman.IsValidAndSynced()) { // no old budget blocks should be accepted here on mainnet, // testnet/devnet/regtest should produce regular blocks only LogPrint(BCLog::GOBJECT, "CMNPaymentsProcessor::%s -- WARNING! Client synced but old budget system is disabled, checking block value against block reward\n", __func__); @@ -232,7 +232,7 @@ bool CMNPaymentsProcessor::IsBlockValueValid(const CBlock& block, const int nBlo return false; } - if (!m_mn_sync.IsSynced() || !m_govman.IsValid()) { + if (!m_govman.IsValidAndSynced()) { LogPrint(BCLog::MNPAYMENTS, "CMNPaymentsProcessor::%s -- WARNING! Not enough data, checked superblock max bounds only\n", __func__); // not enough data for full checks but at least we know that the superblock limits were honored. // We rely on the network to have followed the correct chain in this case @@ -291,7 +291,7 @@ bool CMNPaymentsProcessor::IsBlockPayeeValid(const CTransaction& txNew, const CB return false; } - if (!m_mn_sync.IsSynced() || !m_govman.IsValid()) { + if (!m_govman.IsValidAndSynced()) { // governance data is either incomplete or non-existent LogPrint(BCLog::MNPAYMENTS, "CMNPaymentsProcessor::%s -- WARNING! Not enough data, skipping superblock payee checks\n", __func__); return true; // not an error diff --git a/src/masternode/payments.h b/src/masternode/payments.h index a4df891c8c1a..4c7c640afbc2 100644 --- a/src/masternode/payments.h +++ b/src/masternode/payments.h @@ -15,7 +15,6 @@ class CBlockIndex; class CDeterministicMNManager; class CGovernanceManager; class ChainstateManager; -class CMasternodeSync; class CTransaction; class CSporkManager; class CTxOut; @@ -37,7 +36,6 @@ class CMNPaymentsProcessor CGovernanceManager& m_govman; const ChainstateManager& m_chainman; const Consensus::Params& m_consensus_params; - const CMasternodeSync& m_mn_sync; const CSporkManager& m_sporkman; private: @@ -50,10 +48,16 @@ class CMNPaymentsProcessor [[nodiscard]] bool IsOldBudgetBlockValueValid(const CBlock& block, const int nBlockHeight, const CAmount blockReward, std::string& strErrorRet); public: - explicit CMNPaymentsProcessor(CDeterministicMNManager& dmnman, CGovernanceManager& govman, const ChainstateManager& chainman, - const Consensus::Params& consensus_params, const CMasternodeSync& mn_sync, const CSporkManager& sporkman) : - m_dmnman{dmnman}, m_govman{govman}, m_chainman{chainman}, m_consensus_params{consensus_params}, m_mn_sync{mn_sync}, - m_sporkman{sporkman} {} + explicit CMNPaymentsProcessor(CDeterministicMNManager& dmnman, CGovernanceManager& govman, + const ChainstateManager& chainman, const Consensus::Params& consensus_params, + const CSporkManager& sporkman) : + m_dmnman{dmnman}, + m_govman{govman}, + m_chainman{chainman}, + m_consensus_params{consensus_params}, + m_sporkman{sporkman} + { + } 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); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index d5211c6a36bb..fed510763de9 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -236,7 +236,7 @@ void DashChainstateSetup(ChainstateManager& chainman, mempool->ConnectManagers(dmnman.get(), llmq_ctx->isman.get()); chain_helper.reset(); chain_helper = std::make_unique(evodb, *dmnman, govman, *(llmq_ctx->isman), *(llmq_ctx->quorum_block_processor), - *(llmq_ctx->qsnapman), chainman, consensus_params, mn_sync, sporkman, chainlocks, + *(llmq_ctx->qsnapman), chainman, consensus_params, sporkman, chainlocks, *(llmq_ctx->qman)); } From 14f5ce4ebafe4cab3cb5cd95cfc5b702e4f25e00 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 19:47:05 +0700 Subject: [PATCH 6/7] refactor: remove usages of CMasternodeSync from chainstate --- src/init.cpp | 1 - src/node/chainstate.cpp | 4 +--- src/node/chainstate.h | 3 --- src/test/util/setup_common.cpp | 3 +-- 4 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 703c50646cc3..80264f04c765 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1992,7 +1992,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) chainman, *node.govman, *node.mn_metaman, - *node.mn_sync, *node.sporkman, *node.chainlocks, node.chain_helper, diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index fed510763de9..f9919d81ffdc 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -37,7 +37,6 @@ std::optional LoadChainstate(bool fReset, ChainstateManager& chainman, CGovernanceManager& govman, CMasternodeMetaMan& mn_metaman, - CMasternodeSync& mn_sync, CSporkManager& sporkman, chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, @@ -83,7 +82,7 @@ std::optional LoadChainstate(bool fReset, pblocktree.reset(); pblocktree.reset(new CBlockTreeDB(nBlockTreeDBCache, block_tree_db_in_memory, fReset)); - DashChainstateSetup(chainman, govman, mn_metaman, mn_sync, sporkman, chainlocks, chain_helper, + DashChainstateSetup(chainman, govman, mn_metaman, sporkman, chainlocks, chain_helper, dmnman, *evodb, llmq_ctx, mempool, data_dir, dash_dbs_in_memory, /*llmq_dbs_wipe=*/fReset || fReindexChainState, bls_threads, worker_count, max_recsigs_age, consensus_params); @@ -209,7 +208,6 @@ std::optional LoadChainstate(bool fReset, void DashChainstateSetup(ChainstateManager& chainman, CGovernanceManager& govman, CMasternodeMetaMan& mn_metaman, - CMasternodeSync& mn_sync, CSporkManager& sporkman, chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 09adc7cdf214..ac6b3f9235da 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -17,7 +17,6 @@ class CEvoDB; class CGovernanceManager; class ChainstateManager; class CMasternodeMetaMan; -class CMasternodeSync; class CSporkManager; class CTxMemPool; struct LLMQContext; @@ -80,7 +79,6 @@ std::optional LoadChainstate(bool fReset, ChainstateManager& chainman, CGovernanceManager& govman, CMasternodeMetaMan& mn_metaman, - CMasternodeSync& mn_sync, CSporkManager& sporkman, chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, @@ -111,7 +109,6 @@ std::optional LoadChainstate(bool fReset, void DashChainstateSetup(ChainstateManager& chainman, CGovernanceManager& govman, CMasternodeMetaMan& mn_metaman, - CMasternodeSync& mn_sync, CSporkManager& sporkman, chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 9847e4b2f913..4eb5e3f45f07 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -143,7 +143,7 @@ void DashChainstateSetup(ChainstateManager& chainman, bool llmq_dbs_wipe, const Consensus::Params& consensus_params) { - DashChainstateSetup(chainman, *Assert(node.govman.get()), *Assert(node.mn_metaman.get()), *Assert(node.mn_sync.get()), + DashChainstateSetup(chainman, *Assert(node.govman.get()), *Assert(node.mn_metaman.get()), *Assert(node.sporkman.get()), *Assert(node.chainlocks), node.chain_helper, node.dmnman, *node.evodb, node.llmq_ctx, Assert(node.mempool.get()), node.args->GetDataDirNet(), llmq_dbs_in_memory, llmq_dbs_wipe, llmq::DEFAULT_BLSCHECK_THREADS, llmq::DEFAULT_WORKER_COUNT, llmq::DEFAULT_MAX_RECOVERED_SIGS_AGE, @@ -323,7 +323,6 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vector Date: Wed, 11 Mar 2026 17:38:46 +0700 Subject: [PATCH 7/7] refactor: new stub implementation (NullNodeSyncNotifier) of NodeSyncNotifier is introduced to use it for CChainState --- src/masternode/sync.cpp | 5 +++++ src/masternode/sync.h | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/masternode/sync.cpp b/src/masternode/sync.cpp index 2b69c71cb47d..e367a24e3e27 100644 --- a/src/masternode/sync.cpp +++ b/src/masternode/sync.cpp @@ -9,6 +9,11 @@ #include #include +#include + +void NullNodeSyncNotifier::SyncReset() { assert(false); } +void NullNodeSyncNotifier::SyncFinished() { assert(false); } + CMasternodeSync::CMasternodeSync(std::unique_ptr&& sync_notifier) : nTimeAssetSyncStarted{GetTime()}, nTimeLastBumped{GetTime()}, diff --git a/src/masternode/sync.h b/src/masternode/sync.h index 247d8d0ff889..98401982727e 100644 --- a/src/masternode/sync.h +++ b/src/masternode/sync.h @@ -32,6 +32,20 @@ class NodeSyncNotifier virtual ~NodeSyncNotifier() = default; }; +/** Stub implementation for use in chainstate-only (non-network) contexts. + * CMasternodeSync constructed with this notifier permanently returns + * IsBlockchainSynced()=false and IsSynced()=false, which correctly disables + * network-dependent validation paths. + * + * Asserts on any call — if sync state is being advanced, a real notifier + * (NodeSyncNotifierImpl) must be used instead. */ +class NullNodeSyncNotifier final : public NodeSyncNotifier +{ +public: + void SyncReset() override; + void SyncFinished() override; +}; + // // CMasternodeSync : Sync masternode assets in stages //