From e2485cafc0452a4c2fefefae7ed20e3876807dd8 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 29 Jan 2026 02:25:05 +0700 Subject: [PATCH 01/42] refactor: move ProcessInstantSendLock to NetInstantSend --- src/init.cpp | 2 +- src/instantsend/instantsend.cpp | 89 ++++++----------------------- src/instantsend/instantsend.h | 18 ++++-- src/instantsend/net_instantsend.cpp | 72 ++++++++++++++++++++++- src/instantsend/net_instantsend.h | 13 ++++- 5 files changed, 115 insertions(+), 79 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 5c2a0bcf0ec2..653740d62902 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2208,7 +2208,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 7d: Setup other Dash services - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate())); + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool)); node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman, node.active_ctx ? node.active_ctx->shareman.get() : nullptr, *node.sporkman)); if (node.active_ctx) { diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 0726da6911e3..92f4b2a7bc0f 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -14,17 +14,9 @@ #include #include #include -#include - -// Forward declaration to break dependency over node/transaction.h -namespace node { -CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, - const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock); -} // namespace node using node::fImporting; using node::fReindex; -using node::GetTransaction; namespace llmq { namespace { @@ -118,22 +110,15 @@ instantsend::PendingState CInstantSendManager::FetchPendingLocks() return ret; } -std::variant CInstantSendManager::ProcessInstantSendLock( - NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) +bool CInstantSendManager::PreVerifyIsLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) const { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", - __func__, islock->txid.ToString(), hash.ToString(), from); - - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->ClearLockFromQueue(islock); - } if (db.KnownInstantSendLock(hash)) { - return std::monostate{}; + return false; } if (const auto sameTxIsLock = db.GetInstantSendLockByTxid(islock->txid)) { // can happen, nothing to do - return std::monostate{}; + return false; } for (const auto& in : islock->inputs) { const auto sameOutpointIsLock = db.GetInstantSendLockByInput(in); @@ -142,62 +127,23 @@ std::variant CInstantSendManager::Proc islock->txid.ToString(), hash.ToString(), in.ToStringShort(), ::SerializeHash(*sameOutpointIsLock).ToString(), from); } } + return true; +} - uint256 hashBlock{}; - auto tx = GetTransaction(nullptr, &mempool, islock->txid, Params().GetConsensus(), hashBlock); - const bool found_transaction{tx != nullptr}; - // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally - std::optional minedHeight = GetBlockHeight(hashBlock); - if (found_transaction) { - if (!minedHeight.has_value()) { - const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); - if (pindexMined != nullptr) { - CacheBlockHeight(pindexMined); - minedHeight = pindexMined->nHeight; - } - } - // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes, - // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain - if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) { - LogPrint(BCLog::INSTANTSEND, /* Continued */ - "CInstantSendManager::%s -- txlock=%s, islock=%s: dropping islock as it already got a " - "ChainLock in block %s, peer=%d\n", - __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from); - return std::monostate{}; - } - } - - if (found_transaction) { - db.WriteNewInstantSendLock(hash, islock); - if (minedHeight.has_value()) { - db.WriteInstantSendLockMined(hash, *minedHeight); - } - } else { - // put it in a separate pending map and try again later - LOCK(cs_pendingLocks); - pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); - } - - // This will also add children TXs to pendingRetryTxs - RemoveNonLockedTx(islock->txid, true); - // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs. - // We only need the ISLOCK from now on to detect conflicts - TruncateRecoveredSigsForInputs(*islock); - ResolveBlockConflicts(hash, *islock); - - if (found_transaction) { - RemoveMempoolConflictsForLock(hash, *islock); - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- notify about lock %s for tx %s\n", __func__, - hash.ToString(), tx->GetHash().ToString()); - GetMainSignals().NotifyTransactionLock(tx, islock); - // bump mempool counter to make sure newly locked txes are picked up by getblocktemplate - mempool.AddTransactionsUpdated(1); +void CInstantSendManager::WriteNewISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, + std::optional minedHeight) +{ + db.WriteNewInstantSendLock(hash, islock); + if (minedHeight.has_value()) { + db.WriteInstantSendLockMined(hash, *minedHeight); } +} - if (found_transaction) { - return tx; - } - return islock->txid; +void CInstantSendManager::AddPendingISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) +{ + // put it in a separate pending map and try again later + LOCK(cs_pendingLocks); + pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); } void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx) @@ -811,4 +757,5 @@ bool CInstantSendManager::RejectConflictingBlocks() const } return true; } + } // namespace llmq diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index dde3739d6030..e11f8ff61b58 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -130,17 +130,23 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent private: void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_timingsTxSeen); + +public: void RemoveNonLockedTx(const uint256& txid, bool retryChildren) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + +private: void RemoveConflictedTx(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); +public: + void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); void RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache); +private: void HandleFullyConfirmedBlock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); @@ -157,9 +163,13 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent [[nodiscard]] std::vector PrepareTxToRetry() EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); CSigningManager& Sigman() { return sigman; } - [[nodiscard]] std::variant ProcessInstantSendLock( - NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache); + const chainlock::Chainlocks& Chainlocks() { return m_chainlocks; } + + void WriteNewISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, std::optional minedHeight); + void AddPendingISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) + EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + + bool PreVerifyIsLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) const; void TransactionAddedToMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 64fcfd0fd8ee..3779e9159e77 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -5,19 +5,27 @@ #include #include -#include +#include #include #include #include #include #include #include +#include #include #include #include #include +// Forward declaration to break dependency over node/transaction.h +namespace node { +CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, + const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock); +} // namespace node + +using node::GetTransaction; namespace { constexpr int BATCH_VERIFIER_SOURCE_THRESHOLD{8}; constexpr int INVALID_ISLOCK_MISBEHAVIOR_SCORE{100}; @@ -311,6 +319,68 @@ void NetInstantSend::ProcessPendingISLocks(std::vector NetInstantSend::ProcessInstantSendLock( + NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) +{ + LogPrint(BCLog::INSTANTSEND, "NetSigning::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__, + islock->txid.ToString(), hash.ToString(), from); + + if (auto signer = m_is_manager.Signer(); signer) { + signer->ClearLockFromQueue(islock); + } + if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return std::monostate{}; + + uint256 hashBlock{}; + auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock); + const bool found_transaction{tx != nullptr}; + // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally + std::optional minedHeight = m_is_manager.GetBlockHeight(hashBlock); + if (found_transaction) { + if (!minedHeight.has_value()) { + const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, + return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); + if (pindexMined != nullptr) { + m_is_manager.CacheBlockHeight(pindexMined); + minedHeight = pindexMined->nHeight; + } + } + // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes, + // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain + if (minedHeight.has_value() && m_is_manager.Chainlocks().HasChainLock(*minedHeight, hashBlock)) { + LogPrint(BCLog::INSTANTSEND, /* Continued */ + "NetSigning::%s -- txlock=%s, islock=%s: dropping islock as it already got a " + "ChainLock in block %s, peer=%d\n", + __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from); + return std::monostate{}; + } + m_is_manager.WriteNewISLock(hash, islock, minedHeight); + } else { + m_is_manager.AddPendingISLock(hash, islock, from); + } + + + // This will also add children TXs to pendingRetryTxs + m_is_manager.RemoveNonLockedTx(islock->txid, true); + // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs. + // We only need the ISLOCK from now on to detect conflicts + m_is_manager.TruncateRecoveredSigsForInputs(*islock); + m_is_manager.ResolveBlockConflicts(hash, *islock); + + if (found_transaction) { + m_is_manager.RemoveMempoolConflictsForLock(hash, *islock); + LogPrint(BCLog::INSTANTSEND, "NetSigning::%s -- notify about lock %s for tx %s\n", __func__, hash.ToString(), + tx->GetHash().ToString()); + GetMainSignals().NotifyTransactionLock(tx, islock); + // bump mempool counter to make sure newly locked txes are picked up by getblocktemplate + m_mempool.AddTransactionsUpdated(1); + } + + if (found_transaction) { + return tx; + } + return islock->txid; +} + void NetInstantSend::WorkThreadMain() { while (!workInterrupt) { diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index 5601cbb5f1c2..f90331cacbb3 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -13,13 +13,18 @@ #include #include #include +#include #include namespace Consensus { struct LLMQParams; } // namespace Consensus + +class CTxMemPool; + namespace instantsend { struct InstantSendLock; +using InstantSendLockPtr = std::shared_ptr; struct PendingISLockEntry; } // namespace instantsend namespace llmq { @@ -31,11 +36,12 @@ class NetInstantSend final : public NetHandler { public: NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, llmq::CQuorumManager& qman, - CChainState& chainstate) : + CChainState& chainstate, CTxMemPool& mempool) : NetHandler(peer_manager), m_is_manager{is_manager}, m_qman(qman), - m_chainstate{chainstate} + m_chainstate{chainstate}, + m_mempool{mempool} { workInterrupt.reset(); } @@ -63,6 +69,8 @@ class NetInstantSend final : public NetHandler const std::vector& pend); void ProcessPendingISLocks(std::vector&& locks_to_process); + std::variant ProcessInstantSendLock( + NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock); Uint256HashSet ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, @@ -70,6 +78,7 @@ class NetInstantSend final : public NetHandler llmq::CInstantSendManager& m_is_manager; llmq::CQuorumManager& m_qman; const CChainState& m_chainstate; + CTxMemPool& m_mempool; std::thread workThread; CThreadInterrupt workInterrupt; From 69e47be92da26c6024474b6004e3c4a9182e16ef Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 29 Jan 2026 02:33:32 +0700 Subject: [PATCH 02/42] refactor: simplify ProcessInstantSendLock by using PeerManager on place instead of returning std::variant --- src/dsnotificationinterface.cpp | 1 - src/init.cpp | 2 +- src/instantsend/instantsend.cpp | 72 ++++++++----------------- src/instantsend/instantsend.h | 22 +++----- src/instantsend/net_instantsend.cpp | 81 ++++++++++++++++++++++------- src/instantsend/net_instantsend.h | 17 +++--- 6 files changed, 101 insertions(+), 94 deletions(-) diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index f2566970adc3..b899b4caccc7 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -85,7 +85,6 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con void CDSNotificationInterface::TransactionAddedToMempool(const CTransactionRef& ptx, int64_t nAcceptTime, uint64_t mempool_sequence) { - Assert(m_llmq_ctx)->isman->TransactionAddedToMempool(ptx); m_dstxman.TransactionAddedToMempool(ptx); } diff --git a/src/init.cpp b/src/init.cpp index 653740d62902..8b6d41042fe6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2208,7 +2208,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 7d: Setup other Dash services - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool)); + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman, node.active_ctx ? node.active_ctx->shareman.get() : nullptr, *node.sporkman)); if (node.active_ctx) { diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 92f4b2a7bc0f..a8b2d61fd272 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -146,41 +146,6 @@ void CInstantSendManager::AddPendingISLock(const uint256& hash, const instantsen pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); } -void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx) -{ - if (!IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced() || tx->vin.empty()) { - return; - } - - instantsend::InstantSendLockPtr islock{nullptr}; - { - LOCK(cs_pendingLocks); - auto it = pendingNoTxInstantSendLocks.begin(); - while (it != pendingNoTxInstantSendLocks.end()) { - if (it->second.islock->txid == tx->GetHash()) { - // we received an islock earlier - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, - tx->GetHash().ToString(), it->first.ToString()); - islock = it->second.islock; - pendingInstantSendLocks.try_emplace(it->first, it->second); - pendingNoTxInstantSendLocks.erase(it); - break; - } - ++it; - } - } - - if (islock == nullptr) { - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->ProcessTx(*tx, false, Params().GetConsensus()); - } - // TX is not locked, so make sure it is tracked - AddNonLockedTx(tx, nullptr); - } else { - RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock); - } -} - void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& tx) { if (tx->vin.empty()) { @@ -243,6 +208,26 @@ void CInstantSendManager::BlockDisconnected(const std::shared_ptr& db.RemoveBlockInstantSendLocks(pblock, pindexDisconnected); } +instantsend::InstantSendLockPtr CInstantSendManager::AttachISLockToTx(const CTransactionRef& tx) +{ + instantsend::InstantSendLockPtr ret_islock{nullptr}; + LOCK(cs_pendingLocks); + auto it = pendingNoTxInstantSendLocks.begin(); + while (it != pendingNoTxInstantSendLocks.end()) { + if (it->second.islock->txid == tx->GetHash()) { + // we received an islock earlier, let's put it back into pending and verify/lock + LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, + tx->GetHash().ToString(), it->first.ToString()); + ret_islock = it->second.islock; + pendingInstantSendLocks.try_emplace(it->first, it->second); + pendingNoTxInstantSendLocks.erase(it); + return ret_islock; + } + ++it; + } + return ret_islock; // not found, nullptr +} + void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) { { @@ -259,22 +244,7 @@ void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlock } } } - { - LOCK(cs_pendingLocks); - auto it = pendingNoTxInstantSendLocks.begin(); - while (it != pendingNoTxInstantSendLocks.end()) { - if (it->second.islock->txid == tx->GetHash()) { - // we received an islock earlier, let's put it back into pending and verify/lock - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, - tx->GetHash().ToString(), it->first.ToString()); - pendingInstantSendLocks.try_emplace(it->first, it->second); - pendingNoTxInstantSendLocks.erase(it); - break; - } - ++it; - } - } - + AttachISLockToTx(tx); if (ShouldReportISLockTiming()) { LOCK(cs_timingsTxSeen); // Only insert the time the first time we see the tx, as we sometimes try to resign diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index e11f8ff61b58..71a32f1ea1f3 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -127,22 +126,17 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent instantsend::InstantSendSigner* Signer() const { return m_signer.load(std::memory_order_acquire); } -private: void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_timingsTxSeen); - -public: void RemoveNonLockedTx(const uint256& txid, bool retryChildren) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); -private: + instantsend::InstantSendLockPtr AttachISLockToTx(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + void RemoveConflictedTx(const CTransaction& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); -public: void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); - void RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache); @@ -171,24 +165,22 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent bool PreVerifyIsLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) const; - void TransactionAddedToMempool(const CTransactionRef& tx) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen); + // -- CValidationInterface + void UpdatedBlockTip(const CBlockIndex* pindexNew) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry, !cs_height_cache); void TransactionRemovedFromMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen, !cs_height_cache); void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + void NotifyChainLock(const CBlockIndex* pindexChainLock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); bool GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); instantsend::InstantSendLockPtr GetInstantSendLockByTxid(const uint256& txid) const; - void NotifyChainLock(const CBlockIndex* pindexChainLock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - void UpdatedBlockTip(const CBlockIndex* pindexNew) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry, !cs_height_cache); - void RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) override diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 3779e9159e77..5aa3495a0079 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -21,7 +22,7 @@ // Forward declaration to break dependency over node/transaction.h namespace node { -CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, +CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const m_mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock); } // namespace node @@ -180,16 +181,7 @@ Uint256HashSet NetInstantSend::ApplyVerificationResults( continue; } - CInv inv(MSG_ISDLOCK, hash); - auto ret = m_is_manager.ProcessInstantSendLock(nodeId, hash, islock); - if (std::holds_alternative(ret)) { - m_peer_manager->PeerRelayInvFiltered(inv, std::get(ret)); - m_peer_manager->PeerAskPeersForTransaction(islock->txid); - } else if (std::holds_alternative(ret)) { - m_peer_manager->PeerRelayInvFiltered(inv, *std::get(ret)); - } else { - assert(std::holds_alternative(ret)); - } + ProcessInstantSendLock(nodeId, hash, islock); // Pass a reconstructed recovered sig to the signing manager to avoid double-verification of the sig. auto it = data.recSigs.find(hash); @@ -290,7 +282,6 @@ Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks( return ApplyVerificationResults(llmq_params, ban, *batch, pend); } - void NetInstantSend::ProcessPendingISLocks(std::vector&& locks_to_process) { // TODO Investigate if leaving this is ok @@ -319,8 +310,7 @@ void NetInstantSend::ProcessPendingISLocks(std::vector NetInstantSend::ProcessInstantSendLock( - NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) +void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) { LogPrint(BCLog::INSTANTSEND, "NetSigning::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), from); @@ -328,7 +318,7 @@ std::variant NetInstantSend::ProcessIn if (auto signer = m_is_manager.Signer(); signer) { signer->ClearLockFromQueue(islock); } - if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return std::monostate{}; + if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return; uint256 hashBlock{}; auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock); @@ -351,7 +341,7 @@ std::variant NetInstantSend::ProcessIn "NetSigning::%s -- txlock=%s, islock=%s: dropping islock as it already got a " "ChainLock in block %s, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from); - return std::monostate{}; + return; } m_is_manager.WriteNewISLock(hash, islock, minedHeight); } else { @@ -367,18 +357,21 @@ std::variant NetInstantSend::ProcessIn m_is_manager.ResolveBlockConflicts(hash, *islock); if (found_transaction) { - m_is_manager.RemoveMempoolConflictsForLock(hash, *islock); + RemoveMempoolConflictsForLock(hash, *islock); LogPrint(BCLog::INSTANTSEND, "NetSigning::%s -- notify about lock %s for tx %s\n", __func__, hash.ToString(), tx->GetHash().ToString()); GetMainSignals().NotifyTransactionLock(tx, islock); - // bump mempool counter to make sure newly locked txes are picked up by getblocktemplate + // bump m_mempool counter to make sure newly locked txes are picked up by getblocktemplate m_mempool.AddTransactionsUpdated(1); } + CInv inv(MSG_ISDLOCK, hash); if (found_transaction) { - return tx; + m_peer_manager->PeerRelayInvFiltered(inv, *tx); + } else { + m_peer_manager->PeerRelayInvFiltered(inv, islock->txid); + m_peer_manager->PeerAskPeersForTransaction(islock->txid); } - return islock->txid; } void NetInstantSend::WorkThreadMain() @@ -402,3 +395,51 @@ void NetInstantSend::WorkThreadMain() } } } + +void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_t, uint64_t mempool_sequence) +{ + if (!m_is_manager.IsInstantSendEnabled() || !m_mn_sync.IsBlockchainSynced() || tx->vin.empty()) { + return; + } + + instantsend::InstantSendLockPtr islock = m_is_manager.AttachISLockToTx(tx); + if (islock == nullptr) { + if (auto signer = m_is_manager.Signer(); signer) { + signer->ProcessTx(*tx, false, Params().GetConsensus()); + } + // TX is not locked, so make sure it is tracked + m_is_manager.AddNonLockedTx(tx, nullptr); + } else { + RemoveMempoolConflictsForLock(::SerializeHash(*islock), *islock); + } +} + +void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock) +{ + Uint256HashMap toDelete; + + { + LOCK(m_mempool.cs); + + for (const auto& in : islock.inputs) { + auto it = m_mempool.mapNextTx.find(in); + if (it == m_mempool.mapNextTx.end()) { + continue; + } + if (it->second->GetHash() != islock.txid) { + toDelete.emplace(it->second->GetHash(), m_mempool.get(it->second->GetHash())); + + LogPrintf("%s -- txid=%s, mempool TX %s with input %s conflicts with islock=%s\n", __func__, + islock.txid.ToString(), it->second->GetHash().ToString(), in.ToStringShort(), hash.ToString()); + } + } + + for (const auto& p : toDelete) { + m_mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT); + } + } + + for (const auto& p : toDelete) { + m_is_manager.RemoveConflictedTx(*p.second); + } +} diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index f90331cacbb3..bc9270ac4065 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -9,17 +9,18 @@ #include #include +#include #include #include #include -#include #include namespace Consensus { struct LLMQParams; } // namespace Consensus +class CMasternodeSync; class CTxMemPool; namespace instantsend { @@ -32,16 +33,17 @@ class CInstantSendManager; class CQuorumManager; } // namespace llmq -class NetInstantSend final : public NetHandler +class NetInstantSend final : public NetHandler, public CValidationInterface { public: NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, llmq::CQuorumManager& qman, - CChainState& chainstate, CTxMemPool& mempool) : + CChainState& chainstate, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : NetHandler(peer_manager), m_is_manager{is_manager}, m_qman(qman), m_chainstate{chainstate}, - m_mempool{mempool} + m_mempool{mempool}, + m_mn_sync{mn_sync} { workInterrupt.reset(); } @@ -53,6 +55,9 @@ class NetInstantSend final : public NetHandler void WorkThreadMain(); +protected: + void TransactionAddedToMempool(const CTransactionRef&, int64_t, uint64_t mempool_sequence) override; + private: struct BatchVerificationData; @@ -69,8 +74,7 @@ class NetInstantSend final : public NetHandler const std::vector& pend); void ProcessPendingISLocks(std::vector&& locks_to_process); - std::variant ProcessInstantSendLock( - NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock); + void ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock); Uint256HashSet ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, @@ -79,6 +83,7 @@ class NetInstantSend final : public NetHandler llmq::CQuorumManager& m_qman; const CChainState& m_chainstate; CTxMemPool& m_mempool; + const CMasternodeSync& m_mn_sync; std::thread workThread; CThreadInterrupt workInterrupt; From 9e97f7cd7c979e93e52a06e8bf032dae06ddf51c Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 29 Jan 2026 14:06:21 +0700 Subject: [PATCH 03/42] refactor: move usages of mempool from CInstantSendManager to NetInstantSend --- src/instantsend/instantsend.cpp | 30 ------------------------------ src/instantsend/net_instantsend.h | 1 + 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index a8b2d61fd272..e01613678132 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -408,36 +408,6 @@ void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) } } -void CInstantSendManager::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock) -{ - Uint256HashMap toDelete; - - { - LOCK(mempool.cs); - - for (const auto& in : islock.inputs) { - auto it = mempool.mapNextTx.find(in); - if (it == mempool.mapNextTx.end()) { - continue; - } - if (it->second->GetHash() != islock.txid) { - toDelete.emplace(it->second->GetHash(), mempool.get(it->second->GetHash())); - - LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: mempool TX %s with input %s conflicts with islock\n", __func__, - islock.txid.ToString(), hash.ToString(), it->second->GetHash().ToString(), in.ToStringShort()); - } - } - - for (const auto& p : toDelete) { - mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT); - } - } - - for (const auto& p : toDelete) { - RemoveConflictedTx(*p.second); - } -} - void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) { // Lets first collect all non-locked TXs which conflict with the given ISLOCK diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index bc9270ac4065..71b9970d73d7 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -75,6 +75,7 @@ class NetInstantSend final : public NetHandler, public CValidationInterface void ProcessPendingISLocks(std::vector&& locks_to_process); void ProcessInstantSendLock(NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock); + void RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock); Uint256HashSet ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, From 49edd0a466fdd7a144d6c271eef7575cd33b764d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 29 Jan 2026 14:11:31 +0700 Subject: [PATCH 04/42] refactor: drop dependency of mempool from instantsend/instantsend --- src/instantsend/instantsend.cpp | 4 +--- src/instantsend/instantsend.h | 6 ++---- src/llmq/context.cpp | 8 ++++---- src/llmq/context.h | 7 +++---- src/node/chainstate.cpp | 2 +- test/lint/lint-circular-dependencies.py | 2 +- 6 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index e01613678132..345bc95fb12e 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include using node::fImporting; @@ -41,14 +40,13 @@ Uint256HashSet GetIdsFromLockable(const std::vector& vec) } // anonymous namespace CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CChainState& chainstate, - CSigningManager& _sigman, CSporkManager& sporkman, CTxMemPool& _mempool, + CSigningManager& _sigman, CSporkManager& sporkman, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : db{db_params}, m_chainlocks{chainlocks}, m_chainstate{chainstate}, sigman{_sigman}, spork_manager{sporkman}, - mempool{_mempool}, m_mn_sync{mn_sync} { } diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 71a32f1ea1f3..05810f140d3d 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -28,7 +28,6 @@ class CChainState; class CDataStream; class CMasternodeSync; class CSporkManager; -class CTxMemPool; namespace Consensus { struct LLMQParams; } // namespace Consensus @@ -70,7 +69,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CChainState& m_chainstate; CSigningManager& sigman; CSporkManager& spork_manager; - CTxMemPool& mempool; const CMasternodeSync& m_mn_sync; std::atomic m_signer{nullptr}; @@ -112,8 +110,8 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CChainState& chainstate, - CSigningManager& _sigman, CSporkManager& sporkman, CTxMemPool& _mempool, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); + CSigningManager& _sigman, CSporkManager& sporkman, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params); ~CInstantSendManager(); void ConnectSigner(gsl::not_null signer) diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index f617c2f77342..2dbba1050925 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -13,9 +13,9 @@ #include LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, - chainlock::Chainlocks& chainlocks, CTxMemPool& mempool, ChainstateManager& chainman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params, int8_t bls_threads, - int16_t worker_count, int64_t max_recsigs_age) : + chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, const CMasternodeSync& mn_sync, + 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, @@ -24,7 +24,7 @@ LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSpork chainman, db_params)}, sigman{std::make_unique(*qman, db_params, max_recsigs_age)}, isman{std::make_unique(chainlocks, chainman.ActiveChainstate(), *sigman, sporkman, - mempool, mn_sync, db_params)} + mn_sync, 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 5f8a4ea65378..1eb314d7ad3d 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -15,7 +15,6 @@ class CDeterministicMNManager; class CEvoDB; class CMasternodeSync; class CSporkManager; -class CTxMemPool; class PeerManager; namespace chainlock { @@ -39,9 +38,9 @@ struct LLMQContext { LLMQContext(const LLMQContext&) = delete; LLMQContext& operator=(const LLMQContext&) = delete; explicit LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, - chainlock::Chainlocks& chainlocks, CTxMemPool& mempool, ChainstateManager& chainman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params, int8_t bls_threads, - int16_t worker_count, int64_t max_recsigs_age); + chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, const CMasternodeSync& mn_sync, + 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 98ac8e3d4f04..d1b45ae14956 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, chainlocks, *mempool, chainman, mn_sync, + llmq_ctx = std::make_unique(*dmnman, evodb, sporkman, chainlocks, chainman, mn_sync, 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()); diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index db7472d9eaef..289c81da1b29 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -42,7 +42,7 @@ "evo/specialtxman -> validation -> evo/specialtxman", "governance/classes -> governance/object -> governance/governance -> governance/classes", "governance/governance -> governance/signing -> governance/object -> governance/governance", - "instantsend/instantsend -> txmempool -> instantsend/instantsend", + "instantsend/instantsend -> validation -> txmempool -> instantsend/instantsend", "llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> llmq/blockprocessor", "llmq/commitment -> llmq/utils -> llmq/snapshot -> llmq/commitment", "llmq/dkgsessionhandler -> net_processing -> llmq/dkgsessionmgr -> llmq/dkgsessionhandler", From 20194958640e9e0581005476f742c2dfd96a8c65 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 30 Jan 2026 01:14:18 +0700 Subject: [PATCH 05/42] refactor: move all notification handlers from CInstantSendManager to NetInstantSend --- src/dsnotificationinterface.cpp | 33 +++---------- src/dsnotificationinterface.h | 14 ++---- src/init.cpp | 2 +- src/instantsend/instantsend.cpp | 75 +++++------------------------ src/instantsend/instantsend.h | 16 ++---- src/instantsend/net_instantsend.cpp | 69 ++++++++++++++++++++++++++ src/instantsend/net_instantsend.h | 11 +++-- src/net_processing.cpp | 4 +- 8 files changed, 105 insertions(+), 119 deletions(-) diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index b899b4caccc7..271ce2daa752 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -5,34 +5,25 @@ #include -#include -#include - #include #include #include #include #include -#include -#include -#include -#include #include +#include +#include -CDSNotificationInterface::CDSNotificationInterface(CConnman& connman, - CDSTXManager& dstxman, - CMasternodeSync& mn_sync, - CGovernanceManager& govman, - const ChainstateManager& chainman, - const std::unique_ptr& dmnman, - const std::unique_ptr& llmq_ctx) : + +CDSNotificationInterface::CDSNotificationInterface(CConnman& connman, CDSTXManager& dstxman, CMasternodeSync& mn_sync, + CGovernanceManager& govman, const ChainstateManager& chainman, + const std::unique_ptr& dmnman) : m_connman{connman}, m_dstxman{dstxman}, m_mn_sync{mn_sync}, m_govman{govman}, m_chainman{chainman}, - m_dmnman{dmnman}, - m_llmq_ctx{llmq_ctx} + m_dmnman{dmnman} { } @@ -76,7 +67,6 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con m_dstxman.UpdatedBlockTip(pindexNew); } - m_llmq_ctx->isman->UpdatedBlockTip(pindexNew); if (m_govman.IsValid()) { m_govman.UpdatedBlockTip(pindexNew); } @@ -88,21 +78,13 @@ void CDSNotificationInterface::TransactionAddedToMempool(const CTransactionRef& m_dstxman.TransactionAddedToMempool(ptx); } -void CDSNotificationInterface::TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason, - uint64_t mempool_sequence) -{ - Assert(m_llmq_ctx)->isman->TransactionRemovedFromMempool(ptx); -} - void CDSNotificationInterface::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) { - Assert(m_llmq_ctx)->isman->BlockConnected(pblock, pindex); m_dstxman.BlockConnected(pblock, pindex); } void CDSNotificationInterface::BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) { - Assert(m_llmq_ctx)->isman->BlockDisconnected(pblock, pindexDisconnected); m_dstxman.BlockDisconnected(pblock, pindexDisconnected); } @@ -117,7 +99,6 @@ void CDSNotificationInterface::NotifyMasternodeListChanged(bool undo, const CDet void CDSNotificationInterface::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig) { - Assert(m_llmq_ctx)->isman->NotifyChainLock(pindex); if (m_mn_sync.IsBlockchainSynced()) { m_dstxman.NotifyChainLock(pindex); } diff --git a/src/dsnotificationinterface.h b/src/dsnotificationinterface.h index f8ae3b02560e..07d49332fd60 100644 --- a/src/dsnotificationinterface.h +++ b/src/dsnotificationinterface.h @@ -13,7 +13,6 @@ class CDeterministicMNManager; class CGovernanceManager; class ChainstateManager; class CMasternodeSync; -struct LLMQContext; class CDSNotificationInterface : public CValidationInterface { @@ -21,13 +20,9 @@ class CDSNotificationInterface : public CValidationInterface CDSNotificationInterface() = delete; CDSNotificationInterface(const CDSNotificationInterface&) = delete; CDSNotificationInterface& operator=(const CDSNotificationInterface&) = delete; - explicit CDSNotificationInterface(CConnman& connman, - CDSTXManager& dstxman, - CMasternodeSync& mn_sync, - CGovernanceManager& govman, - const ChainstateManager& chainman, - const std::unique_ptr& dmnman, - const std::unique_ptr& llmq_ctx); + explicit CDSNotificationInterface(CConnman& connman, CDSTXManager& dstxman, CMasternodeSync& mn_sync, + CGovernanceManager& govman, const ChainstateManager& chainman, + const std::unique_ptr& dmnman); virtual ~CDSNotificationInterface(); // a small helper to initialize current block height in sub-modules on startup @@ -40,8 +35,6 @@ class CDSNotificationInterface : public CValidationInterface void SynchronousUpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t mempool_sequence) override; - void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason, - uint64_t mempool_sequence) override; void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) override; void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) override; void NotifyMasternodeListChanged(bool undo, const CDeterministicMNList& oldMNList, const CDeterministicMNListDiff& diff) override; @@ -54,7 +47,6 @@ class CDSNotificationInterface : public CValidationInterface CGovernanceManager& m_govman; const ChainstateManager& m_chainman; const std::unique_ptr& m_dmnman; - const std::unique_ptr& m_llmq_ctx; }; extern std::unique_ptr g_ds_notification_interface; diff --git a/src/init.cpp b/src/init.cpp index 8b6d41042fe6..ee9e07d5f80a 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2176,7 +2176,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) RegisterValidationInterface(node.peerman.get()); g_ds_notification_interface = std::make_unique( - *node.connman, *node.dstxman, *node.mn_sync, *node.govman, chainman, node.dmnman, node.llmq_ctx + *node.connman, *node.dstxman, *node.mn_sync, *node.govman, chainman, node.dmnman // todo: replace unique_ptr for dmnman to reference ); RegisterValidationInterface(g_ds_notification_interface.get()); diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 345bc95fb12e..9a3811605f88 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -144,13 +144,13 @@ void CInstantSendManager::AddPendingISLock(const uint256& hash, const instantsen pendingNoTxInstantSendLocks.try_emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); } -void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& tx) +void CInstantSendManager::TransactionIsRemoved(const CTransactionRef& tx) { if (tx->vin.empty()) { return; } - instantsend::InstantSendLockPtr islock = db.GetInstantSendLockByTxid(tx->GetHash()); + instantsend::InstantSendLockPtr islock = GetInstantSendLockByTxid(tx->GetHash()); if (islock == nullptr) { return; @@ -161,49 +161,14 @@ void CInstantSendManager::TransactionRemovedFromMempool(const CTransactionRef& t RemoveConflictingLock(::SerializeHash(*islock), *islock); } -void CInstantSendManager::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) +void CInstantSendManager::WriteBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex) { - if (!IsInstantSendEnabled()) { - return; - } - - CacheTipHeight(pindex); - - if (m_mn_sync.IsBlockchainSynced()) { - const bool has_chainlock = m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash()); - for (const auto& tx : pblock->vtx) { - if (tx->IsCoinBase() || tx->vin.empty()) { - // coinbase and TXs with no inputs can't be locked - continue; - } - - if (!IsLocked(tx->GetHash()) && !has_chainlock) { - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->ProcessTx(*tx, true, Params().GetConsensus()); - } - // TX is not locked, so make sure it is tracked - AddNonLockedTx(tx, pindex); - } else { - // TX is locked, so make sure we don't track it anymore - RemoveNonLockedTx(tx->GetHash(), true); - } - } - } - db.WriteBlockInstantSendLocks(pblock, pindex); } -void CInstantSendManager::BlockDisconnected(const std::shared_ptr& pblock, - const CBlockIndex* pindexDisconnected) +void CInstantSendManager::RemoveBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex) { - { - LOCK(cs_height_cache); - m_cached_block_heights.erase(pindexDisconnected->GetBlockHash()); - } - - CacheTipHeight(pindexDisconnected->pprev); - - db.RemoveBlockInstantSendLocks(pblock, pindexDisconnected); + db.RemoveBlockInstantSendLocks(pblock, pindex); } instantsend::InstantSendLockPtr CInstantSendManager::AttachISLockToTx(const CTransactionRef& tx) @@ -340,30 +305,6 @@ void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeI } } -void CInstantSendManager::NotifyChainLock(const CBlockIndex* pindexChainLock) -{ - HandleFullyConfirmedBlock(pindexChainLock); -} - -void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) -{ - CacheTipHeight(pindexNew); - - bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height; - - if (m_chainlocks.IsEnabled() && fDIP0008Active) { - // Nothing to do here. We should keep all islocks and let chainlocks handle them. - return; - } - - int nConfirmedHeight = pindexNew->nHeight - Params().GetConsensus().nInstantSendKeepLock; - const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight); - - if (pindex) { - HandleFullyConfirmedBlock(pindex); - } -} - void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) { if (!IsInstantSendEnabled()) { @@ -633,6 +574,12 @@ void CInstantSendManager::CacheBlockHeight(const CBlockIndex* const block_index) CacheBlockHeightInternal(block_index); } +void CInstantSendManager::CacheDisconnectBlock(const CBlockIndex* pindexDisconnected) +{ + LOCK(cs_height_cache); + m_cached_block_heights.erase(pindexDisconnected->GetBlockHash()); +} + std::optional CInstantSendManager::GetBlockHeight(const uint256& hash) const { if (hash.IsNull()) { diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 05810f140d3d..7b5adc42b1fd 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -142,7 +142,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void HandleFullyConfirmedBlock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); -public: bool IsLocked(const uint256& txHash) const override; bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const override; @@ -157,28 +156,20 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CSigningManager& Sigman() { return sigman; } const chainlock::Chainlocks& Chainlocks() { return m_chainlocks; } + void RemoveBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex); + void WriteBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex); void WriteNewISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, std::optional minedHeight); void AddPendingISLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); bool PreVerifyIsLock(const uint256& hash, const instantsend::InstantSendLockPtr& islock, NodeId from) const; - // -- CValidationInterface - void UpdatedBlockTip(const CBlockIndex* pindexNew) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry, !cs_height_cache); - void TransactionRemovedFromMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_timingsTxSeen, !cs_height_cache); - void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) - EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - void NotifyChainLock(const CBlockIndex* pindexChainLock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - - bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); bool GetInstantSendLockByHash(const uint256& hash, instantsend::InstantSendLock& ret) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); instantsend::InstantSendLockPtr GetInstantSendLockByTxid(const uint256& txid) const; + void TransactionIsRemoved(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) override @@ -195,6 +186,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent Counts GetCounts() const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks, !cs_nonLocked); void CacheBlockHeight(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + void CacheDisconnectBlock(const CBlockIndex* pindexDisconnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); std::optional GetBlockHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void CacheTipHeight(const CBlockIndex* const tip) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); int GetTipHeight() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 5aa3495a0079..d1779bb30d36 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -443,3 +444,71 @@ void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const in m_is_manager.RemoveConflictedTx(*p.second); } } + +void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +{ + m_is_manager.CacheTipHeight(pindexNew); + + bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height; + + if (m_is_manager.Chainlocks().IsEnabled() && fDIP0008Active) { + // Nothing to do here. We should keep all islocks and let chainlocks handle them. + return; + } + + int nConfirmedHeight = pindexNew->nHeight - Params().GetConsensus().nInstantSendKeepLock; + const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight); + + if (pindex) { + m_is_manager.HandleFullyConfirmedBlock(pindex); + } +} + +void NetInstantSend::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, + uint64_t mempool_sequence) +{ + m_is_manager.TransactionIsRemoved(tx); +} + +void NetInstantSend::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) +{ + if (!m_is_manager.IsInstantSendEnabled()) { + return; + } + + m_is_manager.CacheTipHeight(pindex); + + if (m_mn_sync.IsBlockchainSynced()) { + const bool has_chainlock = m_is_manager.Chainlocks().HasChainLock(pindex->nHeight, pindex->GetBlockHash()); + for (const auto& tx : pblock->vtx) { + if (tx->IsCoinBase() || tx->vin.empty()) { + // coinbase and TXs with no inputs can't be locked + continue; + } + + if (!m_is_manager.IsLocked(tx->GetHash()) && !has_chainlock) { + if (auto signer = m_is_manager.Signer(); signer) { + signer->ProcessTx(*tx, true, Params().GetConsensus()); + } + // TX is not locked, so make sure it is tracked + m_is_manager.AddNonLockedTx(tx, pindex); + } else { + // TX is locked, so make sure we don't track it anymore + m_is_manager.RemoveNonLockedTx(tx->GetHash(), true); + } + } + } + m_is_manager.WriteBlockISLocks(pblock, pindex); +} + +void NetInstantSend::BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) +{ + m_is_manager.CacheDisconnectBlock(pindexDisconnected); + m_is_manager.CacheTipHeight(pindexDisconnected->pprev); + m_is_manager.RemoveBlockISLocks(pblock, pindexDisconnected); +} + +void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig) +{ + m_is_manager.HandleFullyConfirmedBlock(pindex); +} diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index 71b9970d73d7..b02f0d1d965e 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -6,8 +6,6 @@ #define BITCOIN_INSTANTSEND_NET_INSTANTSEND_H #include -#include - #include #include @@ -56,7 +54,14 @@ class NetInstantSend final : public NetHandler, public CValidationInterface void WorkThreadMain(); protected: + // -- CValidationInterface + void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; void TransactionAddedToMempool(const CTransactionRef&, int64_t, uint64_t mempool_sequence) override; + void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason, + uint64_t mempool_sequence) override; + void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) override; + void BlockDisconnected(const std::shared_ptr& pblock, const CBlockIndex* pindexDisconnected) override; + void NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig) override; private: struct BatchVerificationData; @@ -82,7 +87,7 @@ class NetInstantSend final : public NetHandler, public CValidationInterface const std::vector& pend); llmq::CInstantSendManager& m_is_manager; llmq::CQuorumManager& m_qman; - const CChainState& m_chainstate; + CChainState& m_chainstate; CTxMemPool& m_mempool; const CMasternodeSync& m_mn_sync; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index cfa5845a78c5..057fd3bf49c1 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -4768,7 +4768,7 @@ void PeerManagerImpl::ProcessMessage( // We will continue to reject this tx since it has rejected // parents so avoid re-requesting it from other peers. m_recent_rejects.insert(tx.GetHash()); - m_llmq_ctx->isman->TransactionRemovedFromMempool(ptx); + m_llmq_ctx->isman->TransactionIsRemoved(ptx); } } else { m_recent_rejects.insert(tx.GetHash()); @@ -4799,7 +4799,7 @@ void PeerManagerImpl::ProcessMessage( pfrom.GetId(), state.ToString()); MaybePunishNodeForTx(pfrom.GetId(), state); - m_llmq_ctx->isman->TransactionRemovedFromMempool(ptx); + m_llmq_ctx->isman->TransactionIsRemoved(ptx); } return; } From eb05faa1d172b12e8c28f3721ad31287c888af7c Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 14:17:37 +0700 Subject: [PATCH 06/42] refactor: move ResolveBlockConflicts to NetInstantSend --- src/instantsend/instantsend.cpp | 67 ++++------------------------- src/instantsend/instantsend.h | 6 ++- src/instantsend/net_instantsend.cpp | 66 +++++++++++++++++++++++++++- src/instantsend/net_instantsend.h | 3 ++ 4 files changed, 80 insertions(+), 62 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 9a3811605f88..714825ff4b9f 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -347,7 +347,8 @@ void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) } } -void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) +std::unordered_map> CInstantSendManager::RetrieveISConflicts( + const uint256& islockHash, const instantsend::InstantSendLock& islock) { // Lets first collect all non-locked TXs which conflict with the given ISLOCK std::unordered_map> conflicts; @@ -375,65 +376,13 @@ void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const } } - // Lets see if any of the conflicts was already mined into a ChainLocked block - bool hasChainLockedConflict = false; - for (const auto& p : conflicts) { - const auto* pindex = p.first; - if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { - hasChainLockedConflict = true; - break; - } - } - - // If a conflict was mined into a ChainLocked block, then we have no other choice and must prune the ISLOCK and all - // chained ISLOCKs that build on top of this one. The probability of this is practically zero and can only happen - // when large parts of the masternode network are controlled by an attacker. In this case we must still find - // consensus and its better to sacrifice individual ISLOCKs then to sacrifice whole ChainLocks. - if (hasChainLockedConflict) { - LogPrintf("CInstantSendManager::%s -- txid=%s, islock=%s: at least one conflicted TX already got a ChainLock\n", - __func__, islock.txid.ToString(), islockHash.ToString()); - RemoveConflictingLock(islockHash, islock); - return; - } - - bool isLockedTxKnown = WITH_LOCK(cs_pendingLocks, return pendingNoTxInstantSendLocks.find(islockHash) == - pendingNoTxInstantSendLocks.end()); - - bool activateBestChain = false; - for (const auto& p : conflicts) { - const auto* pindex = p.first; - for (const auto& p2 : p.second) { - const auto& tx = *p2.second; - RemoveConflictedTx(tx); - } - - LogPrintf("CInstantSendManager::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString()); - - BlockValidationState state; - // need non-const pointer - auto pindex2 = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(pindex->GetBlockHash())); - if (!m_chainstate.InvalidateBlock(state, pindex2)) { - LogPrintf("CInstantSendManager::%s -- InvalidateBlock failed: %s\n", __func__, state.ToString()); - // This should not have happened and we are in a state were it's not safe to continue anymore - assert(false); - } - if (isLockedTxKnown) { - activateBestChain = true; - } else { - LogPrintf("CInstantSendManager::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString()); - LOCK(::cs_main); - m_chainstate.ResetBlockFailureFlags(pindex2); - } - } + return conflicts; +} - if (activateBestChain) { - BlockValidationState state; - if (!m_chainstate.ActivateBestChain(state)) { - LogPrintf("CInstantSendManager::%s -- ActivateBestChain failed: %s\n", __func__, state.ToString()); - // This should not have happened and we are in a state were it's not safe to continue anymore - assert(false); - } - } +bool CInstantSendManager::IsKnownTx(const uint256& islockHash) const +{ + LOCK(cs_pendingLocks); + return pendingNoTxInstantSendLocks.find(islockHash) == pendingNoTxInstantSendLocks.end(); } void CInstantSendManager::RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock) diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 7b5adc42b1fd..ddb8e2e1a482 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -135,8 +135,10 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); - void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry, !cs_height_cache); + std::unordered_map> RetrieveISConflicts( + const uint256& islockHash, const instantsend::InstantSendLock& islock) + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks); + bool IsKnownTx(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); private: void HandleFullyConfirmedBlock(const CBlockIndex* pindex) diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index d1779bb30d36..62c031eeb110 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -355,7 +355,7 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs. // We only need the ISLOCK from now on to detect conflicts m_is_manager.TruncateRecoveredSigsForInputs(*islock); - m_is_manager.ResolveBlockConflicts(hash, *islock); + ResolveBlockConflicts(hash, *islock); if (found_transaction) { RemoveMempoolConflictsForLock(hash, *islock); @@ -512,3 +512,67 @@ void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::share { m_is_manager.HandleFullyConfirmedBlock(pindex); } + +void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) +{ + auto conflicts = m_is_manager.RetrieveISConflicts(islockHash, islock); + + // Lets see if any of the conflicts was already mined into a ChainLocked block + bool hasChainLockedConflict = false; + for (const auto& p : conflicts) { + const auto* pindex = p.first; + if (m_is_manager.Chainlocks().HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { + hasChainLockedConflict = true; + break; + } + } + + // If a conflict was mined into a ChainLocked block, then we have no other choice and must prune the ISLOCK and all + // chained ISLOCKs that build on top of this one. The probability of this is practically zero and can only happen + // when large parts of the masternode network are controlled by an attacker. In this case we must still find + // consensus and its better to sacrifice individual ISLOCKs then to sacrifice whole ChainLocks. + if (hasChainLockedConflict) { + LogPrintf("NetInstantSend::%s -- txid=%s, islock=%s: at least one conflicted TX already got a ChainLock\n", + __func__, islock.txid.ToString(), islockHash.ToString()); + m_is_manager.RemoveConflictingLock(islockHash, islock); + return; + } + + bool isLockedTxKnown = m_is_manager.IsKnownTx(islockHash); + + bool activateBestChain = false; + for (const auto& p : conflicts) { + const auto* pindex = p.first; + for (const auto& p2 : p.second) { + const auto& tx = *p2.second; + m_is_manager.RemoveConflictedTx(tx); + } + + LogPrintf("NetInstantSend::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString()); + + BlockValidationState state; + // need non-const pointer + auto pindex2 = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(pindex->GetBlockHash())); + if (!m_chainstate.InvalidateBlock(state, pindex2)) { + LogPrintf("NetInstantSend::%s -- InvalidateBlock failed: %s\n", __func__, state.ToString()); + // This should not have happened and we are in a state were it's not safe to continue anymore + assert(false); + } + if (isLockedTxKnown) { + activateBestChain = true; + } else { + LogPrintf("NetInstantSend::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString()); + LOCK(::cs_main); + m_chainstate.ResetBlockFailureFlags(pindex2); + } + } + + if (activateBestChain) { + BlockValidationState state; + if (!m_chainstate.ActivateBestChain(state)) { + LogPrintf("NetInstantSend::%s -- ActivateBestChain failed: %s\n", __func__, state.ToString()); + // This should not have happened and we are in a state were it's not safe to continue anymore + assert(false); + } + } +} diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index b02f0d1d965e..b9c2bfd8f689 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -85,6 +85,9 @@ class NetInstantSend final : public NetHandler, public CValidationInterface Uint256HashSet ProcessPendingInstantSendLocks( const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, const std::vector& pend); + + void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock); + llmq::CInstantSendManager& m_is_manager; llmq::CQuorumManager& m_qman; CChainState& m_chainstate; From 5ff6893f169c772cf512ae7eb7c9796fd59415d3 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 14:28:58 +0700 Subject: [PATCH 07/42] refactor: use only cached height of tip, don't retrieve it directly ever --- src/instantsend/instantsend.cpp | 17 +++++++---------- src/instantsend/net_instantsend.cpp | 6 +++++- src/instantsend/net_instantsend.h | 2 ++ 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 714825ff4b9f..cc7fd69f3576 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -562,17 +562,14 @@ void CInstantSendManager::CacheTipHeight(const CBlockIndex* const tip) const int CInstantSendManager::GetTipHeight() const { - { - LOCK(cs_height_cache); - if (m_cached_tip_height >= 0) { - return m_cached_tip_height; - } + // It returns the cached tip height which is updated through notification mechanism + // If cached tip is not set by any reason, it's okay to return 0 because + // chainstate is not fully loaded yet and tip is not set + LOCK(cs_height_cache); + if (m_cached_tip_height >= 0) { + return m_cached_tip_height; } - - const CBlockIndex* tip = WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip()); - - CacheTipHeight(tip); - return tip ? tip->nHeight : -1; + return 0; } bool CInstantSendManager::IsInstantSendEnabled() const diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 62c031eeb110..7b5db51270d7 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -445,10 +445,14 @@ void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const in } } -void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +void NetInstantSend::SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, + bool fInitialDownload) { m_is_manager.CacheTipHeight(pindexNew); +} +void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +{ bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height; if (m_is_manager.Chainlocks().IsEnabled() && fDIP0008Active) { diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index b9c2bfd8f689..b23334a68bfb 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -55,6 +55,8 @@ class NetInstantSend final : public NetHandler, public CValidationInterface protected: // -- CValidationInterface + void SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, + bool fInitialDownload) override; void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override; void TransactionAddedToMempool(const CTransactionRef&, int64_t, uint64_t mempool_sequence) override; void TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason, From 29d33d70b2f8500e0022eb928be5322774f57859 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 15:01:04 +0700 Subject: [PATCH 08/42] refactor: remove dependency of CInstantSendManager on chainstate --- src/instantsend/instantsend.cpp | 38 +++++++------------------ src/instantsend/instantsend.h | 13 +++------ src/instantsend/net_instantsend.cpp | 23 +++++++++++++-- src/instantsend/signing.cpp | 28 +++++++++++------- src/instantsend/signing.h | 3 +- src/llmq/context.cpp | 3 +- test/lint/lint-circular-dependencies.py | 2 +- 7 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index cc7fd69f3576..536e1abf3500 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -12,7 +12,6 @@ #include #include #include -#include using node::fImporting; using node::fReindex; @@ -39,12 +38,11 @@ Uint256HashSet GetIdsFromLockable(const std::vector& vec) } } // anonymous namespace -CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CChainState& chainstate, - CSigningManager& _sigman, CSporkManager& sporkman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : +CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSigningManager& _sigman, + CSporkManager& sporkman, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params) : db{db_params}, m_chainlocks{chainlocks}, - m_chainstate{chainstate}, sigman{_sigman}, spork_manager{sporkman}, m_mn_sync{mn_sync} @@ -511,16 +509,10 @@ CInstantSendManager::Counts CInstantSendManager::GetCounts() const return ret; } -void CInstantSendManager::CacheBlockHeightInternal(const CBlockIndex* const block_index) const -{ - AssertLockHeld(cs_height_cache); - m_cached_block_heights.insert(block_index->GetBlockHash(), block_index->nHeight); -} - void CInstantSendManager::CacheBlockHeight(const CBlockIndex* const block_index) const { LOCK(cs_height_cache); - CacheBlockHeightInternal(block_index); + m_cached_block_heights.insert(block_index->GetBlockHash(), block_index->nHeight); } void CInstantSendManager::CacheDisconnectBlock(const CBlockIndex* pindexDisconnected) @@ -529,31 +521,21 @@ void CInstantSendManager::CacheDisconnectBlock(const CBlockIndex* pindexDisconne m_cached_block_heights.erase(pindexDisconnected->GetBlockHash()); } -std::optional CInstantSendManager::GetBlockHeight(const uint256& hash) const +std::optional CInstantSendManager::GetCachedHeight(const uint256& hash) const { - if (hash.IsNull()) { - return std::nullopt; - } - { - LOCK(cs_height_cache); - int cached_height{0}; - if (m_cached_block_heights.get(hash, cached_height)) return cached_height; - } + LOCK(cs_height_cache); - const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hash)); - if (pindex == nullptr) { - return std::nullopt; - } + int cached_height{0}; + if (m_cached_block_heights.get(hash, cached_height)) return cached_height; - CacheBlockHeight(pindex); - return pindex->nHeight; + return std::nullopt; } void CInstantSendManager::CacheTipHeight(const CBlockIndex* const tip) const { LOCK(cs_height_cache); if (tip) { - CacheBlockHeightInternal(tip); + m_cached_block_heights.insert(tip->GetBlockHash(), tip->nHeight); m_cached_tip_height = tip->nHeight; } else { m_cached_tip_height = -1; diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index ddb8e2e1a482..ef380f425616 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -24,7 +24,6 @@ #include class CBlockIndex; -class CChainState; class CDataStream; class CMasternodeSync; class CSporkManager; @@ -66,7 +65,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent instantsend::CInstantSendDb db; const chainlock::Chainlocks& m_chainlocks; - CChainState& m_chainstate; CSigningManager& sigman; CSporkManager& spork_manager; const CMasternodeSync& m_mn_sync; @@ -103,15 +101,12 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent GUARDED_BY(cs_height_cache); mutable int m_cached_tip_height GUARDED_BY(cs_height_cache){-1}; - void CacheBlockHeightInternal(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(cs_height_cache); - public: CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; - explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CChainState& chainstate, - CSigningManager& _sigman, CSporkManager& sporkman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params); + explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSigningManager& _sigman, CSporkManager& sporkman, + const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); ~CInstantSendManager(); void ConnectSigner(gsl::not_null signer) @@ -187,9 +182,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent }; Counts GetCounts() const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks, !cs_nonLocked); - void CacheBlockHeight(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + void CacheBlockHeight(const CBlockIndex* const block_index) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void CacheDisconnectBlock(const CBlockIndex* pindexDisconnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - std::optional GetBlockHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + std::optional GetCachedHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void CacheTipHeight(const CBlockIndex* const tip) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); int GetTipHeight() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 7b5db51270d7..a29c9f03e99d 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -36,6 +36,23 @@ constexpr int OLD_ACTIVE_SET_FAILURE_MISBEHAVIOR_SCORE{20}; constexpr auto WORK_THREAD_SLEEP_INTERVAL{std::chrono::milliseconds{100}}; } // namespace +static std::optional GetBlockHeight(llmq::CInstantSendManager& is_manager, const CChainState& chainstate, + const uint256& hash) +{ + if (hash.IsNull()) { + return std::nullopt; + } + auto ret = is_manager.GetCachedHeight(hash); + if (ret) return ret; + + const CBlockIndex* pindex = WITH_LOCK(::cs_main, return chainstate.m_blockman.LookupBlockIndex(hash)); + if (pindex == nullptr) { + return std::nullopt; + } + is_manager.CacheBlockHeight(pindex); + return pindex->nHeight; +} + struct NetInstantSend::BatchVerificationData { CBLSBatchVerifier batchVerifier{false, true, BATCH_VERIFIER_SOURCE_THRESHOLD}; Uint256HashMap recSigs; @@ -55,7 +72,7 @@ bool NetInstantSend::ValidateIncomingISLock(const instantsend::InstantSendLock& std::optional NetInstantSend::ResolveCycleHeight(const uint256& cycle_hash) { - auto cycle_height = m_is_manager.GetBlockHeight(cycle_hash); + auto cycle_height = GetBlockHeight(m_is_manager, m_chainstate, cycle_hash); if (cycle_height) { return cycle_height; } @@ -113,7 +130,7 @@ std::unique_ptr NetInstantSend::BuildVeri continue; } - auto cycleHeightOpt = m_is_manager.GetBlockHeight(islock->cycleHash); + auto cycleHeightOpt = GetBlockHeight(m_is_manager, m_chainstate, islock->cycleHash); if (!cycleHeightOpt) { data->batchVerifier.badSources.emplace(nodeId); continue; @@ -325,7 +342,7 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock); const bool found_transaction{tx != nullptr}; // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally - std::optional minedHeight = m_is_manager.GetBlockHeight(hashBlock); + std::optional minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock); if (found_transaction) { if (!minedHeight.has_value()) { const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index 2bd953e1c1ee..f22a74d0f935 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -207,28 +207,36 @@ bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, return false; } - const auto blockHeight = m_isman.GetBlockHeight(hashBlock); - if (!blockHeight) { - if (printDebug) { - LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n", __func__, - txHash.ToString(), outpoint.hash.ToString()); + int blockHeight{0}; + if (!hashBlock.IsNull()) { + auto ret = m_isman.GetCachedHeight(hashBlock); + if (ret) blockHeight = *ret; + + const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); + if (pindex == nullptr) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n", + __func__, txHash.ToString(), outpoint.hash.ToString()); + } + return false; } - return false; + m_isman.CacheBlockHeight(pindex); + blockHeight = pindex->nHeight; } const int tipHeight = m_isman.GetTipHeight(); - if (tipHeight < *blockHeight) { + if (tipHeight < blockHeight) { if (printDebug) { LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: cached tip height %d is below block height %d for parent TX %s\n", - __func__, txHash.ToString(), tipHeight, *blockHeight, outpoint.hash.ToString()); + __func__, txHash.ToString(), tipHeight, blockHeight, outpoint.hash.ToString()); } return false; } - const int nTxAge = tipHeight - *blockHeight + 1; + const int nTxAge = tipHeight - blockHeight + 1; - if (nTxAge < nInstantSendConfirmationsRequired && !m_chainlocks.HasChainLock(*blockHeight, hashBlock)) { + if (nTxAge < nInstantSendConfirmationsRequired && !m_chainlocks.HasChainLock(blockHeight, hashBlock)) { if (printDebug) { LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: outpoint %s too new and not ChainLocked. nTxAge=%d, nInstantSendConfirmationsRequired=%d\n", __func__, txHash.ToString(), outpoint.ToStringShort(), nTxAge, nInstantSendConfirmationsRequired); diff --git a/src/instantsend/signing.h b/src/instantsend/signing.h index 18860a22ffbd..4e3fc4437001 100644 --- a/src/instantsend/signing.h +++ b/src/instantsend/signing.h @@ -38,8 +38,9 @@ class InstantSendSignerParent virtual bool IsLocked(const uint256& txHash) const = 0; virtual InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const = 0; virtual void TryEmplacePendingLock(const uint256& hash, const NodeId id, const InstantSendLockPtr& islock) = 0; - virtual std::optional GetBlockHeight(const uint256& hash) const = 0; + virtual std::optional GetCachedHeight(const uint256& hash) const = 0; virtual int GetTipHeight() const = 0; + virtual void CacheBlockHeight(const CBlockIndex* const block_index) const = 0; }; class InstantSendSigner final : public llmq::CRecoveredSigsListener diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 2dbba1050925..b5ea2916a71a 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -23,8 +23,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(chainlocks, chainman.ActiveChainstate(), *sigman, sporkman, - mn_sync, db_params)} + isman{std::make_unique(chainlocks, *sigman, sporkman, mn_sync, db_params)} { // Have to start it early to let VerifyDB check ChainLock signatures in coinbase bls_worker->Start(worker_count); diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 289c81da1b29..93df62fb1a91 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -42,7 +42,7 @@ "evo/specialtxman -> validation -> evo/specialtxman", "governance/classes -> governance/object -> governance/governance -> governance/classes", "governance/governance -> governance/signing -> governance/object -> governance/governance", - "instantsend/instantsend -> validation -> txmempool -> instantsend/instantsend", + "instantsend/instantsend -> instantsend/signing -> validation -> txmempool -> instantsend/instantsend", "llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> llmq/blockprocessor", "llmq/commitment -> llmq/utils -> llmq/snapshot -> llmq/commitment", "llmq/dkgsessionhandler -> net_processing -> llmq/dkgsessionmgr -> llmq/dkgsessionhandler", From 00533ea2ffc814ff0a0b72b7576c95f8b6da6562 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 21:58:11 +0700 Subject: [PATCH 09/42] refactor: remove dependency of CInstantSendManager on signer --- src/active/context.cpp | 3 - src/active/context.h | 3 +- src/init.cpp | 2 +- src/instantsend/instantsend.cpp | 113 ++++++++-------------------- src/instantsend/instantsend.h | 26 +------ src/instantsend/net_instantsend.cpp | 102 ++++++++++++++++++++----- src/instantsend/net_instantsend.h | 13 +++- 7 files changed, 132 insertions(+), 130 deletions(-) diff --git a/src/active/context.cpp b/src/active/context.cpp index 831a1b4ccc67..bca4df5a8316 100644 --- a/src/active/context.cpp +++ b/src/active/context.cpp @@ -32,7 +32,6 @@ ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman const CMasternodeSync& mn_sync, const CBLSSecretKey& operator_sk, const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, bool quorums_recovery, bool quorums_watch) : - m_isman{isman}, m_qman{qman}, nodeman{std::make_unique(connman, dmnman, operator_sk)}, dkgdbgman{std::make_unique(dmnman, qsnapman, chainman)}, @@ -53,14 +52,12 @@ ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman qblockman, qsnapman, *nodeman, chainman, sporkman, llmq_params, quorums_watch, quorum_idx); }); - m_isman.ConnectSigner(is_signer.get()); m_qman.ConnectManagers(qman_handler.get(), qdkgsman.get()); } ActiveContext::~ActiveContext() { m_qman.DisconnectManagers(); - m_isman.DisconnectSigner(); } void ActiveContext::Start(CConnman& connman, PeerManager& peerman, int16_t worker_count) diff --git a/src/active/context.h b/src/active/context.h index c93690c4bc4a..74562b8295f5 100644 --- a/src/active/context.h +++ b/src/active/context.h @@ -54,7 +54,6 @@ struct DbWrapperParams; struct ActiveContext final : public CValidationInterface { private: - llmq::CInstantSendManager& m_isman; llmq::CQuorumManager& m_qman; public: @@ -97,6 +96,8 @@ struct ActiveContext final : public CValidationInterface { const std::unique_ptr ehf_sighandler; const std::unique_ptr qman_handler; const std::unique_ptr cl_signer; + +public: const std::unique_ptr is_signer; /** Owned by PeerManager, use GetCJServer() */ diff --git a/src/init.cpp b/src/init.cpp index ee9e07d5f80a..2f0c00b52b52 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2208,7 +2208,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 7d: Setup other Dash services - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman, node.active_ctx ? node.active_ctx->shareman.get() : nullptr, *node.sporkman)); if (node.active_ctx) { diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 536e1abf3500..775615e7d0c5 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -17,27 +17,6 @@ using node::fImporting; using node::fReindex; namespace llmq { -namespace { -template - requires std::same_as || std::same_as -Uint256HashSet GetIdsFromLockable(const std::vector& vec) -{ - Uint256HashSet ret{}; - if (vec.empty()) return ret; - ret.reserve(vec.size()); - for (const auto& in : vec) { - if constexpr (std::is_same_v) { - ret.emplace(instantsend::GenInputLockRequestId(in)); - } else if constexpr (std::is_same_v) { - ret.emplace(instantsend::GenInputLockRequestId(in.prevout)); - } else { - assert(false); - } - } - return ret; -} -} // anonymous namespace - CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSigningManager& _sigman, CSporkManager& sporkman, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : @@ -274,25 +253,6 @@ std::vector CInstantSendManager::PrepareTxToRetry() return txns; } -void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx) -{ - RemoveNonLockedTx(tx.GetHash(), false); - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->ClearInputsFromQueue(GetIdsFromLockable(tx.vin)); - } -} - -void CInstantSendManager::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock) -{ - auto ids = GetIdsFromLockable(islock.inputs); - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->ClearInputsFromQueue(ids); - } - for (const auto& id : ids) { - sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id); - } -} - void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) { @@ -303,48 +263,6 @@ void CInstantSendManager::TryEmplacePendingLock(const uint256& hash, const NodeI } } -void CInstantSendManager::HandleFullyConfirmedBlock(const CBlockIndex* pindex) -{ - if (!IsInstantSendEnabled()) { - return; - } - - auto removeISLocks = db.RemoveConfirmedInstantSendLocks(pindex->nHeight); - - for (const auto& [islockHash, islock] : removeISLocks) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", __func__, - islock->txid.ToString(), islockHash.ToString()); - - // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts - // from now on. All inputs are spent now and can't be spend in any other TX. - TruncateRecoveredSigsForInputs(*islock); - - // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered - // fully confirmed now - sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, islock->GetRequestId()); - } - - db.RemoveArchivedInstantSendLocks(pindex->nHeight - 100); - - // Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them - // from the nonLockedTxs map. Also collect all children of these TXs and mark them for retrying of IS locking. - std::vector toRemove; - { - LOCK(cs_nonLocked); - for (const auto& p : nonLockedTxs) { - const auto* pindexMined = p.second.pindexMined; - - if (pindexMined && pindex->GetAncestor(pindexMined->nHeight) == pindexMined) { - toRemove.emplace_back(p.first); - } - } - } - for (const auto& txid : toRemove) { - // This will also add children to pendingRetryTxs - RemoveNonLockedTx(txid, true); - } -} - std::unordered_map> CInstantSendManager::RetrieveISConflicts( const uint256& islockHash, const instantsend::InstantSendLock& islock) { @@ -571,4 +489,35 @@ bool CInstantSendManager::RejectConflictingBlocks() const return true; } +void CInstantSendManager::RemoveArchivedInstantSendLocks(int nUntilHeight) +{ + db.RemoveArchivedInstantSendLocks(nUntilHeight); +} + +Uint256HashMap CInstantSendManager::RemoveConfirmedInstantSendLocks(int nUntilHeight) +{ + return db.RemoveConfirmedInstantSendLocks(nUntilHeight); +} + +void CInstantSendManager::RemoveNonLockedForConfirmed(const CBlockIndex* pindex) +{ + // Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them + // from the nonLockedTxs map. Also collect all children of these TXs and mark them for retrying of IS locking. + std::vector toRemove; + { + LOCK(cs_nonLocked); + for (const auto& p : nonLockedTxs) { + const auto* pindexMined = p.second.pindexMined; + + if (pindexMined && pindex->GetAncestor(pindexMined->nHeight) == pindexMined) { + toRemove.emplace_back(p.first); + } + } + } + for (const auto& txid : toRemove) { + // This will also add children to pendingRetryTxs + RemoveNonLockedTx(txid, true); + } +} + } // namespace llmq diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index ef380f425616..0bc342eaf079 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -16,7 +16,6 @@ #include #include -#include #include #include #include @@ -39,7 +38,6 @@ class Chainlocks; } namespace instantsend { -class InstantSendSigner; struct PendingISLockFromPeer { NodeId node_id; @@ -69,8 +67,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CSporkManager& spork_manager; const CMasternodeSync& m_mn_sync; - std::atomic m_signer{nullptr}; - mutable Mutex cs_pendingLocks; // Incoming and not verified yet Uint256HashMap pendingInstantSendLocks GUARDED_BY(cs_pendingLocks); @@ -109,16 +105,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); ~CInstantSendManager(); - void ConnectSigner(gsl::not_null signer) - { - // Prohibit double initialization - assert(m_signer.load(std::memory_order_acquire) == nullptr); - m_signer.store(signer, std::memory_order_release); - } - void DisconnectSigner() { m_signer.store(nullptr, std::memory_order_release); } - - instantsend::InstantSendSigner* Signer() const { return m_signer.load(std::memory_order_acquire); } - void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_timingsTxSeen); void RemoveNonLockedTx(const uint256& txid, bool retryChildren) @@ -126,19 +112,11 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent instantsend::InstantSendLockPtr AttachISLockToTx(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); - void RemoveConflictedTx(const CTransaction& tx) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - - void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); std::unordered_map> RetrieveISConflicts( const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks); bool IsKnownTx(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); -private: - void HandleFullyConfirmedBlock(const CBlockIndex* pindex) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - bool IsLocked(const uint256& txHash) const override; bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const override; @@ -195,6 +173,10 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent * allows ChainLocks to continue even while this spork is disabled. */ bool RejectConflictingBlocks() const; + void RemoveArchivedInstantSendLocks(int nUntilHeight); + Uint256HashMap RemoveConfirmedInstantSendLocks(int nUntilHeight); + EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); + void RemoveNonLockedForConfirmed(const CBlockIndex* pindex); }; } // namespace llmq diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index a29c9f03e99d..c7e4fd98bd4f 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -218,6 +219,27 @@ Uint256HashSet NetInstantSend::ApplyVerificationResults( return badISLocks; } +namespace { +template + requires std::same_as || std::same_as +Uint256HashSet GetIdsFromLockable(const std::vector& vec) +{ + Uint256HashSet ret{}; + if (vec.empty()) return ret; + ret.reserve(vec.size()); + for (const auto& in : vec) { + if constexpr (std::is_same_v) { + ret.emplace(instantsend::GenInputLockRequestId(in)); + } else if constexpr (std::is_same_v) { + ret.emplace(instantsend::GenInputLockRequestId(in.prevout)); + } else { + assert(false); + } + } + return ret; +} +} // anonymous namespace + void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv) { if (msg_type != NetMsgType::ISDLOCK) { @@ -333,8 +355,8 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co LogPrint(BCLog::INSTANTSEND, "NetSigning::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), from); - if (auto signer = m_is_manager.Signer(); signer) { - signer->ClearLockFromQueue(islock); + if (m_signer) { + m_signer->ClearLockFromQueue(islock); } if (!m_is_manager.PreVerifyIsLock(hash, islock, from)) return; @@ -371,7 +393,7 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co m_is_manager.RemoveNonLockedTx(islock->txid, true); // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs. // We only need the ISLOCK from now on to detect conflicts - m_is_manager.TruncateRecoveredSigsForInputs(*islock); + TruncateRecoveredSigsForInputs(*islock); ResolveBlockConflicts(hash, *islock); if (found_transaction) { @@ -402,8 +424,8 @@ void NetInstantSend::WorkThreadMain() if (!locks.empty()) { ProcessPendingISLocks(std::move(locks)); } - if (auto signer = m_is_manager.Signer(); signer) { - signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry()); + if (m_signer) { + m_signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry()); } return more_work; }(); @@ -422,8 +444,8 @@ void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_ instantsend::InstantSendLockPtr islock = m_is_manager.AttachISLockToTx(tx); if (islock == nullptr) { - if (auto signer = m_is_manager.Signer(); signer) { - signer->ProcessTx(*tx, false, Params().GetConsensus()); + if (m_signer) { + m_signer->ProcessTx(*tx, false, Params().GetConsensus()); } // TX is not locked, so make sure it is tracked m_is_manager.AddNonLockedTx(tx, nullptr); @@ -432,6 +454,16 @@ void NetInstantSend::TransactionAddedToMempool(const CTransactionRef& tx, int64_ } } +void NetInstantSend::ClearConflicting(const Uint256HashMap& to_delete) +{ + for (const auto& [_, tx] : to_delete) { + m_is_manager.RemoveNonLockedTx(tx->GetHash(), false); + if (m_signer) { + m_signer->ClearInputsFromQueue(GetIdsFromLockable(tx->vin)); + } + } +} + void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const instantsend::InstantSendLock& islock) { Uint256HashMap toDelete; @@ -456,10 +488,7 @@ void NetInstantSend::RemoveMempoolConflictsForLock(const uint256& hash, const in m_mempool.removeRecursive(*p.second, MemPoolRemovalReason::CONFLICT); } } - - for (const auto& p : toDelete) { - m_is_manager.RemoveConflictedTx(*p.second); - } + ClearConflicting(toDelete); } void NetInstantSend::SynchronousUpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, @@ -481,7 +510,7 @@ void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockI const CBlockIndex* pindex = pindexNew->GetAncestor(nConfirmedHeight); if (pindex) { - m_is_manager.HandleFullyConfirmedBlock(pindex); + HandleFullyConfirmedBlock(pindex); } } @@ -508,8 +537,8 @@ void NetInstantSend::BlockConnected(const std::shared_ptr& pblock, } if (!m_is_manager.IsLocked(tx->GetHash()) && !has_chainlock) { - if (auto signer = m_is_manager.Signer(); signer) { - signer->ProcessTx(*tx, true, Params().GetConsensus()); + if (m_signer) { + m_signer->ProcessTx(*tx, true, Params().GetConsensus()); } // TX is not locked, so make sure it is tracked m_is_manager.AddNonLockedTx(tx, pindex); @@ -531,7 +560,7 @@ void NetInstantSend::BlockDisconnected(const std::shared_ptr& pblo void NetInstantSend::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig) { - m_is_manager.HandleFullyConfirmedBlock(pindex); + HandleFullyConfirmedBlock(pindex); } void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) @@ -564,10 +593,7 @@ void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const inst bool activateBestChain = false; for (const auto& p : conflicts) { const auto* pindex = p.first; - for (const auto& p2 : p.second) { - const auto& tx = *p2.second; - m_is_manager.RemoveConflictedTx(tx); - } + ClearConflicting(p.second); LogPrintf("NetInstantSend::%s -- invalidating block %s\n", __func__, pindex->GetBlockHash().ToString()); @@ -597,3 +623,41 @@ void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const inst } } } + +void NetInstantSend::TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock) +{ + auto ids = GetIdsFromLockable(islock.inputs); + if (m_signer) { + m_signer->ClearInputsFromQueue(ids); + } + for (const auto& id : ids) { + m_is_manager.Sigman().TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id); + } +} + +void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex) +{ + if (!m_is_manager.IsInstantSendEnabled()) { + return; + } + + auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex->nHeight); + + for (const auto& [islockHash, islock] : removeISLocks) { + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", + __func__, islock->txid.ToString(), islockHash.ToString()); + + // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts + // from now on. All inputs are spent now and can't be spend in any other TX. + TruncateRecoveredSigsForInputs(*islock); + + // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered + // fully confirmed now + m_is_manager.Sigman().TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, + islock->GetRequestId()); + } + + m_is_manager.RemoveArchivedInstantSendLocks(pindex->nHeight - 100); + + m_is_manager.RemoveNonLockedForConfirmed(pindex); +} diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index b23334a68bfb..338ca7f12ec4 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -25,6 +25,7 @@ namespace instantsend { struct InstantSendLock; using InstantSendLockPtr = std::shared_ptr; struct PendingISLockEntry; +class InstantSendSigner; } // namespace instantsend namespace llmq { class CInstantSendManager; @@ -34,10 +35,12 @@ class CQuorumManager; class NetInstantSend final : public NetHandler, public CValidationInterface { public: - NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, llmq::CQuorumManager& qman, - CChainState& chainstate, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : + NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, + instantsend::InstantSendSigner* signer, llmq::CQuorumManager& qman, CChainState& chainstate, + CTxMemPool& mempool, const CMasternodeSync& mn_sync) : NetHandler(peer_manager), m_is_manager{is_manager}, + m_signer{signer}, m_qman(qman), m_chainstate{chainstate}, m_mempool{mempool}, @@ -90,7 +93,13 @@ class NetInstantSend final : public NetHandler, public CValidationInterface void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock); + void TruncateRecoveredSigsForInputs(const instantsend::InstantSendLock& islock); + + void HandleFullyConfirmedBlock(const CBlockIndex* pindex); + void ClearConflicting(const Uint256HashMap& to_delete); + llmq::CInstantSendManager& m_is_manager; + instantsend::InstantSendSigner* m_signer; // non-null only for masternode llmq::CQuorumManager& m_qman; CChainState& m_chainstate; CTxMemPool& m_mempool; From 69547f7ae2ae66ae0ed1b1d7ec865142b3375814 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 22:07:37 +0700 Subject: [PATCH 10/42] refactor: re-order clean-up calls for confirmed block for instant-send handling --- src/instantsend/instantsend.cpp | 25 +++++++++++++++---------- src/instantsend/instantsend.h | 4 +--- src/instantsend/net_instantsend.cpp | 17 ++--------------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 775615e7d0c5..526c5361c58e 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -489,18 +489,22 @@ bool CInstantSendManager::RejectConflictingBlocks() const return true; } -void CInstantSendManager::RemoveArchivedInstantSendLocks(int nUntilHeight) +Uint256HashMap CInstantSendManager::RemoveConfirmedInstantSendLocks(const CBlockIndex* pindex) { - db.RemoveArchivedInstantSendLocks(nUntilHeight); -} + int nUntilHeight = pindex->nHeight; + auto removeISLocks = db.RemoveConfirmedInstantSendLocks(nUntilHeight); -Uint256HashMap CInstantSendManager::RemoveConfirmedInstantSendLocks(int nUntilHeight) -{ - return db.RemoveConfirmedInstantSendLocks(nUntilHeight); -} + for (const auto& [islockHash, islock] : removeISLocks) { + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", + __func__, islock->txid.ToString(), islockHash.ToString()); + + // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered + // fully confirmed now + sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, islock->GetRequestId()); + } + + db.RemoveArchivedInstantSendLocks(nUntilHeight - 100); -void CInstantSendManager::RemoveNonLockedForConfirmed(const CBlockIndex* pindex) -{ // Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them // from the nonLockedTxs map. Also collect all children of these TXs and mark them for retrying of IS locking. std::vector toRemove; @@ -518,6 +522,7 @@ void CInstantSendManager::RemoveNonLockedForConfirmed(const CBlockIndex* pindex) // This will also add children to pendingRetryTxs RemoveNonLockedTx(txid, true); } -} + return removeISLocks; +} } // namespace llmq diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 0bc342eaf079..383f1324d2fe 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -173,10 +173,8 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent * allows ChainLocks to continue even while this spork is disabled. */ bool RejectConflictingBlocks() const; - void RemoveArchivedInstantSendLocks(int nUntilHeight); - Uint256HashMap RemoveConfirmedInstantSendLocks(int nUntilHeight); + Uint256HashMap RemoveConfirmedInstantSendLocks(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - void RemoveNonLockedForConfirmed(const CBlockIndex* pindex); }; } // namespace llmq diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index c7e4fd98bd4f..11fb7d874622 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -641,23 +641,10 @@ void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex) return; } - auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex->nHeight); - - for (const auto& [islockHash, islock] : removeISLocks) { - LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", - __func__, islock->txid.ToString(), islockHash.ToString()); - + auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex); + for (const auto& [_, islock] : removeISLocks) { // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts // from now on. All inputs are spent now and can't be spend in any other TX. TruncateRecoveredSigsForInputs(*islock); - - // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered - // fully confirmed now - m_is_manager.Sigman().TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, - islock->GetRequestId()); } - - m_is_manager.RemoveArchivedInstantSendLocks(pindex->nHeight - 100); - - m_is_manager.RemoveNonLockedForConfirmed(pindex); } From 6d99e673f6b49bd0f115c85df438a275431ff31a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 22:26:13 +0700 Subject: [PATCH 11/42] refactor: drop helper interface InstantSendSignerParent --- src/init.cpp | 1 + src/instantsend/instantsend.cpp | 1 - src/instantsend/instantsend.h | 17 ++++++++--------- src/instantsend/signing.cpp | 12 ++++++------ src/instantsend/signing.h | 18 +++--------------- test/lint/lint-circular-dependencies.py | 2 +- 6 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 2f0c00b52b52..e824ab533813 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -97,6 +97,7 @@ #include #include #include +#include #include #include #include diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 526c5361c58e..15c2bcaf27af 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 383f1324d2fe..067f125585fe 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -7,7 +7,6 @@ #include #include -#include #include #include @@ -57,7 +56,7 @@ struct PendingState { namespace llmq { class CSigningManager; -class CInstantSendManager final : public instantsend::InstantSendSignerParent +class CInstantSendManager { private: instantsend::CInstantSendDb db; @@ -117,9 +116,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks); bool IsKnownTx(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); - bool IsLocked(const uint256& txHash) const override; + bool IsLocked(const uint256& txHash) const; bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); - instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const override; + instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const; /* Helpers for communications between CInstantSendManager & NetInstantSend */ // This helper returns up to 32 pending locks and remove them from queue of pending @@ -147,7 +146,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void TransactionIsRemoved(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void RemoveConflictingLock(const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - void TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) override + void TryEmplacePendingLock(const uint256& hash, const NodeId id, const instantsend::InstantSendLockPtr& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); size_t GetInstantSendLockCount() const; @@ -160,13 +159,13 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent }; Counts GetCounts() const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks, !cs_nonLocked); - void CacheBlockHeight(const CBlockIndex* const block_index) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + void CacheBlockHeight(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void CacheDisconnectBlock(const CBlockIndex* pindexDisconnected) EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - std::optional GetCachedHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + std::optional GetCachedHeight(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); void CacheTipHeight(const CBlockIndex* const tip) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - int GetTipHeight() const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); + int GetTipHeight() const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); - bool IsInstantSendEnabled() const override; + bool IsInstantSendEnabled() const; /** * If true, MN should sign all transactions, if false, MN should not sign * transactions in mempool, but should sign txes included in a block. This diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index f22a74d0f935..1de7974fb101 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -4,17 +4,17 @@ #include +#include #include +#include +#include +#include #include #include +#include #include #include #include - -#include -#include -#include -#include #include #include @@ -31,7 +31,7 @@ using node::GetTransaction; namespace instantsend { InstantSendSigner::InstantSendSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks, - InstantSendSignerParent& isman, llmq::CSigningManager& sigman, + llmq::CInstantSendManager& isman, llmq::CSigningManager& sigman, llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman, CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : m_chainstate{chainstate}, diff --git a/src/instantsend/signing.h b/src/instantsend/signing.h index 4e3fc4437001..45e6174dcd9f 100644 --- a/src/instantsend/signing.h +++ b/src/instantsend/signing.h @@ -6,6 +6,7 @@ #define BITCOIN_INSTANTSEND_SIGNING_H #include +#include #include #include @@ -29,26 +30,13 @@ class CQuorumManager; } // namespace llmq namespace instantsend { -class InstantSendSignerParent -{ -public: - virtual ~InstantSendSignerParent() = default; - - virtual bool IsInstantSendEnabled() const = 0; - virtual bool IsLocked(const uint256& txHash) const = 0; - virtual InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const = 0; - virtual void TryEmplacePendingLock(const uint256& hash, const NodeId id, const InstantSendLockPtr& islock) = 0; - virtual std::optional GetCachedHeight(const uint256& hash) const = 0; - virtual int GetTipHeight() const = 0; - virtual void CacheBlockHeight(const CBlockIndex* const block_index) const = 0; -}; class InstantSendSigner final : public llmq::CRecoveredSigsListener { private: CChainState& m_chainstate; const chainlock::Chainlocks& m_chainlocks; - InstantSendSignerParent& m_isman; + llmq::CInstantSendManager& m_isman; llmq::CSigningManager& m_sigman; llmq::CSigSharesManager& m_shareman; llmq::CQuorumManager& m_qman; @@ -80,7 +68,7 @@ class InstantSendSigner final : public llmq::CRecoveredSigsListener InstantSendSigner(const InstantSendSigner&) = delete; InstantSendSigner& operator=(const InstantSendSigner&) = delete; explicit InstantSendSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks, - InstantSendSignerParent& isman, llmq::CSigningManager& sigman, + llmq::CInstantSendManager& isman, llmq::CSigningManager& sigman, llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman, CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync); ~InstantSendSigner(); diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 93df62fb1a91..064d8ea504c3 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -42,7 +42,7 @@ "evo/specialtxman -> validation -> evo/specialtxman", "governance/classes -> governance/object -> governance/governance -> governance/classes", "governance/governance -> governance/signing -> governance/object -> governance/governance", - "instantsend/instantsend -> instantsend/signing -> validation -> txmempool -> instantsend/instantsend", + "instantsend/instantsend -> node/blockstorage -> validation -> txmempool -> instantsend/instantsend", "llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> llmq/blockprocessor", "llmq/commitment -> llmq/utils -> llmq/snapshot -> llmq/commitment", "llmq/dkgsessionhandler -> net_processing -> llmq/dkgsessionmgr -> llmq/dkgsessionhandler", From 2f359db23daf3cd7d4596b3eb2dbf5327213aa1e Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 22:40:03 +0700 Subject: [PATCH 12/42] refactor: move usages sigman directly to NetInstantSend --- src/instantsend/instantsend.cpp | 9 --------- src/instantsend/net_instantsend.cpp | 10 +++++++++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 15c2bcaf27af..1a5d432f6674 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -493,15 +493,6 @@ Uint256HashMap CInstantSendManager::RemoveConfi int nUntilHeight = pindex->nHeight; auto removeISLocks = db.RemoveConfirmedInstantSendLocks(nUntilHeight); - for (const auto& [islockHash, islock] : removeISLocks) { - LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", - __func__, islock->txid.ToString(), islockHash.ToString()); - - // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered - // fully confirmed now - sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, islock->GetRequestId()); - } - db.RemoveArchivedInstantSendLocks(nUntilHeight - 100); // Find all previously unlocked TXs that got locked by this fully confirmed (ChainLock) block and remove them diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 11fb7d874622..21072abc96d4 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -642,7 +642,15 @@ void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex) } auto removeISLocks = m_is_manager.RemoveConfirmedInstantSendLocks(pindex); - for (const auto& [_, islock] : removeISLocks) { + for (const auto& [islockHash, islock] : removeISLocks) { + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: removed islock as it got fully confirmed\n", + __func__, islock->txid.ToString(), islockHash.ToString()); + + // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered + // fully confirmed now + m_is_manager.Sigman().TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, + islock->GetRequestId()); + // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts // from now on. All inputs are spent now and can't be spend in any other TX. TruncateRecoveredSigsForInputs(*islock); From 9acaf7f2864f7f38f99b083fe6226cc619d5ceae Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Feb 2026 23:04:54 +0700 Subject: [PATCH 13/42] refactor: drop dependency of CInstantSendManager on llmq::CSigningManager --- src/init.cpp | 2 +- src/instantsend/instantsend.cpp | 6 ++---- src/instantsend/instantsend.h | 8 ++------ src/instantsend/net_instantsend.cpp | 13 ++++++------- src/instantsend/net_instantsend.h | 7 +++++-- src/llmq/context.cpp | 2 +- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index e824ab533813..840c55649991 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2209,7 +2209,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 7d: Setup other Dash services - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->sigman, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman, node.active_ctx ? node.active_ctx->shareman.get() : nullptr, *node.sporkman)); if (node.active_ctx) { diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 1a5d432f6674..75b277cd6f41 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -16,12 +16,10 @@ using node::fImporting; using node::fReindex; namespace llmq { -CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSigningManager& _sigman, - CSporkManager& sporkman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params) : +CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSporkManager& sporkman, + const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : db{db_params}, m_chainlocks{chainlocks}, - sigman{_sigman}, spork_manager{sporkman}, m_mn_sync{mn_sync} { diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 067f125585fe..c77e3166743e 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -19,8 +20,6 @@ #include #include -#include - class CBlockIndex; class CDataStream; class CMasternodeSync; @@ -54,7 +53,6 @@ struct PendingState { } // namespace instantsend namespace llmq { -class CSigningManager; class CInstantSendManager { @@ -62,7 +60,6 @@ class CInstantSendManager instantsend::CInstantSendDb db; const chainlock::Chainlocks& m_chainlocks; - CSigningManager& sigman; CSporkManager& spork_manager; const CMasternodeSync& m_mn_sync; @@ -100,7 +97,7 @@ class CInstantSendManager CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; - explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSigningManager& _sigman, CSporkManager& sporkman, + explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSporkManager& sporkman, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); ~CInstantSendManager(); @@ -127,7 +124,6 @@ class CInstantSendManager EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); [[nodiscard]] std::vector PrepareTxToRetry() EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - CSigningManager& Sigman() { return sigman; } const chainlock::Chainlocks& Chainlocks() { return m_chainlocks; } void RemoveBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 21072abc96d4..8f82dd9e37f3 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -126,7 +126,7 @@ std::unique_ptr NetInstantSend::BuildVeri auto id = islock->GetRequestId(); // no need to verify an ISLOCK if we already have verified the recovered sig that belongs to it - if (m_is_manager.Sigman().HasRecoveredSig(llmq_params.type, id, islock->txid)) { + if (m_sigman.HasRecoveredSig(llmq_params.type, id, islock->txid)) { data->alreadyVerified++; continue; } @@ -160,7 +160,7 @@ std::unique_ptr NetInstantSend::BuildVeri // We can reconstruct the CRecoveredSig objects from the islock and pass it to the signing manager, which // avoids unnecessary double-verification of the signature. We however only do this when verification here // turns out to be good (which is checked further down) - if (!m_is_manager.Sigman().HasRecoveredSigForId(llmq_params.type, id)) { + if (!m_sigman.HasRecoveredSigForId(llmq_params.type, id)) { data->recSigs.try_emplace(hash, llmq::CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid, islock->sig)); } @@ -206,12 +206,12 @@ Uint256HashSet NetInstantSend::ApplyVerificationResults( auto it = data.recSigs.find(hash); if (it != data.recSigs.end()) { auto recSig = std::make_shared(std::move(it->second)); - if (!m_is_manager.Sigman().HasRecoveredSigForId(llmq_params.type, recSig->getId())) { + if (!m_sigman.HasRecoveredSigForId(llmq_params.type, recSig->getId())) { LogPrint(BCLog::INSTANTSEND, /* Continued */ "NetInstantSend::%s -- txid=%s, islock=%s: " "passing reconstructed recSig to signing mgr, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), nodeId); - m_is_manager.Sigman().PushReconstructedRecoveredSig(recSig); + m_sigman.PushReconstructedRecoveredSig(recSig); } } } @@ -631,7 +631,7 @@ void NetInstantSend::TruncateRecoveredSigsForInputs(const instantsend::InstantSe m_signer->ClearInputsFromQueue(ids); } for (const auto& id : ids) { - m_is_manager.Sigman().TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id); + m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, id); } } @@ -648,8 +648,7 @@ void NetInstantSend::HandleFullyConfirmedBlock(const CBlockIndex* pindex) // And we don't need the recovered sig for the ISLOCK anymore, as the block in which it got mined is considered // fully confirmed now - m_is_manager.Sigman().TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, - islock->GetRequestId()); + m_sigman.TruncateRecoveredSig(Params().GetConsensus().llmqTypeDIP0024InstantSend, islock->GetRequestId()); // No need to keep recovered sigs for fully confirmed IS locks, as there is no chance for conflicts // from now on. All inputs are spent now and can't be spend in any other TX. diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index 338ca7f12ec4..34b9e8f7ffe7 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -28,6 +28,7 @@ struct PendingISLockEntry; class InstantSendSigner; } // namespace instantsend namespace llmq { +class CSigningManager; class CInstantSendManager; class CQuorumManager; } // namespace llmq @@ -36,11 +37,12 @@ class NetInstantSend final : public NetHandler, public CValidationInterface { public: NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, - instantsend::InstantSendSigner* signer, llmq::CQuorumManager& qman, CChainState& chainstate, - CTxMemPool& mempool, const CMasternodeSync& mn_sync) : + instantsend::InstantSendSigner* signer, llmq::CSigningManager& sigman, llmq::CQuorumManager& qman, + CChainState& chainstate, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : NetHandler(peer_manager), m_is_manager{is_manager}, m_signer{signer}, + m_sigman{sigman}, m_qman(qman), m_chainstate{chainstate}, m_mempool{mempool}, @@ -100,6 +102,7 @@ class NetInstantSend final : public NetHandler, public CValidationInterface llmq::CInstantSendManager& m_is_manager; instantsend::InstantSendSigner* m_signer; // non-null only for masternode + llmq::CSigningManager& m_sigman; llmq::CQuorumManager& m_qman; CChainState& m_chainstate; CTxMemPool& m_mempool; diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index b5ea2916a71a..adcf9b2dbfda 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -23,7 +23,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(chainlocks, *sigman, sporkman, mn_sync, db_params)} + isman{std::make_unique(chainlocks, sporkman, mn_sync, db_params)} { // Have to start it early to let VerifyDB check ChainLock signatures in coinbase bls_worker->Start(worker_count); From a6551e1005fc5c552e64e728124aecc68e2ac384 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 25 Feb 2026 18:01:47 +0700 Subject: [PATCH 14/42] refactor: drop dependency of CInstantSendManager on chainlocks --- src/init.cpp | 2 +- src/instantsend/instantsend.cpp | 6 ++---- src/instantsend/instantsend.h | 11 ++--------- src/instantsend/net_instantsend.cpp | 8 ++++---- src/instantsend/net_instantsend.h | 9 ++++++++- src/llmq/context.cpp | 4 ++-- src/llmq/context.h | 6 +----- src/node/chainstate.cpp | 2 +- 8 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 840c55649991..3f16e533be7f 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2209,7 +2209,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 7d: Setup other Dash services - node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->sigman, *node.llmq_ctx->qman, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, node.active_ctx ? node.active_ctx->is_signer.get() : nullptr, *node.llmq_ctx->sigman, *node.llmq_ctx->qman, *node.chainlocks, chainman.ActiveChainstate(), *node.mempool, *node.mn_sync)); node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->sigman, node.active_ctx ? node.active_ctx->shareman.get() : nullptr, *node.sporkman)); if (node.active_ctx) { diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 75b277cd6f41..ed0133047f08 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -4,7 +4,6 @@ #include -#include #include #include #include @@ -16,10 +15,9 @@ using node::fImporting; using node::fReindex; namespace llmq { -CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSporkManager& sporkman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : +CInstantSendManager::CInstantSendManager(CSporkManager& sporkman, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params) : db{db_params}, - m_chainlocks{chainlocks}, spork_manager{sporkman}, m_mn_sync{mn_sync} { diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index c77e3166743e..ac56fcf8e667 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -31,10 +31,6 @@ namespace util { struct DbWrapperParams; } // namespace util -namespace chainlock { -class Chainlocks; -} - namespace instantsend { struct PendingISLockFromPeer { @@ -58,8 +54,6 @@ class CInstantSendManager { private: instantsend::CInstantSendDb db; - - const chainlock::Chainlocks& m_chainlocks; CSporkManager& spork_manager; const CMasternodeSync& m_mn_sync; @@ -97,8 +91,8 @@ class CInstantSendManager CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; - explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CSporkManager& sporkman, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); + explicit CInstantSendManager(CSporkManager& sporkman, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params); ~CInstantSendManager(); void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) @@ -124,7 +118,6 @@ class CInstantSendManager EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); [[nodiscard]] std::vector PrepareTxToRetry() EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); - const chainlock::Chainlocks& Chainlocks() { return m_chainlocks; } void RemoveBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex); void WriteBlockISLocks(const std::shared_ptr& pblock, const CBlockIndex* pindex); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 8f82dd9e37f3..628db7a61b3d 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -376,7 +376,7 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co } // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes, // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain - if (minedHeight.has_value() && m_is_manager.Chainlocks().HasChainLock(*minedHeight, hashBlock)) { + if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) { LogPrint(BCLog::INSTANTSEND, /* Continued */ "NetSigning::%s -- txlock=%s, islock=%s: dropping islock as it already got a " "ChainLock in block %s, peer=%d\n", @@ -501,7 +501,7 @@ void NetInstantSend::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockI { bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height; - if (m_is_manager.Chainlocks().IsEnabled() && fDIP0008Active) { + if (m_chainlocks.IsEnabled() && fDIP0008Active) { // Nothing to do here. We should keep all islocks and let chainlocks handle them. return; } @@ -529,7 +529,7 @@ void NetInstantSend::BlockConnected(const std::shared_ptr& pblock, m_is_manager.CacheTipHeight(pindex); if (m_mn_sync.IsBlockchainSynced()) { - const bool has_chainlock = m_is_manager.Chainlocks().HasChainLock(pindex->nHeight, pindex->GetBlockHash()); + const bool has_chainlock = m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash()); for (const auto& tx : pblock->vtx) { if (tx->IsCoinBase() || tx->vin.empty()) { // coinbase and TXs with no inputs can't be locked @@ -571,7 +571,7 @@ void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const inst bool hasChainLockedConflict = false; for (const auto& p : conflicts) { const auto* pindex = p.first; - if (m_is_manager.Chainlocks().HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { + if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { hasChainLockedConflict = true; break; } diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h index 34b9e8f7ffe7..4d0b64609b5c 100644 --- a/src/instantsend/net_instantsend.h +++ b/src/instantsend/net_instantsend.h @@ -18,6 +18,10 @@ namespace Consensus { struct LLMQParams; } // namespace Consensus +namespace chainlock { +class Chainlocks; +} // namespace chainlock + class CMasternodeSync; class CTxMemPool; @@ -38,12 +42,14 @@ class NetInstantSend final : public NetHandler, public CValidationInterface public: NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, instantsend::InstantSendSigner* signer, llmq::CSigningManager& sigman, llmq::CQuorumManager& qman, - CChainState& chainstate, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : + const chainlock::Chainlocks& chainlocks, CChainState& chainstate, CTxMemPool& mempool, + const CMasternodeSync& mn_sync) : NetHandler(peer_manager), m_is_manager{is_manager}, m_signer{signer}, m_sigman{sigman}, m_qman(qman), + m_chainlocks{chainlocks}, m_chainstate{chainstate}, m_mempool{mempool}, m_mn_sync{mn_sync} @@ -104,6 +110,7 @@ class NetInstantSend final : public NetHandler, public CValidationInterface instantsend::InstantSendSigner* m_signer; // non-null only for masternode llmq::CSigningManager& m_sigman; llmq::CQuorumManager& m_qman; + const chainlock::Chainlocks& m_chainlocks; CChainState& m_chainstate; CTxMemPool& m_mempool; const CMasternodeSync& m_mn_sync; diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index adcf9b2dbfda..3e24e34d01fa 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -13,7 +13,7 @@ #include LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, - chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, const CMasternodeSync& mn_sync, + ChainstateManager& chainman, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params, int8_t bls_threads, int16_t worker_count, int64_t max_recsigs_age) : bls_worker{std::make_shared()}, @@ -23,7 +23,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(chainlocks, sporkman, mn_sync, db_params)} + isman{std::make_unique(sporkman, mn_sync, 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 1eb314d7ad3d..4f3e5e984e3f 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -17,10 +17,6 @@ class CMasternodeSync; class CSporkManager; class PeerManager; -namespace chainlock { -class Chainlocks; -} - namespace llmq { class CInstantSendManager; class CQuorumBlockProcessor; @@ -38,7 +34,7 @@ struct LLMQContext { LLMQContext(const LLMQContext&) = delete; LLMQContext& operator=(const LLMQContext&) = delete; explicit LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, - chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, const CMasternodeSync& mn_sync, + ChainstateManager& chainman, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params, int8_t bls_threads, int16_t worker_count, int64_t max_recsigs_age); ~LLMQContext(); diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index d1b45ae14956..98d4dc21d199 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, chainlocks, chainman, mn_sync, + llmq_ctx = std::make_unique(*dmnman, evodb, sporkman, chainman, mn_sync, 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 62a185047c599e07e3ac2c9f8ee6e48dc0b9658f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 25 Feb 2026 23:23:45 +0700 Subject: [PATCH 15/42] refactor: removed unused includes --- src/instantsend/instantsend.h | 3 +-- src/instantsend/signing.h | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index ac56fcf8e667..0699104875a9 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -17,7 +16,6 @@ #include #include -#include #include class CBlockIndex; @@ -30,6 +28,7 @@ struct LLMQParams; namespace util { struct DbWrapperParams; } // namespace util +typedef std::shared_ptr CTransactionRef; namespace instantsend { diff --git a/src/instantsend/signing.h b/src/instantsend/signing.h index 45e6174dcd9f..f7df067e4b9d 100644 --- a/src/instantsend/signing.h +++ b/src/instantsend/signing.h @@ -6,10 +6,9 @@ #define BITCOIN_INSTANTSEND_SIGNING_H #include -#include #include - -#include +#include +#include class CMasternodeSync; class CSporkManager; From 663a7bea39f30c6c2bb7e8431b39f35707874dcc Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Fri, 27 Feb 2026 16:01:50 +0300 Subject: [PATCH 16/42] refactor: review fixes for CInstantSendManager decoupling - Rename IsKnownTx to HasTxForLock for clarity (returns true when the lock's transaction is known, not pending without a TX) - Fix unconditional LookupBlockIndex in CheckCanLock: only take cs_main on cache miss instead of every call - Fix log prefix typos: NetSigning -> NetInstantSend in ProcessInstantSendLock - Remove redundant LookupBlockIndex fallback in ProcessInstantSendLock that duplicated the static GetBlockHeight helper - Simplify AttachISLockToTx: use for-loop with early-continue, remove unnecessary variable - Remove double blank line Co-Authored-By: Claude Opus 4.6 --- src/active/context.h | 1 + src/instantsend/instantsend.cpp | 27 ++++++++++++--------------- src/instantsend/instantsend.h | 2 +- src/instantsend/net_instantsend.cpp | 23 +++++++---------------- src/instantsend/signing.cpp | 23 ++++++++++++----------- 5 files changed, 33 insertions(+), 43 deletions(-) diff --git a/src/active/context.h b/src/active/context.h index 74562b8295f5..e0d774f6e923 100644 --- a/src/active/context.h +++ b/src/active/context.h @@ -100,6 +100,7 @@ struct ActiveContext final : public CValidationInterface { public: const std::unique_ptr is_signer; +public: /** Owned by PeerManager, use GetCJServer() */ CCoinJoinServer* m_cj_server{nullptr}; }; diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index ed0133047f08..26d0e3f90559 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -145,22 +145,19 @@ void CInstantSendManager::RemoveBlockISLocks(const std::shared_ptr instantsend::InstantSendLockPtr CInstantSendManager::AttachISLockToTx(const CTransactionRef& tx) { - instantsend::InstantSendLockPtr ret_islock{nullptr}; LOCK(cs_pendingLocks); - auto it = pendingNoTxInstantSendLocks.begin(); - while (it != pendingNoTxInstantSendLocks.end()) { - if (it->second.islock->txid == tx->GetHash()) { - // we received an islock earlier, let's put it back into pending and verify/lock - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, - tx->GetHash().ToString(), it->first.ToString()); - ret_islock = it->second.islock; - pendingInstantSendLocks.try_emplace(it->first, it->second); - pendingNoTxInstantSendLocks.erase(it); - return ret_islock; - } - ++it; + for (auto it = pendingNoTxInstantSendLocks.begin(); it != pendingNoTxInstantSendLocks.end(); ++it) { + if (it->second.islock->txid != tx->GetHash()) continue; + + // we received an islock earlier, let's put it back into pending and verify/lock + LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s\n", __func__, + tx->GetHash().ToString(), it->first.ToString()); + auto islock = it->second.islock; + pendingInstantSendLocks.try_emplace(it->first, it->second); + pendingNoTxInstantSendLocks.erase(it); + return islock; } - return ret_islock; // not found, nullptr + return nullptr; } void CInstantSendManager::AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) @@ -290,7 +287,7 @@ std::unordered_map> CInstant return conflicts; } -bool CInstantSendManager::IsKnownTx(const uint256& islockHash) const +bool CInstantSendManager::HasTxForLock(const uint256& islockHash) const { LOCK(cs_pendingLocks); return pendingNoTxInstantSendLocks.find(islockHash) == pendingNoTxInstantSendLocks.end(); diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 0699104875a9..a9667ab4384c 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -104,7 +104,7 @@ class CInstantSendManager std::unordered_map> RetrieveISConflicts( const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks); - bool IsKnownTx(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + bool HasTxForLock(const uint256& islockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); bool IsLocked(const uint256& txHash) const; bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); diff --git a/src/instantsend/net_instantsend.cpp b/src/instantsend/net_instantsend.cpp index 628db7a61b3d..2520eb615364 100644 --- a/src/instantsend/net_instantsend.cpp +++ b/src/instantsend/net_instantsend.cpp @@ -352,7 +352,7 @@ void NetInstantSend::ProcessPendingISLocks(std::vectortxid.ToString(), hash.ToString(), from); if (m_signer) { @@ -364,21 +364,13 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co auto tx = GetTransaction(nullptr, &m_mempool, islock->txid, Params().GetConsensus(), hashBlock); const bool found_transaction{tx != nullptr}; // we ignore failure here as we must be able to propagate the lock even if we don't have the TX locally - std::optional minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock); + const auto minedHeight = GetBlockHeight(m_is_manager, m_chainstate, hashBlock); if (found_transaction) { - if (!minedHeight.has_value()) { - const CBlockIndex* pindexMined = WITH_LOCK(::cs_main, - return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); - if (pindexMined != nullptr) { - m_is_manager.CacheBlockHeight(pindexMined); - minedHeight = pindexMined->nHeight; - } - } // Let's see if the TX that was locked by this islock is already mined in a ChainLocked block. If yes, // we can simply ignore the islock, as the ChainLock implies locking of all TXs in that chain if (minedHeight.has_value() && m_chainlocks.HasChainLock(*minedHeight, hashBlock)) { LogPrint(BCLog::INSTANTSEND, /* Continued */ - "NetSigning::%s -- txlock=%s, islock=%s: dropping islock as it already got a " + "NetInstantSend::%s -- txlock=%s, islock=%s: dropping islock as it already got a " "ChainLock in block %s, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), hashBlock.ToString(), from); return; @@ -388,7 +380,6 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co m_is_manager.AddPendingISLock(hash, islock, from); } - // This will also add children TXs to pendingRetryTxs m_is_manager.RemoveNonLockedTx(islock->txid, true); // We don't need the recovered sigs for the inputs anymore. This prevents unnecessary propagation of these sigs. @@ -398,8 +389,8 @@ void NetInstantSend::ProcessInstantSendLock(NodeId from, const uint256& hash, co if (found_transaction) { RemoveMempoolConflictsForLock(hash, *islock); - LogPrint(BCLog::INSTANTSEND, "NetSigning::%s -- notify about lock %s for tx %s\n", __func__, hash.ToString(), - tx->GetHash().ToString()); + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- notify about lock %s for tx %s\n", __func__, + hash.ToString(), tx->GetHash().ToString()); GetMainSignals().NotifyTransactionLock(tx, islock); // bump m_mempool counter to make sure newly locked txes are picked up by getblocktemplate m_mempool.AddTransactionsUpdated(1); @@ -588,7 +579,7 @@ void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const inst return; } - bool isLockedTxKnown = m_is_manager.IsKnownTx(islockHash); + bool hasTxForLock = m_is_manager.HasTxForLock(islockHash); bool activateBestChain = false; for (const auto& p : conflicts) { @@ -605,7 +596,7 @@ void NetInstantSend::ResolveBlockConflicts(const uint256& islockHash, const inst // This should not have happened and we are in a state were it's not safe to continue anymore assert(false); } - if (isLockedTxKnown) { + if (hasTxForLock) { activateBestChain = true; } else { LogPrintf("NetInstantSend::%s -- resetting block %s\n", __func__, pindex2->GetBlockHash().ToString()); diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index 1de7974fb101..d531ee8ae1fb 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -209,19 +209,20 @@ bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, int blockHeight{0}; if (!hashBlock.IsNull()) { - auto ret = m_isman.GetCachedHeight(hashBlock); - if (ret) blockHeight = *ret; - - const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); - if (pindex == nullptr) { - if (printDebug) { - LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n", - __func__, txHash.ToString(), outpoint.hash.ToString()); + if (auto ret = m_isman.GetCachedHeight(hashBlock)) { + blockHeight = *ret; + } else { + const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(hashBlock)); + if (pindex == nullptr) { + if (printDebug) { + LogPrint(BCLog::INSTANTSEND, "%s -- txid=%s: failed to determine mined height for parent TX %s\n", + __func__, txHash.ToString(), outpoint.hash.ToString()); + } + return false; } - return false; + m_isman.CacheBlockHeight(pindex); + blockHeight = pindex->nHeight; } - m_isman.CacheBlockHeight(pindex); - blockHeight = pindex->nHeight; } const int tipHeight = m_isman.GetTipHeight(); From 7e5db6ef2be690624d79f08617486f513551b306 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 15:01:05 +0700 Subject: [PATCH 17/42] 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 efc4ae7280dc44f7558c3bbf0c216ad1d4c45ce5 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 15:37:04 +0700 Subject: [PATCH 18/42] 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 b00802403877fac2efa479498887a46ee69ae00e Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 16:33:00 +0700 Subject: [PATCH 19/42] 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 55dbf049fa97f559530bcba97b0ec9191c11036a Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 16:40:28 +0700 Subject: [PATCH 20/42] 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 9f879c71f7341270f9097f390e84de7b6ad5c34e Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 11 Mar 2026 17:38:46 +0700 Subject: [PATCH 21/42] 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 bf2f188fcbe50481c530f2da76f81f04c451cb31 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 12 Mar 2026 19:47:05 +0700 Subject: [PATCH 22/42] 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 3f16e533be7f..6b55dacdad78 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1988,7 +1988,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 23/42] 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 // From 1d79045b3b86fa950ef85653d10294536538500c Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 13 Mar 2026 13:11:41 +0700 Subject: [PATCH 24/42] refactor: move VerifyChainLock to clsig.h to avoid building up handler.cpp for chain-state --- src/chainlock/clsig.cpp | 12 ++++++++++++ src/chainlock/clsig.h | 17 +++++++++++++++-- src/chainlock/handler.cpp | 20 +++++--------------- src/chainlock/handler.h | 9 --------- 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/src/chainlock/clsig.cpp b/src/chainlock/clsig.cpp index 573331bba610..16438a0c76c8 100644 --- a/src/chainlock/clsig.cpp +++ b/src/chainlock/clsig.cpp @@ -4,6 +4,8 @@ #include +#include +#include #include #include @@ -30,4 +32,14 @@ uint256 GenSigRequestId(const int32_t nHeight) { return ::SerializeHash(std::make_pair(CLSIG_REQUESTID_PREFIX, nHeight)); } + +llmq::VerifyRecSigStatus VerifyChainLock(const Consensus::Params& params, const CChain& chain, + const llmq::CQuorumManager& qman, const chainlock::ChainLockSig& clsig) +{ + const auto llmqType = params.llmqTypeChainLocks; + const uint256 request_id = GenSigRequestId(clsig.getHeight()); + + return llmq::VerifyRecoveredSig(llmqType, chain, qman, clsig.getHeight(), request_id, clsig.getBlockHash(), + clsig.getSig()); +} } // namespace chainlock diff --git a/src/chainlock/clsig.h b/src/chainlock/clsig.h index 3800f2f813cb..62f86db68e67 100644 --- a/src/chainlock/clsig.h +++ b/src/chainlock/clsig.h @@ -5,13 +5,23 @@ #ifndef BITCOIN_CHAINLOCK_CLSIG_H #define BITCOIN_CHAINLOCK_CLSIG_H +#include #include #include -#include - #include +class CChain; + +namespace Consensus { +struct Params; +} // namespace Consensus + +namespace llmq { +class CQuorumManager; +enum class VerifyRecSigStatus : uint8_t; +} // namespace llmq + namespace chainlock { struct ChainLockSig { private: @@ -39,6 +49,9 @@ struct ChainLockSig { //! Generate clsig request ID with block height uint256 GenSigRequestId(const int32_t nHeight); + +llmq::VerifyRecSigStatus VerifyChainLock(const Consensus::Params& params, const CChain& chain, + const llmq::CQuorumManager& qman, const ChainLockSig& clsig); } // namespace chainlock #endif // BITCOIN_CHAINLOCK_CLSIG_H diff --git a/src/chainlock/handler.cpp b/src/chainlock/handler.cpp index ddb7bf1ff59a..d1075a8fc7f6 100644 --- a/src/chainlock/handler.cpp +++ b/src/chainlock/handler.cpp @@ -4,19 +4,18 @@ #include +#include #include +#include +#include #include #include #include -#include -#include - -#include -#include -#include #include #include +#include #include +#include #include #include #include @@ -324,13 +323,4 @@ void ChainlockHandler::Cleanup() } } -llmq::VerifyRecSigStatus VerifyChainLock(const Consensus::Params& params, const CChain& chain, - const llmq::CQuorumManager& qman, const chainlock::ChainLockSig& clsig) -{ - const auto llmqType = params.llmqTypeChainLocks; - const uint256 request_id = chainlock::GenSigRequestId(clsig.getHeight()); - - return llmq::VerifyRecoveredSig(llmqType, chain, qman, clsig.getHeight(), request_id, clsig.getBlockHash(), - clsig.getSig()); -} } // namespace chainlock diff --git a/src/chainlock/handler.h b/src/chainlock/handler.h index e024ca539876..140325c007c6 100644 --- a/src/chainlock/handler.h +++ b/src/chainlock/handler.h @@ -24,20 +24,14 @@ class CBlock; class CBlockIndex; -class CChain; class CMasternodeSync; class ChainstateManager; class CScheduler; class CTxMemPool; struct MessageProcessingResult; -namespace Consensus { -struct Params; -} // namespace Consensus - namespace llmq { class CQuorumManager; -enum class VerifyRecSigStatus : uint8_t; } // namespace llmq namespace chainlock { @@ -107,9 +101,6 @@ class ChainlockHandler final : public CValidationInterface private: void Cleanup() EXCLUSIVE_LOCKS_REQUIRED(!cs); }; - -llmq::VerifyRecSigStatus VerifyChainLock(const Consensus::Params& params, const CChain& chain, - const llmq::CQuorumManager& qman, const chainlock::ChainLockSig& clsig); } // namespace chainlock #endif // BITCOIN_CHAINLOCK_HANDLER_H From d71fa8bb0ea85b0a2fc3ad6238dbcd33611f7433 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 13 Mar 2026 14:16:18 +0700 Subject: [PATCH 25/42] refactor: drop dependency of governance/object on core_write (ScriptToAsmStr) --- src/governance/object.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/governance/object.cpp b/src/governance/object.cpp index d05e5b021e99..86431439196f 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -12,11 +12,11 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -491,12 +491,12 @@ bool CGovernanceObject::IsCollateralValid(const ChainstateManager& chainman, std CAmount nMinFee = GetMinCollateralFee(); LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txCollateral->vout.size() = %s, findScript = %s, nMinFee = %lld\n", - txCollateral->vout.size(), ScriptToAsmStr(findScript, false), nMinFee); + txCollateral->vout.size(), HexStr(findScript), nMinFee); bool foundOpReturn = false; for (const auto& output : txCollateral->vout) { LogPrint(BCLog::GOBJECT, "CGovernanceObject::IsCollateralValid -- txout = %s, output.nValue = %lld, output.scriptPubKey = %s\n", - output.ToString(), output.nValue, ScriptToAsmStr(output.scriptPubKey, false)); + output.ToString(), output.nValue, HexStr(output.scriptPubKey)); if (!output.scriptPubKey.IsPayToPublicKeyHash() && !output.scriptPubKey.IsUnspendable()) { strError = strprintf("Invalid Script %s", txCollateral->ToString()); LogPrintf("CGovernanceObject::IsCollateralValid -- %s\n", strError); From a1c143274feabe99b80fe203543a7c58558deeed Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 13 Mar 2026 13:47:43 +0700 Subject: [PATCH 26/42] refactor: drop pfrom from CGovernanceManager::ProcessVote --- src/governance/governance.cpp | 4 ++-- src/governance/governance.h | 2 +- src/governance/net_governance.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/governance/governance.cpp b/src/governance/governance.cpp index 530524188f1c..83b0459e4363 100644 --- a/src/governance/governance.cpp +++ b/src/governance/governance.cpp @@ -753,14 +753,14 @@ bool CGovernanceManager::ProcessVoteAndRelay(const CGovernanceVote& vote, CGover AssertLockNotHeld(cs_store); AssertLockNotHeld(cs_relay); uint256 hashToRequest; // Ignored for local votes (no peer to request from) - bool fOK = ProcessVote(/*pfrom=*/nullptr, vote, exception, hashToRequest); + bool fOK = ProcessVote(vote, exception, hashToRequest); if (fOK) { RelayVote(vote); } return fOK; } -bool CGovernanceManager::ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception, +bool CGovernanceManager::ProcessVote(const CGovernanceVote& vote, CGovernanceException& exception, uint256& hashToRequest) { AssertLockNotHeld(cs_store); diff --git a/src/governance/governance.h b/src/governance/governance.h index 58addde58e7b..223a3d0db5d0 100644 --- a/src/governance/governance.h +++ b/src/governance/governance.h @@ -383,7 +383,7 @@ class CGovernanceManager : public GovernanceStore, public GovernanceSignerParent CDeterministicMNManager& GetMNManager(); /** Process a governance vote. Returns true on success. * If the vote is for an unknown object (orphan), hashToRequest is set to the object hash. */ - bool ProcessVote(CNode* pfrom, const CGovernanceVote& vote, CGovernanceException& exception, uint256& hashToRequest) + bool ProcessVote(const CGovernanceVote& vote, CGovernanceException& exception, uint256& hashToRequest) EXCLUSIVE_LOCKS_REQUIRED(!cs_store); diff --git a/src/governance/net_governance.cpp b/src/governance/net_governance.cpp index 7849ff91544e..c9314c9cc1d5 100644 --- a/src/governance/net_governance.cpp +++ b/src/governance/net_governance.cpp @@ -169,7 +169,7 @@ void NetGovernance::ProcessMessage(CNode& peer, const std::string& msg_type, CDa CGovernanceException exception; uint256 hashToRequest; - if (m_gov_manager.ProcessVote(&peer, vote, exception, hashToRequest)) { + if (m_gov_manager.ProcessVote(vote, exception, hashToRequest)) { LogPrint(BCLog::GOBJECT, "MNGOVERNANCEOBJECTVOTE -- %s new\n", strHash); m_node_sync.BumpAssetLastTime("MNGOVERNANCEOBJECTVOTE"); From 7862a50972399d6f192108d6a5fb499fd9c8e938 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 13 Mar 2026 22:01:15 +0700 Subject: [PATCH 27/42] fix: add guards if CTxMempool is nullptr in chainstate --- src/node/chainstate.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index f9919d81ffdc..c97cf9c61f2d 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -231,7 +231,9 @@ void DashChainstateSetup(ChainstateManager& chainman, 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()); + if (mempool) { + 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, sporkman, chainlocks, @@ -246,7 +248,9 @@ void DashChainstateSetupClose(std::unique_ptr& chain_helper, { chain_helper.reset(); llmq_ctx.reset(); - mempool->DisconnectManagers(); + if (mempool) { + mempool->DisconnectManagers(); + } dmnman.reset(); } From 994631762d95baa7866e34f1d1b235a82b5c9222 Mon Sep 17 00:00:00 2001 From: MarcoFalke Date: Thu, 3 Mar 2022 19:31:14 +0000 Subject: [PATCH 28/42] Merge bitcoin/bitcoin#24304: [kernel 0/n] Introduce `bitcoin-chainstate` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2c03cec2ff8cdbfd5da92bfb507d218e5c6435b0 ci: Build bitcoin-chainstate (Carl Dong) 095aa6ca37bf0bd5c5e221bab779978a99b2a34c build: Add example bitcoin-chainstate executable (Carl Dong) Pull request description: Part of: #24303 This PR introduces an example/demo `bitcoin-chainstate` executable using said library which can print out information about a datadir and take in new blocks on stdin. Please read the commit messages for more details. ----- #### You may ask: WTF?! Why is `index/*.cpp`, etc. being linked in? This PR is meant only to capture the state of dependencies in our consensus engine as of right now. There are many things to decouple from consensus, which will be done in subsequent PRs. Listing the files out right now in `bitcoin_chainstate_SOURCES` is purely to give us a clear picture of the task at hand, it is **not** to say that these dependencies _belongs_ there in any way. ### TODO 1. Clean up `bitcoin-chainstate.cpp` It is quite ugly, with a lot of comments I've left for myself, I should clean it up to the best of my abilities (the ugliness of our init/shutdown might be the upper bound on cleanliness here...) ACKs for top commit: ajtowns: ACK 2c03cec2ff8cdbfd5da92bfb507d218e5c6435b0 ryanofsky: Code review ACK 2c03cec2ff8cdbfd5da92bfb507d218e5c6435b0. Just rebase, comments, formatting change since last review MarcoFalke: re-ACK 2c03cec2ff8cdbfd5da92bfb507d218e5c6435b0 🏔 Tree-SHA512: 86e7fb5718caa577df8abc8288c754f4a590650d974df9d2f6476c87ed25c70f923c4db651c6963f33498fc7a3a31f6692b9a75cbc996bf4888c5dac2f34a13b --- .gitignore | 1 + ci/dash/matrix.sh | 2 +- ...p_env_native_nowallet_libbitcoinkernel.sh} | 4 +- configure.ac | 14 + src/Makefile.am | 99 +++++++ src/bitcoin-chainstate.cpp | 262 ++++++++++++++++++ 6 files changed, 379 insertions(+), 3 deletions(-) rename ci/test/{00_setup_env_native_nowallet.sh => 00_setup_env_native_nowallet_libbitcoinkernel.sh} (81%) create mode 100644 src/bitcoin-chainstate.cpp diff --git a/.gitignore b/.gitignore index 8c13e82b2289..efa7ab526448 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ src/dash-gui src/dash-node src/dash-tx src/dash-util +src/dash-chainstate src/dash-wallet src/test/fuzz/fuzz src/test/test_dash diff --git a/ci/dash/matrix.sh b/ci/dash/matrix.sh index 6e249fc476d1..de01eaf6d6c7 100755 --- a/ci/dash/matrix.sh +++ b/ci/dash/matrix.sh @@ -27,7 +27,7 @@ elif [ "$BUILD_TARGET" = "linux64_fuzz" ]; then elif [ "$BUILD_TARGET" = "linux64_multiprocess" ]; then source ./ci/test/00_setup_env_native_multiprocess.sh elif [ "$BUILD_TARGET" = "linux64_nowallet" ]; then - source ./ci/test/00_setup_env_native_nowallet.sh + source ./ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh elif [ "$BUILD_TARGET" = "linux64_sqlite" ]; then source ./ci/test/00_setup_env_native_sqlite.sh elif [ "$BUILD_TARGET" = "linux64_tsan" ]; then diff --git a/ci/test/00_setup_env_native_nowallet.sh b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh similarity index 81% rename from ci/test/00_setup_env_native_nowallet.sh rename to ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh index 3e083c3d4d62..ef228c677fef 100755 --- a/ci/test/00_setup_env_native_nowallet.sh +++ b/ci/test/00_setup_env_native_nowallet_libbitcoinkernel.sh @@ -6,9 +6,9 @@ export LC_ALL=C.UTF-8 -export CONTAINER_NAME=ci_native_nowallet +export CONTAINER_NAME=ci_native_nowallet_libbitcoinkernel export HOST=x86_64-pc-linux-gnu export PACKAGES="python3-zmq" export DEP_OPTS="NO_WALLET=1 CC=gcc-14 CXX=g++-14" export GOAL="install" -export BITCOIN_CONFIG="--enable-reduce-exports CC=gcc-14 CXX=g++-14" +export BITCOIN_CONFIG="--enable-reduce-exports CC=gcc-14 CXX=g++-14 --enable-experimental-util-chainstate" diff --git a/configure.ac b/configure.ac index 31951e1f7c47..750d511f3c02 100644 --- a/configure.ac +++ b/configure.ac @@ -35,6 +35,7 @@ BITCOIN_TEST_NAME=test_dash BITCOIN_CLI_NAME=dash-cli BITCOIN_TX_NAME=dash-tx BITCOIN_UTIL_NAME=dash-util +BITCOIN_CHAINSTATE_NAME=dash-chainstate BITCOIN_WALLET_TOOL_NAME=dash-wallet dnl Multi Process BITCOIN_MP_NODE_NAME=dash-node @@ -769,6 +770,13 @@ AC_ARG_ENABLE([util-util], [build_bitcoin_util=$enableval], [build_bitcoin_util=$build_bitcoin_utils]) +AC_ARG_ENABLE([experimental-util-chainstate], + [AS_HELP_STRING([--enable-experimental-util-chainstate], + [build experimental bitcoin-chainstate executable (default=no)])], + [build_bitcoin_chainstate=$enableval], + [build_bitcoin_chainstate=no]) + + AC_ARG_WITH([libs], [AS_HELP_STRING([--with-libs], [build libraries (default=yes)])], @@ -1443,6 +1451,7 @@ if test "$enable_fuzz" = "yes"; then build_bitcoin_cli=no build_bitcoin_tx=no build_bitcoin_util=no + build_bitcoin_chainstate=no build_bitcoin_wallet=no build_bitcoind=no build_bitcoin_libs=no @@ -1781,6 +1790,10 @@ AC_MSG_CHECKING([whether to build dash-util]) AM_CONDITIONAL([BUILD_BITCOIN_UTIL], [test $build_bitcoin_util = "yes"]) AC_MSG_RESULT($build_bitcoin_util) +AC_MSG_CHECKING([whether to build experimental dash-chainstate]) +AM_CONDITIONAL([BUILD_BITCOIN_CHAINSTATE], [test $build_bitcoin_chainstate = "yes"]) +AC_MSG_RESULT($build_bitcoin_chainstate) + AC_MSG_CHECKING([whether to build libraries]) AM_CONDITIONAL([BUILD_BITCOIN_LIBS], [test $build_bitcoin_libs = "yes"]) if test "$build_bitcoin_libs" = "yes"; then @@ -1995,6 +2008,7 @@ AC_SUBST(BITCOIN_TEST_NAME) AC_SUBST(BITCOIN_CLI_NAME) AC_SUBST(BITCOIN_TX_NAME) AC_SUBST(BITCOIN_UTIL_NAME) +AC_SUBST(BITCOIN_CHAINSTATE_NAME) AC_SUBST(BITCOIN_WALLET_TOOL_NAME) AC_SUBST(BITCOIN_MP_NODE_NAME) AC_SUBST(BITCOIN_MP_GUI_NAME) diff --git a/src/Makefile.am b/src/Makefile.am index a36f08be79c5..a9a4ebd3fe3b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -148,6 +148,10 @@ if BUILD_BITCOIN_UTIL bin_PROGRAMS += dash-util endif +if BUILD_BITCOIN_CHAINSTATE + bin_PROGRAMS += bitcoin-chainstate +endif + .PHONY: FORCE check-symbols check-security # dash core # BITCOIN_CORE_H = \ @@ -1157,6 +1161,101 @@ dash_util_LDADD = \ $(BACKTRACE_LIBS) dash_util_LDADD += $(BOOST_LIBS) + +# bitcoin-chainstate binary # +bitcoin_chainstate_SOURCES = \ + bitcoin-chainstate.cpp \ + arith_uint256.cpp \ + blockfilter.cpp \ + chain.cpp \ + chainparamsbase.cpp \ + chainparams.cpp \ + clientversion.cpp \ + coins.cpp \ + compat/glibcxx_sanity.cpp \ + compressor.cpp \ + consensus/merkle.cpp \ + consensus/tx_check.cpp \ + consensus/tx_verify.cpp \ + core_read.cpp \ + dbwrapper.cpp \ + deploymentinfo.cpp \ + deploymentstatus.cpp \ + flatfile.cpp \ + fs.cpp \ + hash.cpp \ + index/base.cpp \ + index/blockfilterindex.cpp \ + index/coinstatsindex.cpp \ + init/common.cpp \ + key.cpp \ + logging.cpp \ + netaddress.cpp \ + node/blockstorage.cpp \ + node/chainstate.cpp \ + node/coinstats.cpp \ + node/ui_interface.cpp \ + policy/feerate.cpp \ + policy/fees.cpp \ + policy/packages.cpp \ + policy/policy.cpp \ + policy/rbf.cpp \ + policy/settings.cpp \ + pow.cpp \ + primitives/block.cpp \ + primitives/transaction.cpp \ + pubkey.cpp \ + random.cpp \ + randomenv.cpp \ + scheduler.cpp \ + script/interpreter.cpp \ + script/script.cpp \ + script/script_error.cpp \ + script/sigcache.cpp \ + script/standard.cpp \ + shutdown.cpp \ + signet.cpp \ + support/cleanse.cpp \ + support/lockedpool.cpp \ + sync.cpp \ + threadinterrupt.cpp \ + timedata.cpp \ + txdb.cpp \ + txmempool.cpp \ + uint256.cpp \ + util/asmap.cpp \ + util/bytevectorhash.cpp \ + util/getuniquepath.cpp \ + util/hasher.cpp \ + util/moneystr.cpp \ + util/rbf.cpp \ + util/serfloat.cpp \ + util/settings.cpp \ + util/strencodings.cpp \ + util/syscall_sandbox.cpp \ + util/system.cpp \ + util/thread.cpp \ + util/threadnames.cpp \ + util/time.cpp \ + util/tokenpipe.cpp \ + validation.cpp \ + validationinterface.cpp \ + versionbits.cpp \ + warnings.cpp +bitcoin_chainstate_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +bitcoin_chainstate_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +bitcoin_chainstate_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) $(PTHREAD_FLAGS) +bitcoin_chainstate_LDADD = \ + $(LIBBITCOIN_CRYPTO) \ + $(LIBUNIVALUE) \ + $(LIBSECP256K1) \ + $(LIBLEVELDB) \ + $(LIBLEVELDB_SSE42) \ + $(LIBMEMENV) + +# Required for obj/build.h to be generated first. +# More details: https://www.gnu.org/software/automake/manual/html_node/Built-Sources-Example.html +bitcoin_chainstate-clientversion.$(OBJEXT): obj/build.h # # dashconsensus library # diff --git a/src/bitcoin-chainstate.cpp b/src/bitcoin-chainstate.cpp new file mode 100644 index 000000000000..f93197350d01 --- /dev/null +++ b/src/bitcoin-chainstate.cpp @@ -0,0 +1,262 @@ +// Copyright (c) 2022 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +// The bitcoin-chainstate executable serves to surface the dependencies required +// by a program wishing to use Bitcoin Core's consensus engine as it is right +// now. +// +// DEVELOPER NOTE: Since this is a "demo-only", experimental, etc. executable, +// it may diverge from Bitcoin Core's coding style. +// +// It is part of the libbitcoinkernel project. + +#include +#include +#include +#include +#include +#include +#include +#include