diff --git a/src/Makefile.am b/src/Makefile.am index f9c03e364236f..9a3b18c65482a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -252,6 +252,7 @@ BITCOIN_CORE_H = \ instantsend/db.h \ instantsend/instantsend.h \ instantsend/lock.h \ + instantsend/net_instantsend.h \ instantsend/signing.h \ kernel/coinstats.h \ key.h \ @@ -523,6 +524,7 @@ libbitcoin_node_a_SOURCES = \ instantsend/db.cpp \ instantsend/instantsend.cpp \ instantsend/lock.cpp \ + instantsend/net_instantsend.cpp \ instantsend/signing.cpp \ kernel/coinstats.cpp \ llmq/blockprocessor.cpp \ diff --git a/src/init.cpp b/src/init.cpp index cadf16db45c6c..642df953f34f3 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -87,6 +87,7 @@ #include #include #include +#include #include #include #include @@ -250,6 +251,9 @@ void Interrupt(NodeContext& node) if (node.active_ctx) { node.active_ctx->Interrupt(); } + if (node.peerman) { + node.peerman->InterruptHandlers(); + } if (node.llmq_ctx) { node.llmq_ctx->Interrupt(); } @@ -285,7 +289,9 @@ void PrepareShutdown(NodeContext& node) StopREST(); StopRPC(); StopHTTPServer(); + if (node.active_ctx) node.active_ctx->Stop(); + if (node.peerman) node.peerman->StopHandlers(); if (node.llmq_ctx) node.llmq_ctx->Stop(); for (const auto& client : node.chain_clients) { @@ -2193,6 +2199,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) g_active_notification_interface = std::make_unique(*node.active_ctx, *node.mn_activeman); RegisterValidationInterface(g_active_notification_interface.get()); } + node.peerman->AddExtraHandler(std::make_unique(node.peerman.get(), *node.llmq_ctx->isman, *node.llmq_ctx->qman, chainman.ActiveChainstate())); // ********************************************************* Step 7d: Setup other Dash services @@ -2288,6 +2295,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 10a: schedule Dash-specific tasks node.llmq_ctx->Start(*node.peerman); + node.peerman->StartHandlers(); if (node.active_ctx) node.active_ctx->Start(*node.connman, *node.peerman); node.scheduler->scheduleEvery(std::bind(&CNetFulfilledRequestManager::DoMaintenance, std::ref(*node.netfulfilledman)), std::chrono::minutes{1}); diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 293d35928e3c8..0415a19d53776 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -4,25 +4,17 @@ #include +#include #include #include -#include -#include -#include -#include -#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 { @@ -56,100 +48,26 @@ Uint256HashSet GetIdsFromLockable(const std::vector& vec) } } // anonymous namespace -CInstantSendManager::CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CQuorumManager& _qman, +CInstantSendManager::CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CSigningManager& _sigman, CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : db{db_params}, clhandler{_clhandler}, m_chainstate{chainstate}, - qman{_qman}, sigman{_sigman}, spork_manager{sporkman}, mempool{_mempool}, m_mn_sync{mn_sync} { - workInterrupt.reset(); } CInstantSendManager::~CInstantSendManager() = default; -void CInstantSendManager::Start(PeerManager& peerman) -{ - // can't start new thread if we have one running already - if (workThread.joinable()) { - assert(false); - } +bool ShouldReportISLockTiming() { return g_stats_client->active() || LogAcceptDebug(BCLog::INSTANTSEND); } - workThread = std::thread(&util::TraceThread, "isman", [this, &peerman] { WorkThreadMain(peerman); }); - - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->Start(); - } -} - -void CInstantSendManager::Stop() -{ - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->Stop(); - } - - // make sure to call InterruptWorkerThread() first - if (!workInterrupt) { - assert(false); - } - - if (workThread.joinable()) { - workThread.join(); - } -} - -bool ShouldReportISLockTiming() { - return g_stats_client->active() || LogAcceptDebug(BCLog::INSTANTSEND); -} - -MessageProcessingResult CInstantSendManager::ProcessMessage(NodeId from, std::string_view msg_type, CDataStream& vRecv) +void CInstantSendManager::EnqueueInstantSendLock(NodeId from, const uint256& hash, + std::shared_ptr islock) { - if (!IsInstantSendEnabled() || msg_type != NetMsgType::ISDLOCK) { - return {}; - } - - const auto islock = std::make_shared(); - vRecv >> *islock; - - auto hash = ::SerializeHash(*islock); - - MessageProcessingResult ret{}; - ret.m_to_erase = CInv{MSG_ISDLOCK, hash}; - - if (!islock->TriviallyValid()) { - ret.m_error = MisbehavingError{100}; - return ret; - } - - const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash)); - if (blockIndex == nullptr) { - // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash - ret.m_error = MisbehavingError{1}; - return ret; - } - - // Deterministic islocks MUST use rotation based llmq - auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; - const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt); - if (blockIndex->nHeight % llmq_params_opt->dkgInterval != 0) { - ret.m_error = MisbehavingError{100}; - return ret; - } - - if (WITH_LOCK(cs_pendingLocks, return pendingInstantSendLocks.count(hash) || pendingNoTxInstantSendLocks.count(hash)) || - db.KnownInstantSendLock(hash)) { - return ret; - } - - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: received islock, peer=%d\n", __func__, - islock->txid.ToString(), hash.ToString(), from); - if (ShouldReportISLockTiming()) { auto time_diff = [&]() -> int64_t { LOCK(cs_timingsTxSeen); @@ -168,191 +86,40 @@ MessageProcessingResult CInstantSendManager::ProcessMessage(NodeId from, std::st } LOCK(cs_pendingLocks); - pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{from, islock}); - return ret; + pendingInstantSendLocks.emplace(hash, instantsend::PendingISLockFromPeer{from, std::move(islock)}); } -instantsend::PendingState CInstantSendManager::ProcessPendingInstantSendLocks() +instantsend::PendingState CInstantSendManager::FetchPendingLocks() { - std::vector> pend; instantsend::PendingState ret; - if (!IsInstantSendEnabled()) { - return ret; - } - - { - LOCK(cs_pendingLocks); - // only process a max 32 locks at a time to avoid duplicate verification of recovered signatures which have been - // verified by CSigningManager in parallel - const size_t maxCount = 32; - // The keys of the removed values are temporaily stored here to avoid invalidating an iterator - std::vector removed; - removed.reserve(maxCount); - pend.reserve(maxCount); - - for (const auto& [islockHash, nodeid_islptr_pair] : pendingInstantSendLocks) { - // Check if we've reached max count - if (pend.size() >= maxCount) { - ret.m_pending_work = true; - break; - } - pend.emplace_back(islockHash, std::move(nodeid_islptr_pair)); - removed.emplace_back(islockHash); - } - - for (const auto& islockHash : removed) { - pendingInstantSendLocks.erase(islockHash); + LOCK(cs_pendingLocks); + // only process a max 32 locks at a time to avoid duplicate verification of recovered signatures which have been + // verified by CSigningManager in parallel + const size_t maxCount = 32; + // The keys of the removed values are temporaily stored here to avoid invalidating an iterator + std::vector removed; + removed.reserve(std::min(maxCount, pendingInstantSendLocks.size())); + + for (const auto& [islockHash, nodeid_islptr_pair] : pendingInstantSendLocks) { + // Check if we've reached max count + if (ret.m_pending_is.size() >= maxCount) { + ret.m_pending_work = true; + break; } + ret.m_pending_is.emplace_back(islockHash, std::move(nodeid_islptr_pair)); + removed.emplace_back(islockHash); } - if (pend.empty()) { - ret.m_pending_work = false; - return ret; - } - - // TODO Investigate if leaving this is ok - auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; - const auto& llmq_params_opt = Params().GetLLMQ(llmqType); - assert(llmq_params_opt); - const auto& llmq_params = llmq_params_opt.value(); - auto dkgInterval = llmq_params.dkgInterval; - - // First check against the current active set and don't ban - auto badISLocks = ProcessPendingInstantSendLocks(llmq_params, /*signOffset=*/0, /*ban=*/false, pend, ret.m_peer_activity); - if (!badISLocks.empty()) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- doing verification on old active set\n", __func__); - - // filter out valid IS locks from "pend" - keep only bad ones - std::vector> filteredPend; - filteredPend.reserve(badISLocks.size()); - for (auto& p : pend) { - if (badISLocks.contains(p.first)) { - filteredPend.push_back(std::move(p)); - } - } - - // Now check against the previous active set and perform banning if this fails - ProcessPendingInstantSendLocks(llmq_params, dkgInterval, /*ban=*/true, filteredPend, ret.m_peer_activity); + for (const auto& islockHash : removed) { + pendingInstantSendLocks.erase(islockHash); } return ret; } -Uint256HashSet CInstantSendManager::ProcessPendingInstantSendLocks( - const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, - const std::vector>& pend, - std::vector>& peer_activity) -{ - CBLSBatchVerifier batchVerifier(false, true, 8); - Uint256HashMap recSigs; - - size_t verifyCount = 0; - size_t alreadyVerified = 0; - for (const auto& p : pend) { - const auto& hash = p.first; - auto nodeId = p.second.node_id; - const auto& islock = p.second.islock; - - if (batchVerifier.badSources.count(nodeId)) { - continue; - } - - if (!islock->sig.Get().IsValid()) { - batchVerifier.badSources.emplace(nodeId); - continue; - } - - auto id = islock->GetRequestId(); - - // no need to verify an ISLOCK if we already have verified the recovered sig that belongs to it - if (sigman.HasRecoveredSig(llmq_params.type, id, islock->txid)) { - alreadyVerified++; - continue; - } - - const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash)); - if (blockIndex == nullptr) { - batchVerifier.badSources.emplace(nodeId); - continue; - } - - int nSignHeight{-1}; - const auto dkgInterval = llmq_params.dkgInterval; - if (blockIndex->nHeight + dkgInterval < m_chainstate.m_chain.Height()) { - nSignHeight = blockIndex->nHeight + dkgInterval - 1; - } - // For RegTest non-rotating quorum cycleHash has directly quorum hash - auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, qman, - id, nSignHeight, signOffset) - : qman.GetQuorum(llmq_params.type, islock->cycleHash); - - if (!quorum) { - // should not happen, but if one fails to select, all others will also fail to select - return {}; - } - uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get(); - batchVerifier.PushMessage(nodeId, hash, signHash, islock->sig.Get(), quorum->qc->quorumPublicKey); - verifyCount++; - - // 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 (!sigman.HasRecoveredSigForId(llmq_params.type, id)) { - recSigs.try_emplace(hash, - CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid, islock->sig)); - } - } - - cxxtimer::Timer verifyTimer(true); - batchVerifier.Verify(); - verifyTimer.stop(); - - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- verified locks. count=%d, alreadyVerified=%d, vt=%d, nodes=%d\n", __func__, - verifyCount, alreadyVerified, verifyTimer.count(), batchVerifier.GetUniqueSourceCount()); - - Uint256HashSet badISLocks; - - if (ban && !batchVerifier.badSources.empty()) { - LOCK(::cs_main); - for (const auto& nodeId : batchVerifier.badSources) { - // Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which - // does not validate anymore due to changed quorums - peer_activity.emplace_back(nodeId, MisbehavingError{20}); - } - } - for (const auto& p : pend) { - const auto& hash = p.first; - auto nodeId = p.second.node_id; - const auto& islock = p.second.islock; - - if (batchVerifier.badMessages.count(hash)) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", - __func__, islock->txid.ToString(), hash.ToString(), nodeId); - badISLocks.emplace(hash); - continue; - } - - peer_activity.emplace_back(nodeId, ProcessInstantSendLock(nodeId, hash, islock)); - - // See comment further on top. We pass a reconstructed recovered sig to the signing manager to avoid - // double-verification of the sig. - auto it = recSigs.find(hash); - if (it != recSigs.end()) { - auto recSig = std::make_shared(std::move(it->second)); - if (!sigman.HasRecoveredSigForId(llmq_params.type, recSig->getId())) { - LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: passing reconstructed recSig to signing mgr, peer=%d\n", __func__, - islock->txid.ToString(), hash.ToString(), nodeId); - sigman.PushReconstructedRecoveredSig(recSig); - } - } - } - - return badISLocks; -} - -MessageProcessingResult CInstantSendManager::ProcessInstantSendLock(NodeId from, const uint256& hash, - const instantsend::InstantSendLockPtr& islock) +std::variant CInstantSendManager::ProcessInstantSendLock( + NodeId from, const uint256& hash, const instantsend::InstantSendLockPtr& islock) { LogPrint(BCLog::INSTANTSEND, "CInstantSendManager::%s -- txid=%s, islock=%s: processing islock, peer=%d\n", __func__, islock->txid.ToString(), hash.ToString(), from); @@ -361,12 +128,12 @@ MessageProcessingResult CInstantSendManager::ProcessInstantSendLock(NodeId from, signer->ClearLockFromQueue(islock); } if (db.KnownInstantSendLock(hash)) { - return {}; + return std::monostate{}; } if (const auto sameTxIsLock = db.GetInstantSendLockByTxid(islock->txid)) { // can happen, nothing to do - return {}; + return std::monostate{}; } for (const auto& in : islock->inputs) { const auto sameOutpointIsLock = db.GetInstantSendLockByInput(in); @@ -389,7 +156,7 @@ MessageProcessingResult CInstantSendManager::ProcessInstantSendLock(NodeId from, if (pindexMined != nullptr && clhandler.HasChainLock(pindexMined->nHeight, pindexMined->GetBlockHash())) { LogPrint(BCLog::INSTANTSEND, "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 {}; + return std::monostate{}; } } @@ -420,17 +187,10 @@ MessageProcessingResult CInstantSendManager::ProcessInstantSendLock(NodeId from, mempool.AddTransactionsUpdated(1); } - MessageProcessingResult ret{}; - CInv inv(MSG_ISDLOCK, hash); if (found_transaction) { - ret.m_inv_filter = std::make_pair(inv, tx); - } else { - // we don't have the TX yet, so we only filter based on txid. Later when that TX arrives, we will re-announce - // with the TX taken into account. - ret.m_inv_filter = std::make_pair(inv, islock->txid); - ret.m_request_tx = islock->txid; + return tx; } - return ret; + return islock->txid; } void CInstantSendManager::TransactionAddedToMempool(const CTransactionRef& tx) @@ -603,6 +363,24 @@ void CInstantSendManager::RemoveNonLockedTx(const uint256& txid, bool retryChild __func__, txid.ToString(), retryChildren, retryChildrenCount); } +std::vector CInstantSendManager::PrepareTxToRetry() +{ + std::vector txns{}; + + LOCK2(cs_nonLocked, cs_pendingRetry); + if (pendingRetryTxs.empty()) return txns; + txns.reserve(pendingRetryTxs.size()); + for (const auto& txid : pendingRetryTxs) { + if (auto it = nonLockedTxs.find(txid); it != nonLockedTxs.end()) { + const auto& [_, tx_info] = *it; + if (tx_info.tx) { + txns.push_back(tx_info.tx); + } + } + } + return txns; +} + void CInstantSendManager::RemoveConflictedTx(const CTransaction& tx) { RemoveNonLockedTx(tx.GetHash(), false); @@ -925,43 +703,6 @@ size_t CInstantSendManager::GetInstantSendLockCount() const return db.GetInstantSendLockCount(); } -void CInstantSendManager::WorkThreadMain(PeerManager& peerman) -{ - while (!workInterrupt) { - bool fMoreWork = [&]() -> bool { - if (!IsInstantSendEnabled()) return false; - auto [more_work, peer_activity] = ProcessPendingInstantSendLocks(); - for (auto& [node_id, mpr] : peer_activity) { - peerman.PostProcessMessage(std::move(mpr), node_id); - } - auto signer = m_signer.load(std::memory_order_acquire); - if (!signer) return more_work; - // Construct set of non-locked transactions that are pending to retry - std::vector txns{}; - { - LOCK2(cs_nonLocked, cs_pendingRetry); - if (pendingRetryTxs.empty()) return more_work; - txns.reserve(pendingRetryTxs.size()); - for (const auto& txid : pendingRetryTxs) { - if (auto it = nonLockedTxs.find(txid); it != nonLockedTxs.end()) { - const auto& [_, tx_info] = *it; - if (tx_info.tx) { - txns.push_back(tx_info.tx); - } - } - } - } - // Retry processing them - signer->ProcessPendingRetryLockTxs(txns); - return more_work; - }(); - - if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { - return; - } - } -} - bool CInstantSendManager::IsInstantSendEnabled() const { return !fReindex && !fImporting && spork_manager.IsSporkActive(SPORK_2_INSTANTSEND_ENABLED); diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index ebe6b4cb48b29..30e8ba4a8ebfd 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -5,21 +5,21 @@ #ifndef BITCOIN_INSTANTSEND_INSTANTSEND_H #define BITCOIN_INSTANTSEND_INSTANTSEND_H +#include +#include +#include + #include #include #include #include #include -#include - -#include -#include -#include #include #include #include #include +#include #include class CBlockIndex; @@ -28,7 +28,6 @@ class CDataStream; class CMasternodeSync; class CSporkManager; class CTxMemPool; -class PeerManager; namespace Consensus { struct LLMQParams; } // namespace Consensus @@ -46,13 +45,12 @@ struct PendingISLockFromPeer { struct PendingState { bool m_pending_work{false}; - std::vector> m_peer_activity{}; + std::vector> m_pending_is; }; } // namespace instantsend namespace llmq { class CChainLocksHandler; -class CQuorumManager; class CSigningManager; class CInstantSendManager final : public instantsend::InstantSendSignerParent @@ -62,7 +60,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CChainLocksHandler& clhandler; CChainState& m_chainstate; - CQuorumManager& qman; CSigningManager& sigman; CSporkManager& spork_manager; CTxMemPool& mempool; @@ -70,9 +67,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent std::atomic m_signer{nullptr}; - std::thread workThread; - CThreadInterrupt workInterrupt; - mutable Mutex cs_pendingLocks; // Incoming and not verified yet Uint256HashMap pendingInstantSendLocks GUARDED_BY(cs_pendingLocks); @@ -101,9 +95,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; - explicit CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CQuorumManager& _qman, - CSigningManager& _sigman, CSporkManager& sporkman, CTxMemPool& _mempool, - const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); + explicit CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CSigningManager& _sigman, + CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync, + const util::DbWrapperParams& db_params); ~CInstantSendManager(); void ConnectSigner(gsl::not_null signer) @@ -114,22 +108,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent } void DisconnectSigner() { m_signer.store(nullptr, std::memory_order_release); } - void Start(PeerManager& peerman); - void Stop(); - void InterruptWorkerThread() { workInterrupt(); }; + instantsend::InstantSendSigner* Signer() const { return m_signer.load(); } private: - instantsend::PendingState ProcessPendingInstantSendLocks() - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - - Uint256HashSet ProcessPendingInstantSendLocks(const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, - const std::vector>& pend, - std::vector>& peer_activity) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - MessageProcessingResult ProcessInstantSendLock(NodeId from, const uint256& hash, - const instantsend::InstantSendLockPtr& islock) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void AddNonLockedTx(const CTransactionRef& tx, const CBlockIndex* pindexMined) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_timingsTxSeen); void RemoveNonLockedTx(const uint256& txid, bool retryChildren) @@ -143,9 +124,6 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent void ResolveBlockConflicts(const uint256& islockHash, const instantsend::InstantSendLock& islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void WorkThreadMain(PeerManager& peerman) - EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingLocks, !cs_pendingRetry); - void HandleFullyConfirmedBlock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked, !cs_pendingRetry); @@ -154,8 +132,17 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent bool IsWaitingForTx(const uint256& txHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); instantsend::InstantSendLockPtr GetConflictingLock(const CTransaction& tx) const override; - [[nodiscard]] MessageProcessingResult ProcessMessage(NodeId from, std::string_view msg_type, CDataStream& vRecv) + /* Helpers for communications between CInstantSendManager & NetInstantSend */ + // This helper returns up to 32 pending locks and remove them from queue of pending + [[nodiscard]] instantsend::PendingState FetchPendingLocks() EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + void EnqueueInstantSendLock(NodeId from, const uint256& hash, std::shared_ptr islock) EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + [[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); 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 new file mode 100644 index 0000000000000..ecb81b6a5edb2 --- /dev/null +++ b/src/instantsend/net_instantsend.cpp @@ -0,0 +1,267 @@ +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void NetInstantSend::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv) +{ + if (msg_type != NetMsgType::ISDLOCK) { + return; + } + + if (!m_is_manager.IsInstantSendEnabled()) return; + + auto islock = std::make_shared(); + vRecv >> *islock; + + uint256 hash = ::SerializeHash(*islock); + + WITH_LOCK(::cs_main, m_peer_manager->PeerEraseObjectRequest(pfrom.GetId(), CInv{MSG_ISDLOCK, hash})); + + if (!islock->TriviallyValid()) { + m_peer_manager->PeerMisbehaving(pfrom.GetId(), 100); + return; + } + + int block_height = [this, islock] { + const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash)); + if (blockIndex == nullptr) { + return -1; + } + return blockIndex->nHeight; + }(); + if (block_height < 0) { + // Maybe we don't have the block yet or maybe some peer spams invalid values for cycleHash + m_peer_manager->PeerMisbehaving(pfrom.GetId(), 1); + return; + } + + // Deterministic islocks MUST use rotation based llmq + auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + assert(llmq_params_opt); + if (block_height % llmq_params_opt->dkgInterval != 0) { + m_peer_manager->PeerMisbehaving(pfrom.GetId(), 100); + return; + } + + if (!m_is_manager.AlreadyHave(CInv{MSG_ISDLOCK, hash})) { + LogPrint(BCLog::INSTANTSEND, "NetInstantSend -- ISDLOCK txid=%s, islock=%s: received islock, peer=%d\n", + islock->txid.ToString(), hash.ToString(), pfrom.GetId()); + + m_is_manager.EnqueueInstantSendLock(pfrom.GetId(), hash, std::move(islock)); + } +} + +void NetInstantSend::Start() +{ + // can't start new thread if we have one running already + if (workThread.joinable()) { + assert(false); + } + + workThread = std::thread(&util::TraceThread, "isman", [this] { WorkThreadMain(); }); + + if (auto signer = m_is_manager.Signer(); signer) { + signer->Start(); + } +} + +void NetInstantSend::Stop() +{ + if (auto signer = m_is_manager.Signer(); signer) { + signer->Stop(); + } + + // make sure to call Interrupt() first + if (!workInterrupt) { + assert(false); + } + + if (workThread.joinable()) { + workThread.join(); + } +} + +Uint256HashSet NetInstantSend::ProcessPendingInstantSendLocks( + const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, + const std::vector>& pend) +{ + CBLSBatchVerifier batchVerifier(false, true, 8); + Uint256HashMap recSigs; + + size_t verifyCount = 0; + size_t alreadyVerified = 0; + for (const auto& p : pend) { + const auto& hash = p.first; + auto nodeId = p.second.node_id; + const auto& islock = p.second.islock; + + if (batchVerifier.badSources.count(nodeId)) { + continue; + } + + if (!islock->sig.Get().IsValid()) { + batchVerifier.badSources.emplace(nodeId); + continue; + } + + 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)) { + alreadyVerified++; + continue; + } + + const auto blockIndex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(islock->cycleHash)); + if (blockIndex == nullptr) { + batchVerifier.badSources.emplace(nodeId); + continue; + } + + int nSignHeight{-1}; + const auto dkgInterval = llmq_params.dkgInterval; + if (blockIndex->nHeight + dkgInterval < m_chainstate.m_chain.Height()) { + nSignHeight = blockIndex->nHeight + dkgInterval - 1; + } + // For RegTest non-rotating quorum cycleHash has directly quorum hash + auto quorum = llmq_params.useRotation ? llmq::SelectQuorumForSigning(llmq_params, m_chainstate.m_chain, m_qman, + id, nSignHeight, signOffset) + : m_qman.GetQuorum(llmq_params.type, islock->cycleHash); + + if (!quorum) { + // should not happen, but if one fails to select, all others will also fail to select + return {}; + } + uint256 signHash = llmq::SignHash{llmq_params.type, quorum->qc->quorumHash, id, islock->txid}.Get(); + batchVerifier.PushMessage(nodeId, hash, signHash, islock->sig.Get(), quorum->qc->quorumPublicKey); + verifyCount++; + + // 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)) { + recSigs.try_emplace(hash, llmq::CRecoveredSig(llmq_params.type, quorum->qc->quorumHash, id, islock->txid, + islock->sig)); + } + } + + cxxtimer::Timer verifyTimer(true); + batchVerifier.Verify(); + verifyTimer.stop(); + + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- verified locks. count=%d, alreadyVerified=%d, vt=%d, nodes=%d\n", + __func__, verifyCount, alreadyVerified, verifyTimer.count(), batchVerifier.GetUniqueSourceCount()); + + Uint256HashSet badISLocks; + + if (ban && !batchVerifier.badSources.empty()) { + LOCK(::cs_main); + for (const auto& nodeId : batchVerifier.badSources) { + // Let's not be too harsh, as the peer might simply be unlucky and might have sent us an old lock which + // does not validate anymore due to changed quorums + m_peer_manager->PeerMisbehaving(nodeId, 20); + } + } + for (const auto& p : pend) { + const auto& hash = p.first; + auto nodeId = p.second.node_id; + const auto& islock = p.second.islock; + + if (batchVerifier.badMessages.count(hash)) { + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- txid=%s, islock=%s: invalid sig in islock, peer=%d\n", + __func__, islock->txid.ToString(), hash.ToString(), nodeId); + badISLocks.emplace(hash); + 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)); + } + + // See comment further on top. We pass a reconstructed recovered sig to the signing manager to avoid + // double-verification of the sig. + auto it = recSigs.find(hash); + if (it != recSigs.end()) { + auto recSig = std::make_shared(std::move(it->second)); + if (!m_is_manager.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); + } + } + } + + return badISLocks; +} + + +void NetInstantSend::ProcessPendingISLocks(std::vector>&& locks_to_process) +{ + // TODO Investigate if leaving this is ok + auto llmqType = Params().GetConsensus().llmqTypeDIP0024InstantSend; + const auto& llmq_params_opt = Params().GetLLMQ(llmqType); + assert(llmq_params_opt); + const auto& llmq_params = llmq_params_opt.value(); + auto dkgInterval = llmq_params.dkgInterval; + + // First check against the current active set and don't ban + auto bad_is_locks = ProcessPendingInstantSendLocks(llmq_params, /*signOffset=*/0, /*ban=*/false, locks_to_process); + if (!bad_is_locks.empty()) { + LogPrint(BCLog::INSTANTSEND, "NetInstantSend::%s -- doing verification on old active set\n", __func__); + + // filter out valid IS locks from "pend" - keep only bad ones + std::vector> still_pending; + still_pending.reserve(bad_is_locks.size()); + for (auto& p : locks_to_process) { + if (bad_is_locks.contains(p.first)) { + still_pending.push_back(std::move(p)); + } + } + // Now check against the previous active set and perform banning if this fails + ProcessPendingInstantSendLocks(llmq_params, dkgInterval, /*ban=*/true, std::move(still_pending)); + } +} + +void NetInstantSend::WorkThreadMain() +{ + while (!workInterrupt) { + bool fMoreWork = [&]() -> bool { + if (!m_is_manager.IsInstantSendEnabled()) return false; + + auto [more_work, locks] = m_is_manager.FetchPendingLocks(); + if (!locks.empty()) { + ProcessPendingISLocks(std::move(locks)); + } + if (auto signer = m_is_manager.Signer(); signer) { + signer->ProcessPendingRetryLockTxs(m_is_manager.PrepareTxToRetry()); + } + return more_work; + }(); + + if (!fMoreWork && !workInterrupt.sleep_for(std::chrono::milliseconds(100))) { + return; + } + } +} diff --git a/src/instantsend/net_instantsend.h b/src/instantsend/net_instantsend.h new file mode 100644 index 0000000000000..d21ffb734ef1c --- /dev/null +++ b/src/instantsend/net_instantsend.h @@ -0,0 +1,56 @@ +// Copyright (c) 2025 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_INSTANTSEND_NET_INSTANTSEND_H +#define BITCOIN_INSTANTSEND_NET_INSTANTSEND_H + +#include + +#include + +namespace instantsend { +struct InstantSendLock; +struct PendingISLockFromPeer; +using InstantSendLockPtr = std::shared_ptr; +} // namespace instantsend +namespace llmq { +class CInstantSendManager; +class CQuorumManager; +} // namespace llmq + +class NetInstantSend final : public NetHandler +{ +public: + NetInstantSend(PeerManagerInternal* peer_manager, llmq::CInstantSendManager& is_manager, llmq::CQuorumManager& qman, + CChainState& chainstate) : + NetHandler(peer_manager), + m_is_manager{is_manager}, + m_qman(qman), + m_chainstate{chainstate} + { + workInterrupt.reset(); + } + void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv) override; + + void Start() override; + void Stop() override; + void Interrupt() override { workInterrupt(); }; + + void WorkThreadMain(); + +private: + void ProcessPendingISLocks(std::vector>&& locks_to_process); + + Uint256HashSet ProcessPendingInstantSendLocks( + const Consensus::LLMQParams& llmq_params, int signOffset, bool ban, + const std::vector>& pend); + llmq::CInstantSendManager& m_is_manager; + llmq::CQuorumManager& m_qman; + const CChainState& m_chainstate; + + std::thread workThread; + CThreadInterrupt workInterrupt; +}; + +#endif // BITCOIN_INSTANTSEND_NET_INSTANTSEND_H diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 9e8cb27dea713..e3c7b4911f1b4 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -32,7 +32,7 @@ LLMQContext::LLMQContext(ChainstateManager& chainman, CDeterministicMNManager& d db_params)}, sigman{std::make_unique(chainman.ActiveChainstate(), *qman, db_params)}, clhandler{std::make_unique(chainman.ActiveChainstate(), *qman, sporkman, mempool, mn_sync)}, - isman{std::make_unique(*clhandler, chainman.ActiveChainstate(), *qman, *sigman, sporkman, + isman{std::make_unique(*clhandler, chainman.ActiveChainstate(), *sigman, sporkman, mempool, mn_sync, db_params)} { // Have to start it early to let VerifyDB check ChainLock signatures in coinbase @@ -44,7 +44,6 @@ LLMQContext::~LLMQContext() { } void LLMQContext::Interrupt() { - isman->InterruptWorkerThread(); sigman->InterruptWorkerThread(); } @@ -53,12 +52,10 @@ void LLMQContext::Start(PeerManager& peerman) qman->Start(); sigman->StartWorkerThread(peerman); clhandler->Start(*isman); - isman->Start(peerman); } void LLMQContext::Stop() { - isman->Stop(); clhandler->Stop(); sigman->StopWorkerThread(); qman->Stop(); diff --git a/src/msg_result.h b/src/msg_result.h index 01b14288ee274..646d2142443ce 100644 --- a/src/msg_result.h +++ b/src/msg_result.h @@ -7,7 +7,6 @@ #include -#include #include #include @@ -54,12 +53,6 @@ struct MessageProcessingResult //! @m_dsq will relay DSQs to connected peers std::vector m_dsq; - //! @m_inv_filter will relay this inventory if filter matches to connected peers if not nullopt - std::optional>> m_inv_filter; - - //! @m_request_tx will ask connected peers to relay transaction if not nullopt - std::optional m_request_tx; - //! @m_transactions will relay transactions to peers which is ready to accept it (some peers does not accept transactions) std::vector m_transactions; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index f4514dbe61f92..392d1d41ef35f 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -595,6 +595,11 @@ class PeerManagerImpl final : public PeerManager const std::unique_ptr& active_ctx, CJWalletManager* const cj_walletman, const std::unique_ptr& llmq_ctx, bool ignore_incoming_txs); + ~PeerManagerImpl() + { + RemoveHandlers(); + } + /** Overridden from CValidationInterface. */ void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindexConnected) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex, !m_recent_confirmed_transactions_mutex); @@ -637,6 +642,21 @@ class PeerManagerImpl final : public PeerManager void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) override; bool IsBanned(NodeId pnode) override EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_peer_mutex); size_t GetRequestedObjectCount(NodeId nodeid) const override EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + + /** Implements external handlers logic */ + void AddExtraHandler(std::unique_ptr&& handler) override; + void RemoveHandlers() override; + void StartHandlers() override; + void StopHandlers() override; + void InterruptHandlers() override; + + /** Implement PeerManagerInternal */ + void PeerMisbehaving(const NodeId pnode, const int howmuch, const std::string& message = "") override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void PeerEraseObjectRequest(const NodeId nodeid, const CInv& inv) override EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + void PeerRelayInv(const CInv& inv) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void PeerRelayInvFiltered(const CInv& inv, const CTransaction& relatedTx) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void PeerRelayInvFiltered(const CInv& inv, const uint256& relatedTxHash) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); + void PeerAskPeersForTransaction(const uint256& txid) override EXCLUSIVE_LOCKS_REQUIRED(!m_peer_mutex); private: void _RelayTransaction(const uint256& txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main, !m_peer_mutex); @@ -1067,6 +1087,8 @@ class PeerManagerImpl final : public PeerManager std::vector> vExtraTxnForCompact GUARDED_BY(g_msgproc_mutex); /** Offset into vExtraTxnForCompact to insert the next tx */ size_t vExtraTxnForCompactIt GUARDED_BY(g_msgproc_mutex) = 0; + + std::vector> m_handlers; }; // Keeps track of the time (in microseconds) when transactions were requested last time @@ -1625,6 +1647,46 @@ size_t PeerManagerImpl::GetRequestedObjectCount(NodeId nodeid) const return state->m_object_download.m_object_process_time.size(); } +void PeerManagerImpl::AddExtraHandler(std::unique_ptr&& handler) +{ + assert(handler != nullptr); + if (auto i = dynamic_cast(handler.get()); i != nullptr) { + RegisterValidationInterface(i); + } + m_handlers.emplace_back(std::move(handler)); +} + +void PeerManagerImpl::RemoveHandlers() +{ + InterruptHandlers(); + StopHandlers(); + m_handlers.clear(); +} + +void PeerManagerImpl::StartHandlers() +{ + for (auto& handler : m_handlers) { + handler->Start(); + } +} + +void PeerManagerImpl::StopHandlers() +{ + for (auto& handler : m_handlers) { + if (auto i = dynamic_cast(handler.get()); i != nullptr) { + UnregisterValidationInterface(i); + } + handler->Stop(); + } +} + +void PeerManagerImpl::InterruptHandlers() +{ + for (auto& handler : m_handlers) { + handler->Interrupt(); + } +} + void PeerManagerImpl::UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) { LOCK(cs_main); @@ -3521,19 +3583,6 @@ void PeerManagerImpl::PostProcessMessage(MessageProcessingResult&& result, NodeI for (const auto& dsq : result.m_dsq) { RelayDSQ(dsq); } - if (result.m_inv_filter) { - const auto& [inv, filter] = result.m_inv_filter.value(); - if (std::holds_alternative(filter)) { - RelayInvFiltered(inv, *std::get(filter)); - } else if (std::holds_alternative(filter)) { - RelayInvFiltered(inv, std::get(filter)); - } else { - assert(false); - } - } - if (result.m_request_tx) { - AskPeersForTransaction(result.m_request_tx.value()); - } } MessageProcessingResult PeerManagerImpl::ProcessPlatformBanMessage(NodeId node, std::string_view msg_type, CDataStream& vRecv) @@ -5394,7 +5443,9 @@ void PeerManagerImpl::ProcessMessage( return; // CLSIG } - PostProcessMessage(m_llmq_ctx->isman->ProcessMessage(pfrom.GetId(), msg_type, vRecv), pfrom.GetId()); + for (const auto& handler : m_handlers) { + handler->ProcessMessage(pfrom, msg_type, vRecv); + } return; } @@ -6470,3 +6521,33 @@ bool PeerManagerImpl::SendMessages(CNode* pto) } // release cs_main return true; } + +void PeerManagerImpl::PeerMisbehaving(const NodeId pnode, const int howmuch, const std::string& message) +{ + Misbehaving(pnode, howmuch, message); +} + +void PeerManagerImpl::PeerEraseObjectRequest(const NodeId nodeid, const CInv& inv) +{ + EraseObjectRequest(nodeid, inv); +} + +void PeerManagerImpl::PeerRelayInv(const CInv& inv) +{ + RelayInv(inv); +} + +void PeerManagerImpl::PeerRelayInvFiltered(const CInv& inv, const CTransaction& relatedTx) +{ + RelayInvFiltered(inv, relatedTx); +} + +void PeerManagerImpl::PeerRelayInvFiltered(const CInv& inv, const uint256& relatedTxHash) +{ + RelayInvFiltered(inv, relatedTxHash); +} + +void PeerManagerImpl::PeerAskPeersForTransaction(const uint256& txid) +{ + AskPeersForTransaction(txid); +} diff --git a/src/net_processing.h b/src/net_processing.h index 8a4a4e7aba884..d15298401c246 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -55,7 +55,36 @@ struct CNodeStateStats { ServiceFlags their_services; }; -class PeerManager : public CValidationInterface, public NetEventsInterface +class PeerManagerInternal +{ +public: + virtual void PeerMisbehaving(const NodeId pnode, const int howmuch, const std::string& message = "") = 0; + virtual void PeerEraseObjectRequest(const NodeId nodeid, const CInv& inv) = 0; + virtual void PeerRelayInv(const CInv& inv) = 0; + virtual void PeerRelayInvFiltered(const CInv& inv, const CTransaction& relatedTx) = 0; + virtual void PeerRelayInvFiltered(const CInv& inv, const uint256& relatedTxHash) = 0; + virtual void PeerAskPeersForTransaction(const uint256& txid) = 0; +}; + +class NetHandler +{ +public: + NetHandler(PeerManagerInternal* peer_manager) : m_peer_manager{Assert(peer_manager)} {} + virtual ~NetHandler() { + Interrupt(); + Stop(); + } + + virtual void Start() {} + virtual void Stop() {} + virtual void Interrupt() {} + virtual void ProcessMessage(CNode& pfrom, const std::string& msg_type, CDataStream& vRecv) {} +protected: + PeerManagerInternal* m_peer_manager; +}; + + +class PeerManager : public CValidationInterface, public NetEventsInterface, public PeerManagerInternal { public: static std::unique_ptr make(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, @@ -133,6 +162,12 @@ class PeerManager : public CValidationInterface, public NetEventsInterface virtual bool IsBanned(NodeId pnode) = 0; virtual size_t GetRequestedObjectCount(NodeId nodeid) const = 0; + + virtual void AddExtraHandler(std::unique_ptr&& handler) = 0; + virtual void RemoveHandlers() = 0; + virtual void StartHandlers() = 0; + virtual void StopHandlers() = 0; + virtual void InterruptHandlers() = 0; }; #endif // BITCOIN_NET_PROCESSING_H diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 4127d5a7df162..e591e3166ab6f 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -23,15 +23,15 @@ # Dash "banman -> common/bloom -> evo/assetlocktx -> llmq/quorums -> net -> banman", "chainlock/chainlock -> instantsend/instantsend -> chainlock/chainlock", + "chainlock/chainlock -> chainlock/signing -> llmq/signing -> net_processing -> chainlock/chainlock", + "chainlock/chainlock -> chainlock/signing -> llmq/signing -> net_processing -> masternode/active/context -> chainlock/chainlock", "chainlock/chainlock -> instantsend/instantsend -> instantsend/signing -> chainlock/chainlock", - "chainlock/chainlock -> instantsend/instantsend -> net_processing -> chainlock/chainlock", - "chainlock/chainlock -> instantsend/instantsend -> net_processing -> masternode/active/context -> chainlock/chainlock", "chainlock/chainlock -> llmq/quorums -> msg_result -> coinjoin/coinjoin -> chainlock/chainlock", "chainlock/chainlock -> validation -> chainlock/chainlock", "chainlock/chainlock -> validation -> evo/chainhelper -> chainlock/chainlock", - "chainlock/signing -> instantsend/instantsend -> net_processing -> masternode/active/context -> chainlock/signing", - "coinjoin/client -> coinjoin/coinjoin -> instantsend/instantsend -> net_processing -> coinjoin/walletman -> coinjoin/client", - "coinjoin/coinjoin -> instantsend/instantsend -> llmq/quorums -> msg_result -> coinjoin/coinjoin", + "chainlock/signing -> llmq/signing -> net_processing -> masternode/active/context -> chainlock/signing", + "coinjoin/coinjoin -> instantsend/instantsend -> spork -> msg_result -> coinjoin/coinjoin", + "coinjoin/client -> core_io -> evo/mnhftx -> llmq/signing -> net_processing -> coinjoin/walletman -> coinjoin/client", "coinjoin/server -> net_processing -> coinjoin/server", "coinjoin/server -> net_processing -> masternode/active/context -> coinjoin/server", "common/bloom -> evo/assetlocktx -> llmq/commitment -> evo/deterministicmns -> evo/simplifiedmns -> merkleblock -> common/bloom", @@ -53,9 +53,8 @@ "governance/governance -> masternode/sync -> governance/governance", "governance/governance -> net_processing -> masternode/active/context -> governance/governance", "governance/governance -> net_processing -> governance/governance", - "instantsend/instantsend -> net_processing -> instantsend/instantsend", - "instantsend/instantsend -> net_processing -> llmq/context -> instantsend/instantsend", - "instantsend/instantsend -> net_processing -> masternode/active/context -> instantsend/instantsend", + "instantsend/instantsend -> instantsend/signing -> llmq/signing -> net_processing -> instantsend/instantsend", + "instantsend/instantsend -> instantsend/signing -> llmq/signing -> net_processing -> masternode/active/context -> instantsend/instantsend", "instantsend/instantsend -> txmempool -> instantsend/instantsend", "instantsend/signing -> llmq/signing -> net_processing -> masternode/active/context -> instantsend/signing", "llmq/blockprocessor -> llmq/utils -> llmq/snapshot -> llmq/blockprocessor",