diff --git a/src/Makefile.am b/src/Makefile.am index 47f3c6bd55fc..711ee2ad98e9 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -174,6 +174,7 @@ BITCOIN_CORE_H = \ blockfilter.h \ chain.h \ chainlock/chainlock.h \ + chainlock/handler.h \ chainlock/clsig.h \ chainlock/signing.h \ chainparams.h \ @@ -499,6 +500,7 @@ libbitcoin_node_a_SOURCES = \ chain.cpp \ chainlock/chainlock.cpp \ chainlock/clsig.cpp \ + chainlock/handler.cpp \ chainlock/signing.cpp \ coinjoin/coinjoin.cpp \ coinjoin/server.cpp \ diff --git a/src/active/context.cpp b/src/active/context.cpp index d025c61b6149..a844505be505 100644 --- a/src/active/context.cpp +++ b/src/active/context.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -19,20 +19,19 @@ #include #include #include - #include #include +#include ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman, CConnman& connman, - CDeterministicMNManager& dmnman, CGovernanceManager& govman, - CMasternodeMetaMan& mn_metaman, CMNHFManager& mnhfman, CSporkManager& sporkman, - CTxMemPool& mempool, llmq::CChainLocksHandler& clhandler, llmq::CInstantSendManager& isman, - llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumManager& qman, - llmq::CQuorumSnapshotManager& qsnapman, llmq::CSigningManager& sigman, - PeerManager& peerman, 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_clhandler{clhandler}, + CDeterministicMNManager& dmnman, CGovernanceManager& govman, CMasternodeMetaMan& mn_metaman, + CMNHFManager& mnhfman, CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, + CTxMemPool& mempool, chainlock::ChainlockHandler& clhandler, + llmq::CInstantSendManager& isman, llmq::CQuorumBlockProcessor& qblockman, + llmq::CQuorumManager& qman, llmq::CQuorumSnapshotManager& qsnapman, + llmq::CSigningManager& sigman, PeerManager& peerman, 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)}, @@ -44,9 +43,9 @@ ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman ehf_sighandler{std::make_unique(chainman, mnhfman, sigman, *shareman, qman)}, qman_handler{std::make_unique(bls_worker, connman, dmnman, qman, qsnapman, *nodeman, chainman, mn_sync, sporkman, sync_map, quorums_recovery, quorums_watch)}, - cl_signer{std::make_unique(chainman.ActiveChainstate(), clhandler, sigman, *shareman, - sporkman, mn_sync)}, - is_signer{std::make_unique(chainman.ActiveChainstate(), clhandler, isman, sigman, + cl_signer{std::make_unique(chainman.ActiveChainstate(), chainlocks, clhandler, isman, + qman, sigman, *shareman, mn_sync)}, + is_signer{std::make_unique(chainman.ActiveChainstate(), chainlocks, isman, sigman, *shareman, qman, sporkman, mempool, mn_sync)} { qdkgsman->InitializeHandlers([&](const Consensus::LLMQParams& llmq_params, @@ -55,7 +54,6 @@ ActiveContext::ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman qblockman, qsnapman, *nodeman, chainman, sporkman, llmq_params, quorums_watch, quorum_idx); }); - m_clhandler.ConnectSigner(cl_signer.get()); m_isman.ConnectSigner(is_signer.get()); m_qman.ConnectManagers(qman_handler.get(), qdkgsman.get()); } @@ -64,7 +62,6 @@ ActiveContext::~ActiveContext() { m_qman.DisconnectManagers(); m_isman.DisconnectSigner(); - m_clhandler.DisconnectSigner(); } void ActiveContext::Interrupt() @@ -77,16 +74,22 @@ void ActiveContext::Start(CConnman& connman, PeerManager& peerman) qman_handler->Start(); qdkgsman->StartThreads(connman, peerman); shareman->Start(); + cl_signer->Start(); cl_signer->RegisterRecoveryInterface(); is_signer->RegisterRecoveryInterface(); shareman->RegisterRecoveryInterface(); + + RegisterValidationInterface(cl_signer.get()); } void ActiveContext::Stop() { + UnregisterValidationInterface(cl_signer.get()); + shareman->UnregisterRecoveryInterface(); is_signer->UnregisterRecoveryInterface(); cl_signer->UnregisterRecoveryInterface(); + cl_signer->Stop(); shareman->Stop(); qdkgsman->StopThreads(); qman_handler->Stop(); diff --git a/src/active/context.h b/src/active/context.h index 9b3d5896ec07..445a6d829f8a 100644 --- a/src/active/context.h +++ b/src/active/context.h @@ -29,13 +29,14 @@ class CTxMemPool; class GovernanceSigner; class PeerManager; namespace chainlock { +class Chainlocks; +class ChainlockHandler; class ChainLockSigner; } // namespace chainlock namespace instantsend { class InstantSendSigner; } // namespace instantsend namespace llmq { -class CChainLocksHandler; class CDKGDebugManager; class CDKGSessionManager; class CEHFSignalsHandler; @@ -53,7 +54,6 @@ struct DbWrapperParams; struct ActiveContext final : public CValidationInterface { private: - llmq::CChainLocksHandler& m_clhandler; llmq::CInstantSendManager& m_isman; llmq::CQuorumManager& m_qman; @@ -63,13 +63,13 @@ struct ActiveContext final : public CValidationInterface { ActiveContext& operator=(const ActiveContext&) = delete; explicit ActiveContext(CBLSWorker& bls_worker, ChainstateManager& chainman, CConnman& connman, CDeterministicMNManager& dmnman, CGovernanceManager& govman, CMasternodeMetaMan& mn_metaman, - CMNHFManager& mnhfman, CSporkManager& sporkman, CTxMemPool& mempool, - llmq::CChainLocksHandler& clhandler, llmq::CInstantSendManager& isman, - llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumManager& qman, - llmq::CQuorumSnapshotManager& qsnapman, llmq::CSigningManager& sigman, PeerManager& peerman, - const CMasternodeSync& mn_sync, const CBLSSecretKey& operator_sk, - const llmq::QvvecSyncModeMap& sync_map, const util::DbWrapperParams& db_params, - bool quorums_recovery, bool quorums_watch); + CMNHFManager& mnhfman, CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, + CTxMemPool& mempool, chainlock::ChainlockHandler& clhandler, + llmq::CInstantSendManager& isman, llmq::CQuorumBlockProcessor& qblockman, + llmq::CQuorumManager& qman, llmq::CQuorumSnapshotManager& qsnapman, + llmq::CSigningManager& sigman, PeerManager& peerman, const CMasternodeSync& mn_sync, + const CBLSSecretKey& operator_sk, const llmq::QvvecSyncModeMap& sync_map, + const util::DbWrapperParams& db_params, bool quorums_recovery, bool quorums_watch); ~ActiveContext(); void Interrupt(); diff --git a/src/bench/rpc_blockchain.cpp b/src/bench/rpc_blockchain.cpp index 9ed1fffb2f78..c60558009edd 100644 --- a/src/bench/rpc_blockchain.cpp +++ b/src/bench/rpc_blockchain.cpp @@ -45,7 +45,7 @@ static void BlockToJsonVerbose(benchmark::Bench& bench) TestBlockAndIndex data; const LLMQContext& llmq_ctx = *data.testing_setup->m_node.llmq_ctx; bench.run([&] { - auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *llmq_ctx.clhandler, *llmq_ctx.isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *data.testing_setup->m_node.chainlocks, *llmq_ctx.isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); ankerl::nanobench::doNotOptimizeAway(univalue); }); } @@ -56,7 +56,7 @@ static void BlockToJsonVerboseWrite(benchmark::Bench& bench) { TestBlockAndIndex data; const LLMQContext& llmq_ctx = *data.testing_setup->m_node.llmq_ctx; - auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *llmq_ctx.clhandler, *llmq_ctx.isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); + auto univalue = blockToJSON(data.testing_setup->m_node.chainman->m_blockman, data.block, &data.blockindex, &data.blockindex, *data.testing_setup->m_node.chainlocks, *llmq_ctx.isman, TxVerbosity::SHOW_DETAILS_AND_PREVOUT); bench.run([&] { auto str = univalue.write(); ankerl::nanobench::doNotOptimizeAway(str); diff --git a/src/chainlock/chainlock.cpp b/src/chainlock/chainlock.cpp index bbfb3c9f83af..b12b02879d99 100644 --- a/src/chainlock/chainlock.cpp +++ b/src/chainlock/chainlock.cpp @@ -1,386 +1,38 @@ -// Copyright (c) 2019-2025 The Dash Core developers +// Copyright (c) 2019-2026 The Dash Core developers // Distributed under the MIT/X11 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 -#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 +namespace chainlock { -using node::GetTransaction; - -namespace llmq { -namespace { -static constexpr auto CLEANUP_SEEN_TIMEOUT{24h}; -//! How long to wait for islocks until we consider a block with non-islocked TXs to be safe to sign -static constexpr auto WAIT_FOR_ISLOCK_TIMEOUT{10min}; -} // anonymous namespace - -bool AreChainLocksEnabled(const CSporkManager& sporkman) -{ - return sporkman.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED); -} - -CChainLocksHandler::CChainLocksHandler(CChainState& chainstate, CQuorumManager& _qman, CSporkManager& sporkman, - CTxMemPool& _mempool, const CMasternodeSync& mn_sync) : - m_chainstate{chainstate}, - qman{_qman}, - spork_manager{sporkman}, - mempool{_mempool}, - m_mn_sync{mn_sync}, - scheduler{std::make_unique()}, - scheduler_thread{ - std::make_unique(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))} -{ -} - -CChainLocksHandler::~CChainLocksHandler() -{ - scheduler->stop(); - scheduler_thread->join(); -} - -void CChainLocksHandler::Start(const llmq::CInstantSendManager& isman) +Chainlocks::Chainlocks(const CSporkManager& sporkman) : + m_sporks(sporkman) { - scheduler->scheduleEvery( - [&]() { - auto signer = m_signer.load(std::memory_order_acquire); - CheckActiveState(); - EnforceBestChainLock(); - Cleanup(); - // regularly retry signing the current chaintip as it might have failed before due to missing islocks - if (signer) { - signer->TrySignChainTip(isman); - } - }, - std::chrono::seconds{5}); } -void CChainLocksHandler::Stop() -{ - scheduler->stop(); -} +bool Chainlocks::IsEnabled() const { return m_sporks.IsSporkActive(SPORK_19_CHAINLOCKS_ENABLED); } -bool CChainLocksHandler::AlreadyHave(const CInv& inv) const -{ - LOCK(cs); - return seenChainLocks.count(inv.hash) != 0; -} +bool Chainlocks::IsSigningEnabled() const { return m_sporks.GetSporkValue(SPORK_19_CHAINLOCKS_ENABLED) == 0; } -bool CChainLocksHandler::GetChainLockByHash(const uint256& hash, chainlock::ChainLockSig& ret) const -{ - LOCK(cs); - - if (hash != bestChainLockHash) { - // we only propagate the best one and ditch all the old ones - return false; - } - - ret = bestChainLock; - return true; -} - -chainlock::ChainLockSig CChainLocksHandler::GetBestChainLock() const +chainlock::ChainLockSig Chainlocks::GetBestChainLock() const { LOCK(cs); return bestChainLock; } -void CChainLocksHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) -{ - AssertLockNotHeld(cs); - LOCK(cs); - for (const auto& txid : tx) { - txFirstSeenTime.emplace(txid, time); - } -} - -MessageProcessingResult CChainLocksHandler::ProcessNewChainLock(const NodeId from, const chainlock::ChainLockSig& clsig, - const uint256& hash) -{ - CheckActiveState(); - - { - LOCK(cs); - if (!seenChainLocks.emplace(hash, GetTime()).second) { - return {}; - } - - if (!bestChainLock.IsNull() && clsig.getHeight() <= bestChainLock.getHeight()) { - // no need to process older/same CLSIGs - return {}; - } - } - - if (const auto ret = VerifyChainLock(clsig); ret != VerifyRecSigStatus::Valid) { - LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__, - clsig.ToString(), ToUnderlying(ret), from); - if (from != -1) { - return MisbehavingError{10}; - } - return {}; - } - - const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainstate.m_blockman.LookupBlockIndex(clsig.getBlockHash())); - const CInv clsig_inv(MSG_CLSIG, hash); - - { - LOCK(cs); - // newer chainlock could be processed via another thread while we were not holding the lock, re-verify - if (!bestChainLock.IsNull() && clsig.getHeight() <= bestChainLock.getHeight()) { - // no need to process older/same CLSIGs - return {}; - } - bestChainLockHash = hash; - bestChainLock = clsig; - - if (pindex) { - if (pindex->nHeight != clsig.getHeight()) { - // Should not happen, same as the conflict check from above. - LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n", - __func__, clsig.ToString(), pindex->nHeight); - // Note: not relaying clsig here - return {}; - } - bestChainLockWithKnownBlock = bestChainLock; - bestChainLockBlockIndex = pindex; - } else { - // We don't know the block/header for this CLSIG yet, so bail out for now and when the - // block/header later comes in, we will enforce the correct chain. We still relay further. - return clsig_inv; - } - } - - scheduler->scheduleFromNow( - [&]() { - CheckActiveState(); - EnforceBestChainLock(); - }, - std::chrono::seconds{0}); - - LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__, - clsig.ToString(), from); - - return clsig_inv; -} - -void CChainLocksHandler::AcceptedBlockHeader(gsl::not_null pindexNew) +int32_t Chainlocks::GetBestChainLockHeight() const { LOCK(cs); - - if (pindexNew->GetBlockHash() == bestChainLock.getBlockHash()) { - LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- block header %s came in late, updating and enforcing\n", - __func__, pindexNew->GetBlockHash().ToString()); - - if (bestChainLock.getHeight() != pindexNew->nHeight) { - // Should not happen, same as the conflict check from ProcessNewChainLock. - LogPrintf("CChainLocksHandler::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n", - __func__, bestChainLock.ToString(), pindexNew->nHeight); - return; - } - - // when EnforceBestChainLock is called later, it might end up invalidating other chains but not activating the - // CLSIG locked chain. This happens when only the header is known but the block is still missing yet. The usual - // block processing logic will handle this when the block arrives - bestChainLockWithKnownBlock = bestChainLock; - bestChainLockBlockIndex = pindexNew; - } -} - -void CChainLocksHandler::UpdatedBlockTip(const llmq::CInstantSendManager& isman) -{ - // don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that cs_main is - // never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to - // EnforceBestChainLock switching chains. - // atomic[If tryLockChainTipScheduled is false, do (set it to true] and schedule signing). - if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) { - scheduler->scheduleFromNow( - [&]() { - auto signer = m_signer.load(std::memory_order_acquire); - CheckActiveState(); - EnforceBestChainLock(); - Cleanup(); - if (signer) { - signer->TrySignChainTip(isman); - } - tryLockChainTipScheduled = false; - }, - std::chrono::seconds{0}); - } -} - -void CChainLocksHandler::CheckActiveState() -{ - bool oldIsEnabled = isEnabled; - isEnabled = AreChainLocksEnabled(spork_manager); - - if (!oldIsEnabled && isEnabled) { - // ChainLocks got activated just recently, but it's possible that it was already running before, leaving - // us with some stale values which we should not try to enforce anymore (there probably was a good reason - // to disable spork19) - LOCK(cs); - bestChainLockHash = uint256(); - bestChainLock = bestChainLockWithKnownBlock = chainlock::ChainLockSig(); - bestChainLockBlockIndex = lastNotifyChainLockBlockIndex = nullptr; - } -} - -void CChainLocksHandler::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) -{ - if (tx->IsCoinBase() || tx->vin.empty()) { - return; - } - - LOCK(cs); - txFirstSeenTime.emplace(tx->GetHash(), nAcceptTime); -} - -void CChainLocksHandler::BlockConnected(const std::shared_ptr& pblock, gsl::not_null pindex) -{ - if (!m_mn_sync.IsBlockchainSynced()) { - return; - } - - // We listen for BlockConnected so that we can collect all TX ids of all included TXs of newly received blocks - int64_t curTime = GetTime().count(); - { - LOCK(cs); - for (const auto& tx : pblock->vtx) { - if (!tx->IsCoinBase() && !tx->vin.empty()) { - txFirstSeenTime.emplace(tx->GetHash(), curTime); - } - } - } - - // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are safe. - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->UpdateBlockHashTxidMap(pindex->GetBlockHash(), pblock->vtx); - } -} - -void CChainLocksHandler::BlockDisconnected(const std::shared_ptr& pblock, - gsl::not_null pindexDisconnected) -{ - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - signer->EraseFromBlockHashTxidMap(pindexDisconnected->GetBlockHash()); - } -} - -int32_t CChainLocksHandler::GetBestChainLockHeight() const -{ - AssertLockNotHeld(cs); - LOCK(cs); return bestChainLock.getHeight(); } -bool CChainLocksHandler::IsTxSafeForMining(const uint256& txid) const +bool Chainlocks::HasChainLock(int nHeight, const uint256& blockHash) const { - auto tx_age{0s}; - { - LOCK(cs); - auto it = txFirstSeenTime.find(txid); - if (it != txFirstSeenTime.end()) { - tx_age = GetTime() - it->second; - } - } - - return tx_age >= WAIT_FOR_ISLOCK_TIMEOUT; -} - -// WARNING: cs_main and cs should not be held! -// This should also not be called from validation signals, as this might result in recursive calls -void CChainLocksHandler::EnforceBestChainLock() -{ - AssertLockNotHeld(cs); - AssertLockNotHeld(cs_main); - - std::shared_ptr clsig; - const CBlockIndex* pindex; - const CBlockIndex* currentBestChainLockBlockIndex; - { - LOCK(cs); - - if (!IsEnabled()) { - return; - } - - clsig = std::make_shared(bestChainLockWithKnownBlock); - pindex = currentBestChainLockBlockIndex = this->bestChainLockBlockIndex; - - if (currentBestChainLockBlockIndex == nullptr) { - // we don't have the header/block, so we can't do anything right now - return; - } - } - - BlockValidationState dummy_state; - - // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain. - // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig - // and mark all of them as conflicting. - LogPrint(BCLog::CHAINLOCKS, "CChainLocksHandler::%s -- enforcing block %s via CLSIG (%s)\n", __func__, - pindex->GetBlockHash().ToString(), clsig->ToString()); - m_chainstate.EnforceBlock(dummy_state, pindex); - - - if (/*activateNeeded =*/WITH_LOCK(::cs_main, return m_chainstate.m_chain.Tip()->GetAncestor( - currentBestChainLockBlockIndex->nHeight)) != - currentBestChainLockBlockIndex) { - if (!m_chainstate.ActivateBestChain(dummy_state)) { - LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, dummy_state.ToString()); - return; - } - LOCK(::cs_main); - if (m_chainstate.m_chain.Tip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != - currentBestChainLockBlockIndex) { - return; - } - } - - { - LOCK(cs); - if (lastNotifyChainLockBlockIndex == currentBestChainLockBlockIndex) return; - lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex; - } - - GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, clsig, clsig->ToString()); - uiInterface.NotifyChainLock(clsig->getBlockHash().ToString(), clsig->getHeight()); - ::g_stats_client->gauge("chainlocks.blockHeight", clsig->getHeight(), 1.0f); -} - -VerifyRecSigStatus CChainLocksHandler::VerifyChainLock(const chainlock::ChainLockSig& clsig) const -{ - const auto llmqType = Params().GetConsensus().llmqTypeChainLocks; - const uint256 nRequestId = chainlock::GenSigRequestId(clsig.getHeight()); - - return llmq::VerifyRecoveredSig(llmqType, m_chainstate.m_chain, qman, clsig.getHeight(), nRequestId, - clsig.getBlockHash(), clsig.getSig()); -} - -bool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash) const -{ - AssertLockNotHeld(cs); LOCK(cs); if (!IsEnabled()) { @@ -403,9 +55,8 @@ bool CChainLocksHandler::HasChainLock(int nHeight, const uint256& blockHash) con return (pAncestor != nullptr) && pAncestor->GetBlockHash() == blockHash; } -bool CChainLocksHandler::HasConflictingChainLock(int nHeight, const uint256& blockHash) const +bool Chainlocks::HasConflictingChainLock(int nHeight, const uint256& blockHash) const { - AssertLockNotHeld(cs); LOCK(cs); if (!IsEnabled()) { @@ -429,58 +80,80 @@ bool CChainLocksHandler::HasConflictingChainLock(int nHeight, const uint256& blo return pAncestor->GetBlockHash() != blockHash; } -void CChainLocksHandler::Cleanup() +void Chainlocks::ResetChainlock() { - constexpr auto CLEANUP_INTERVAL{30s}; - if (!m_mn_sync.IsBlockchainSynced()) { - return; - } + LOCK(cs); + bestChainLockHash = uint256{}; + bestChainLock = bestChainLockWithKnownBlock = chainlock::ChainLockSig{}; + bestChainLockBlockIndex = nullptr; +} - if (!cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) { - return; +bool Chainlocks::UpdateBestChainlock(const uint256& hash, const chainlock::ChainLockSig& clsig, const CBlockIndex* pindex) +{ + LOCK(cs); + + if (clsig.getHeight() <= bestChainLock.getHeight()) { + // no need to process older/same CLSIGs + return false; } - { - LOCK(cs); - for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) { - if (GetTime() - it->second >= CLEANUP_SEEN_TIMEOUT) { - it = seenChainLocks.erase(it); - } else { - ++it; - } + bestChainLockHash = hash; + bestChainLock = clsig; + + if (pindex) { + if (pindex->nHeight != clsig.getHeight()) { + // Should not happen, same as the conflict check from above. + LogPrintf("Chainlocks::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n", + __func__, clsig.ToString(), pindex->nHeight); + // Note: not relaying clsig here + return false; } + bestChainLockWithKnownBlock = clsig; + bestChainLockBlockIndex = pindex; } + // We don't know the block/header for this CLSIG yet, so bail out for now and when the + // block/header later comes in, we will enforce the correct chain. We still relay further. + return true; +} - if (auto signer = m_signer.load(std::memory_order_acquire); signer) { - const auto cleanup_txes{signer->Cleanup()}; - LOCK(cs); - for (const auto& tx : cleanup_txes) { - for (const auto& txid : *tx) { - txFirstSeenTime.erase(txid); - } - } +std::pair Chainlocks::GetBestChainlockWithPindex() const +{ + LOCK(cs); + return {bestChainLockWithKnownBlock, bestChainLockBlockIndex}; +} + +bool Chainlocks::GetChainLockByHash(const uint256& hash, chainlock::ChainLockSig& ret) const +{ + LOCK(cs); + + if (bestChainLockHash != hash) { + // we only propagate the best one and ditch all the old ones + return false; } + ret = bestChainLock; + return true; +} + +void Chainlocks::AcceptedBlockHeader(gsl::not_null pindexNew) +{ + LOCK(cs); + + if (pindexNew->GetBlockHash() == bestChainLock.getBlockHash()) { + LogPrint(BCLog::CHAINLOCKS, "Chainlocks::%s -- block header %s came in late, updating and enforcing\n", + __func__, pindexNew->GetBlockHash().ToString()); - LOCK(::cs_main); - LOCK2(mempool.cs, cs); // need mempool.cs due to GetTransaction calls - for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end();) { - uint256 hashBlock; - if (auto tx = GetTransaction(nullptr, &mempool, it->first, Params().GetConsensus(), hashBlock); !tx) { - // tx has vanished, probably due to conflicts - it = txFirstSeenTime.erase(it); - } else if (!hashBlock.IsNull()) { - const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(hashBlock); - assert(pindex); // GetTransaction gave us that hashBlock, it should resolve to a valid block index - if (m_chainstate.m_chain.Tip()->GetAncestor(pindex->nHeight) == pindex && - m_chainstate.m_chain.Height() - pindex->nHeight > chainlock::TX_CONFIRM_THRESHOLD) { - // tx is sufficiently deep, we can stop tracking it - it = txFirstSeenTime.erase(it); - } else { - ++it; - } - } else { - ++it; + if (bestChainLock.getHeight() != pindexNew->nHeight) { + // Should not happen, same as the conflict check from ProcessNewChainLock. + LogPrintf("Chainlocks::%s -- height of CLSIG (%s) does not match the specified block's height (%d)\n", + __func__, bestChainLock.ToString(), pindexNew->nHeight); + return; } + + // when EnforceBestChainLock is called later, it might end up invalidating other chains but not activating the + // CLSIG locked chain. This happens when only the header is known but the block is still missing yet. The usual + // block processing logic will handle this when the block arrives + bestChainLockWithKnownBlock = bestChainLock; + bestChainLockBlockIndex = pindexNew; } } -} // namespace llmq +} // namespace chainlock diff --git a/src/chainlock/chainlock.h b/src/chainlock/chainlock.h index 0a4ae9822089..e6d34ad9aa60 100644 --- a/src/chainlock/chainlock.h +++ b/src/chainlock/chainlock.h @@ -5,134 +5,59 @@ #ifndef BITCOIN_CHAINLOCK_CHAINLOCK_H #define BITCOIN_CHAINLOCK_CHAINLOCK_H +#include #include - -#include -#include -#include -#include -#include -#include - -#include - #include +#include +#include -#include -#include -#include -#include -#include -#include -#include - -class CBlock; -class CBlockIndex; -class CChainState; -class CMasternodeSync; -class CScheduler; class CSporkManager; -class CTxMemPool; -namespace llmq { -class CInstantSendManager; -class CQuorumManager; -class CSigningManager; -enum class VerifyRecSigStatus : uint8_t; +namespace chainlock { + +//! Depth of block including transactions before it's considered safe +static constexpr int32_t TX_CONFIRM_THRESHOLD{5}; -class CChainLocksHandler final : public chainlock::ChainLockSignerParent +class Chainlocks { private: - CChainState& m_chainstate; - CQuorumManager& qman; - CSporkManager& spork_manager; - CTxMemPool& mempool; - const CMasternodeSync& m_mn_sync; - std::unique_ptr scheduler; - std::unique_ptr scheduler_thread; - - std::atomic m_signer{nullptr}; + const CSporkManager& m_sporks; mutable Mutex cs; - std::atomic tryLockChainTipScheduled{false}; - std::atomic isEnabled{false}; + const CBlockIndex* bestChainLockBlockIndex GUARDED_BY(cs){nullptr}; uint256 bestChainLockHash GUARDED_BY(cs); chainlock::ChainLockSig bestChainLock GUARDED_BY(cs); chainlock::ChainLockSig bestChainLockWithKnownBlock GUARDED_BY(cs); - const CBlockIndex* bestChainLockBlockIndex GUARDED_BY(cs){nullptr}; - const CBlockIndex* lastNotifyChainLockBlockIndex GUARDED_BY(cs){nullptr}; - Uint256HashMap txFirstSeenTime GUARDED_BY(cs); +public: + Chainlocks(const CSporkManager& sporkman); - std::map seenChainLocks GUARDED_BY(cs); + [[nodiscard]] bool IsEnabled() const; + [[nodiscard]] bool IsSigningEnabled() const; - CleanupThrottler cleanupThrottler; + [[nodiscard]] chainlock::ChainLockSig GetBestChainLock() const EXCLUSIVE_LOCKS_REQUIRED(!cs); -public: - CChainLocksHandler() = delete; - CChainLocksHandler(const CChainLocksHandler&) = delete; - CChainLocksHandler& operator=(const CChainLocksHandler&) = delete; - explicit CChainLocksHandler(CChainState& chainstate, CQuorumManager& _qman, CSporkManager& sporkman, - CTxMemPool& _mempool, const CMasternodeSync& mn_sync); - ~CChainLocksHandler(); - - 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); } - - void Start(const llmq::CInstantSendManager& isman); - void Stop(); - - bool AlreadyHave(const CInv& inv) const - EXCLUSIVE_LOCKS_REQUIRED(!cs); - bool GetChainLockByHash(const uint256& hash, chainlock::ChainLockSig& ret) const - EXCLUSIVE_LOCKS_REQUIRED(!cs); - chainlock::ChainLockSig GetBestChainLock() const - EXCLUSIVE_LOCKS_REQUIRED(!cs); - void UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) override EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] int32_t GetBestChainLockHeight() const EXCLUSIVE_LOCKS_REQUIRED(!cs); - [[nodiscard]] MessageProcessingResult ProcessNewChainLock(NodeId from, const chainlock::ChainLockSig& clsig, - const uint256& hash) override - EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] bool HasChainLock(int nHeight, const uint256& blockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs); - void AcceptedBlockHeader(gsl::not_null pindexNew) - EXCLUSIVE_LOCKS_REQUIRED(!cs); - void UpdatedBlockTip(const llmq::CInstantSendManager& isman); - void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime) - EXCLUSIVE_LOCKS_REQUIRED(!cs); - void BlockConnected(const std::shared_ptr& pblock, gsl::not_null pindex) - EXCLUSIVE_LOCKS_REQUIRED(!cs); - void BlockDisconnected(const std::shared_ptr& pblock, gsl::not_null pindexDisconnected) - EXCLUSIVE_LOCKS_REQUIRED(!cs); - void CheckActiveState() - EXCLUSIVE_LOCKS_REQUIRED(!cs); - void EnforceBestChainLock() - EXCLUSIVE_LOCKS_REQUIRED(!cs); + [[nodiscard]] bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const EXCLUSIVE_LOCKS_REQUIRED(!cs); - bool HasChainLock(int nHeight, const uint256& blockHash) const override + bool UpdateBestChainlock(const uint256& hash, const chainlock::ChainLockSig& clsig, const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs); - bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const override - EXCLUSIVE_LOCKS_REQUIRED(!cs); - VerifyRecSigStatus VerifyChainLock(const chainlock::ChainLockSig& clsig) const; - [[nodiscard]] int32_t GetBestChainLockHeight() const override - EXCLUSIVE_LOCKS_REQUIRED(!cs); - bool IsTxSafeForMining(const uint256& txid) const override + std::pair GetBestChainlockWithPindex() const EXCLUSIVE_LOCKS_REQUIRED(!cs); - [[nodiscard]] bool IsEnabled() const override { return isEnabled; } -private: - void Cleanup() - EXCLUSIVE_LOCKS_REQUIRED(!cs); + bool GetChainLockByHash(const uint256& hash, chainlock::ChainLockSig& ret) const EXCLUSIVE_LOCKS_REQUIRED(!cs); + + void AcceptedBlockHeader(gsl::not_null pindexNew) EXCLUSIVE_LOCKS_REQUIRED(!cs); + + void ResetChainlock() EXCLUSIVE_LOCKS_REQUIRED(!cs); }; -bool AreChainLocksEnabled(const CSporkManager& sporkman); -} // namespace llmq +} // namespace chainlock #endif // BITCOIN_CHAINLOCK_CHAINLOCK_H diff --git a/src/chainlock/handler.cpp b/src/chainlock/handler.cpp new file mode 100644 index 000000000000..d9addc2e70b2 --- /dev/null +++ b/src/chainlock/handler.cpp @@ -0,0 +1,335 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT/X11 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 +#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 { +static constexpr auto CLEANUP_SEEN_TIMEOUT{24h}; +//! How long to wait for islocks until we consider a block with non-islocked TXs to be safe to sign +static constexpr auto WAIT_FOR_ISLOCK_TIMEOUT{10min}; +} // anonymous namespace + +namespace chainlock { +ChainlockHandler::ChainlockHandler(chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, CTxMemPool& _mempool, + const CMasternodeSync& mn_sync) : + m_chainlocks{chainlocks}, + m_chainman{chainman}, + mempool{_mempool}, + m_mn_sync{mn_sync}, + scheduler{std::make_unique()}, + scheduler_thread{ + std::make_unique(std::thread(util::TraceThread, "cl-schdlr", [&] { scheduler->serviceQueue(); }))} +{ +} + +ChainlockHandler::~ChainlockHandler() +{ + scheduler->stop(); + scheduler_thread->join(); +} + +void ChainlockHandler::Start() +{ + scheduler->scheduleEvery( + [&]() { + CheckActiveState(); + EnforceBestChainLock(); + Cleanup(); + }, + std::chrono::seconds{5}); +} + +void ChainlockHandler::Stop() { scheduler->stop(); } + +bool ChainlockHandler::AlreadyHave(const CInv& inv) const +{ + LOCK(cs); + return seenChainLocks.count(inv.hash) != 0; +} + +void ChainlockHandler::UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) +{ + AssertLockNotHeld(cs); + LOCK(cs); + for (const auto& txid : tx) { + txFirstSeenTime.emplace(txid, time); + } +} + +MessageProcessingResult ChainlockHandler::ProcessNewChainLock(const NodeId from, const chainlock::ChainLockSig& clsig, + const llmq::CQuorumManager& qman, const uint256& hash) +{ + CheckActiveState(); + + { + LOCK(cs); + if (!seenChainLocks.emplace(hash, GetTime()).second) { + return {}; + } + + // height is expect to check twice: preliminary (for optimization) and inside UpdateBestsChainlock (as mutex is not kept during validation) + if (clsig.getHeight() <= m_chainlocks.GetBestChainLockHeight()) { + // no need to process older/same CLSIGs + return {}; + } + } + + if (const auto ret = chainlock::VerifyChainLock(Params().GetConsensus(), m_chainman.ActiveChain(), qman, clsig); + ret != llmq::VerifyRecSigStatus::Valid) { + LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- invalid CLSIG (%s), status=%d peer=%d\n", __func__, + clsig.ToString(), ToUnderlying(ret), from); + if (from != -1) { + return MisbehavingError{10}; + } + return {}; + } + + const CBlockIndex* pindex = WITH_LOCK(::cs_main, return m_chainman.m_blockman.LookupBlockIndex(clsig.getBlockHash())); + + if (!m_chainlocks.UpdateBestChainlock(hash, clsig, pindex)) return {}; + + if (pindex) { + scheduler->scheduleFromNow( + [&]() { + CheckActiveState(); + EnforceBestChainLock(); + }, + std::chrono::seconds{0}); + + LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- processed new CLSIG (%s), peer=%d\n", __func__, + clsig.ToString(), from); + } + const CInv clsig_inv(MSG_CLSIG, hash); + return clsig_inv; +} + +void ChainlockHandler::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) +{ + if (pindexNew == pindexFork) // blocks were disconnected without any new ones + return; + if (fInitialDownload) return; + + // TODO: reconsider removing scheduler from here, because UpdatedBlockTip is already async call from notification + // scheduler don't call TrySignChainTip directly but instead let the scheduler call it. This way we ensure that + // cs_main is never locked and TrySignChainTip is not called twice in parallel. Also avoids recursive calls due to + // EnforceBestChainLock switching chains. + // atomic[If tryLockChainTipScheduled is false, do (set it to true] and schedule signing). + + if (bool expected = false; tryLockChainTipScheduled.compare_exchange_strong(expected, true)) { + scheduler->scheduleFromNow( + [&]() { + CheckActiveState(); + EnforceBestChainLock(); + Cleanup(); + tryLockChainTipScheduled = false; + }, + std::chrono::seconds{0}); + } +} + +void ChainlockHandler::CheckActiveState() +{ + bool oldIsEnabled = isEnabled; + isEnabled = m_chainlocks.IsEnabled(); + + if (!oldIsEnabled && isEnabled) { + // ChainLocks got activated just recently, but it's possible that it was already running before, leaving + // us with some stale values which we should not try to enforce anymore (there probably was a good reason + // to disable spork19) + LOCK(cs); + m_chainlocks.ResetChainlock(); + lastNotifyChainLockBlockIndex = nullptr; + } +} + +void ChainlockHandler::TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t) +{ + if (tx->IsCoinBase() || tx->vin.empty()) { + return; + } + + LOCK(cs); + txFirstSeenTime.emplace(tx->GetHash(), nAcceptTime); +} + +void ChainlockHandler::AcceptedBlockHeader(const CBlockIndex* pindexNew) +{ + m_chainlocks.AcceptedBlockHeader(pindexNew); +} + +void ChainlockHandler::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) +{ + if (!m_mn_sync.IsBlockchainSynced()) { + return; + } + + // We listen for BlockConnected so that we can collect all TX ids of all included TXs of newly received blocks + int64_t curTime = GetTime().count(); + { + LOCK(cs); + for (const auto& tx : pblock->vtx) { + if (!tx->IsCoinBase() && !tx->vin.empty()) { + txFirstSeenTime.emplace(tx->GetHash(), curTime); + } + } + } +} + +bool ChainlockHandler::IsTxSafeForMining(const uint256& txid) const +{ + auto tx_age{0s}; + { + LOCK(cs); + auto it = txFirstSeenTime.find(txid); + if (it != txFirstSeenTime.end()) { + tx_age = GetTime() - it->second; + } + } + + return tx_age >= WAIT_FOR_ISLOCK_TIMEOUT; +} + +// WARNING: cs_main and cs should not be held! +// This should also not be called from validation signals, as this might result in recursive calls +void ChainlockHandler::EnforceBestChainLock() +{ + if (!isEnabled) { + return; + } + + AssertLockNotHeld(cs); + AssertLockNotHeld(cs_main); + + + auto [clsig, currentBestChainLockBlockIndex] = m_chainlocks.GetBestChainlockWithPindex(); + if (currentBestChainLockBlockIndex == nullptr) { + // we don't have the header/block, so we can't do anything right now + return; + } + + BlockValidationState dummy_state; + + // Go backwards through the chain referenced by clsig until we find a block that is part of the main chain. + // For each of these blocks, check if there are children that are NOT part of the chain referenced by clsig + // and mark all of them as conflicting. + LogPrint(BCLog::CHAINLOCKS, "ChainlockHandler::%s -- enforcing block %s via CLSIG (%s)\n", __func__, + currentBestChainLockBlockIndex->GetBlockHash().ToString(), clsig.ToString()); + m_chainman.ActiveChainstate().EnforceBlock(dummy_state, currentBestChainLockBlockIndex); + + + if (/*activateNeeded =*/WITH_LOCK(::cs_main, return m_chainman.ActiveTip()->GetAncestor( + currentBestChainLockBlockIndex->nHeight)) != + currentBestChainLockBlockIndex) { + if (!m_chainman.ActiveChainstate().ActivateBestChain(dummy_state)) { + LogPrintf("ChainlockHandler::%s -- ActivateBestChain failed: %s\n", __func__, dummy_state.ToString()); + return; + } + LOCK(::cs_main); + if (m_chainman.ActiveTip()->GetAncestor(currentBestChainLockBlockIndex->nHeight) != currentBestChainLockBlockIndex) { + return; + } + } + + { + LOCK(cs); + if (lastNotifyChainLockBlockIndex == currentBestChainLockBlockIndex) return; + lastNotifyChainLockBlockIndex = currentBestChainLockBlockIndex; + } + + GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, std::make_shared(clsig), + clsig.ToString()); + uiInterface.NotifyChainLock(clsig.getBlockHash().ToString(), clsig.getHeight()); + ::g_stats_client->gauge("chainlocks.blockHeight", clsig.getHeight(), 1.0f); +} + +void ChainlockHandler::CleanupFromSigner(const std::vector>& cleanup_txes) +{ + LOCK(cs); + for (const auto& tx : cleanup_txes) { + for (const uint256& txid : *tx) { + txFirstSeenTime.erase(txid); + } + } +} + +void ChainlockHandler::Cleanup() +{ + constexpr auto CLEANUP_INTERVAL{30s}; + if (!m_mn_sync.IsBlockchainSynced()) { + return; + } + + if (!cleanupThrottler.TryCleanup(CLEANUP_INTERVAL)) { + return; + } + + { + LOCK(cs); + for (auto it = seenChainLocks.begin(); it != seenChainLocks.end();) { + if (GetTime() - it->second >= CLEANUP_SEEN_TIMEOUT) { + it = seenChainLocks.erase(it); + } else { + ++it; + } + } + } + + LOCK(::cs_main); + LOCK2(mempool.cs, cs); // need mempool.cs due to GetTransaction calls + for (auto it = txFirstSeenTime.begin(); it != txFirstSeenTime.end();) { + uint256 hashBlock; + if (auto tx = GetTransaction(nullptr, &mempool, it->first, Params().GetConsensus(), hashBlock); !tx) { + // tx has vanished, probably due to conflicts + it = txFirstSeenTime.erase(it); + } else if (!hashBlock.IsNull()) { + const auto* pindex = m_chainman.m_blockman.LookupBlockIndex(hashBlock); + assert(pindex); // GetTransaction gave us that hashBlock, it should resolve to a valid block index + if (m_chainman.ActiveTip()->GetAncestor(pindex->nHeight) == pindex && + m_chainman.ActiveChain().Height() - pindex->nHeight > chainlock::TX_CONFIRM_THRESHOLD) { + // tx is sufficiently deep, we can stop tracking it + it = txFirstSeenTime.erase(it); + } else { + ++it; + } + } else { + ++it; + } + } +} + +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 new file mode 100644 index 000000000000..e024ca539876 --- /dev/null +++ b/src/chainlock/handler.h @@ -0,0 +1,115 @@ +// Copyright (c) 2019-2025 The Dash Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_CHAINLOCK_HANDLER_H +#define BITCOIN_CHAINLOCK_HANDLER_H + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +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 { +class Chainlocks; +struct ChainLockSig; + +class ChainlockHandler final : public CValidationInterface +{ +private: + chainlock::Chainlocks& m_chainlocks; + + ChainstateManager& m_chainman; + CTxMemPool& mempool; + const CMasternodeSync& m_mn_sync; + std::unique_ptr scheduler; + std::unique_ptr scheduler_thread; + + mutable Mutex cs; + std::atomic tryLockChainTipScheduled{false}; + std::atomic isEnabled{false}; + + const CBlockIndex* lastNotifyChainLockBlockIndex GUARDED_BY(cs){nullptr}; + Uint256HashMap txFirstSeenTime GUARDED_BY(cs); + + std::map seenChainLocks GUARDED_BY(cs); + + CleanupThrottler cleanupThrottler; + +public: + ChainlockHandler() = delete; + ChainlockHandler(const ChainlockHandler&) = delete; + ChainlockHandler& operator=(const ChainlockHandler&) = delete; + explicit ChainlockHandler(chainlock::Chainlocks& chainlocks, ChainstateManager& chainman, CTxMemPool& _mempool, + const CMasternodeSync& mn_sync); + ~ChainlockHandler(); + + void Start(); + void Stop(); + + bool AlreadyHave(const CInv& inv) const EXCLUSIVE_LOCKS_REQUIRED(!cs); + void UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) EXCLUSIVE_LOCKS_REQUIRED(!cs); + + [[nodiscard]] MessageProcessingResult ProcessNewChainLock(NodeId from, const chainlock::ChainLockSig& clsig, + const llmq::CQuorumManager& qman, + + const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(!cs); + +public: + void CheckActiveState() EXCLUSIVE_LOCKS_REQUIRED(!cs); + void EnforceBestChainLock() EXCLUSIVE_LOCKS_REQUIRED(!cs); + + bool IsTxSafeForMining(const uint256& txid) const EXCLUSIVE_LOCKS_REQUIRED(!cs); + + void CleanupFromSigner(const std::vector>& cleanup_txes) EXCLUSIVE_LOCKS_REQUIRED(!cs); + +protected: + // CValidationInterface + void AcceptedBlockHeader(const CBlockIndex* pindexNew) override EXCLUSIVE_LOCKS_REQUIRED(!cs); + void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override + EXCLUSIVE_LOCKS_REQUIRED(!cs); + void TransactionAddedToMempool(const CTransactionRef& tx, int64_t nAcceptTime, uint64_t mempool_sequence) override + EXCLUSIVE_LOCKS_REQUIRED(!cs); + + void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) override + EXCLUSIVE_LOCKS_REQUIRED(!cs); + +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 diff --git a/src/chainlock/signing.cpp b/src/chainlock/signing.cpp index 0eba67843312..8f651624d695 100644 --- a/src/chainlock/signing.cpp +++ b/src/chainlock/signing.cpp @@ -4,31 +4,59 @@ #include -#include -#include - #include +#include #include #include #include -#include +#include +#include +#include +#include +#include + +#include using node::ReadBlockFromDisk; namespace chainlock { -ChainLockSigner::ChainLockSigner(CChainState& chainstate, ChainLockSignerParent& clhandler, - llmq::CSigningManager& sigman, llmq::CSigSharesManager& shareman, - CSporkManager& sporkman, const CMasternodeSync& mn_sync) : +ChainLockSigner::ChainLockSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks, + ChainlockHandler& clhandler, const llmq::CInstantSendManager& isman, + const llmq::CQuorumManager& qman, llmq::CSigningManager& sigman, + llmq::CSigSharesManager& shareman, const CMasternodeSync& mn_sync) : m_chainstate{chainstate}, + m_chainlocks{chainlocks}, m_clhandler{clhandler}, + m_isman{isman}, + m_qman{qman}, m_sigman{sigman}, m_shareman{shareman}, - m_sporkman{sporkman}, - m_mn_sync{mn_sync} + m_mn_sync{mn_sync}, + m_scheduler{std::make_unique()}, + m_scheduler_thread{ + std::make_unique(std::thread(util::TraceThread, "cls-schdlr", [&] { m_scheduler->serviceQueue(); }))} +{ +} + +ChainLockSigner::~ChainLockSigner() { Stop(); } + +void ChainLockSigner::Start() { + m_scheduler->scheduleEvery( + [&]() { + if (!m_chainlocks.IsSigningEnabled()) return; + // regularly retry signing the current chaintip as it might have failed before due to missing islocks + TrySignChainTip(); + Cleanup(); + }, + std::chrono::seconds{5}); } -ChainLockSigner::~ChainLockSigner() = default; +void ChainLockSigner::Stop() +{ + m_scheduler->stop(); + if (m_scheduler_thread->joinable()) m_scheduler_thread->join(); +} void ChainLockSigner::RegisterRecoveryInterface() { @@ -40,18 +68,18 @@ void ChainLockSigner::UnregisterRecoveryInterface() m_sigman.UnregisterRecoveredSigsListener(this); } -void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) +void ChainLockSigner::UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) { - if (!m_mn_sync.IsBlockchainSynced()) { - return; - } + TrySignChainTip(); +} - if (!m_clhandler.IsEnabled()) { +void ChainLockSigner::TrySignChainTip() +{ + if (!m_mn_sync.IsBlockchainSynced()) { return; } - if (m_sporkman.GetSporkValue(SPORK_19_CHAINLOCKS_ENABLED) != 0) { - // ChainLocks signing not enabled + if (!m_chainlocks.IsEnabled() || !m_chainlocks.IsSigningEnabled()) { return; } @@ -75,12 +103,12 @@ void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) } } - if (m_clhandler.GetBestChainLockHeight() >= pindex->nHeight) { + if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) { // already got the same CLSIG or a better one return; } - if (m_clhandler.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { + if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { // don't sign if another conflicting CLSIG is already present. EnforceBestChainLock will later enforce // the correct chain. return; @@ -93,7 +121,7 @@ void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) // considered safe when it is islocked or at least known since 10 minutes (from mempool or block). These checks are // performed for the tip (which we try to sign) and the previous 5 blocks. If a ChainLocked block is found on the // way down, we consider all TXs to be safe. - if (isman.IsInstantSendEnabled() && isman.RejectConflictingBlocks()) { + if (m_isman.IsInstantSendEnabled() && m_isman.RejectConflictingBlocks()) { const auto* pindexWalk = pindex; while (pindexWalk != nullptr) { if (pindex->nHeight - pindexWalk->nHeight > TX_CONFIRM_THRESHOLD) { @@ -102,7 +130,7 @@ void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) LogPrint(BCLog::CHAINLOCKS, "%s -- tip and previous %d blocks all safe\n", __func__, TX_CONFIRM_THRESHOLD); break; } - if (m_clhandler.HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) { + if (m_chainlocks.HasChainLock(pindexWalk->nHeight, pindexWalk->GetBlockHash())) { // we don't care about islocks for TXs that are ChainLocked already LogPrint(BCLog::CHAINLOCKS, "%s -- chainlock at height %d\n", __func__, pindexWalk->nHeight); break; @@ -115,7 +143,7 @@ void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) } for (const auto& txid : *txids) { - if (!m_clhandler.IsTxSafeForMining(txid) && !isman.IsLocked(txid)) { + if (!m_clhandler.IsTxSafeForMining(txid) && !m_isman.IsLocked(txid)) { LogPrint(BCLog::CHAINLOCKS, /* Continued */ "%s -- not signing block %s due to TX %s not being islocked and not old enough.\n", __func__, pindexWalk->GetBlockHash().ToString(), txid.ToString()); @@ -130,7 +158,7 @@ void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) uint256 requestId = GenSigRequestId(pindex->nHeight); uint256 msgHash = pindex->GetBlockHash(); - if (m_clhandler.GetBestChainLockHeight() >= pindex->nHeight) { + if (m_chainlocks.GetBestChainLockHeight() >= pindex->nHeight) { // might have happened while we didn't hold cs return; } @@ -144,15 +172,19 @@ void ChainLockSigner::TrySignChainTip(const llmq::CInstantSendManager& isman) m_shareman.AsyncSignIfMember(Params().GetConsensus().llmqTypeChainLocks, m_sigman, requestId, msgHash); } -void ChainLockSigner::EraseFromBlockHashTxidMap(const uint256& hash) +void ChainLockSigner::BlockDisconnected(const std::shared_ptr& block, const CBlockIndex* pindex) { AssertLockNotHeld(cs_signer); LOCK(cs_signer); - blockTxs.erase(hash); + blockTxs.erase(pindex->GetBlockHash()); } -void ChainLockSigner::UpdateBlockHashTxidMap(const uint256& hash, const std::vector& vtx) + +void ChainLockSigner::BlockConnected(const std::shared_ptr& block, const CBlockIndex* pindex) { + // We need this information later when we try to sign a new tip, so that we can determine if all included TXs are safe. + const uint256& hash = pindex->GetBlockHash(); + AssertLockNotHeld(cs_signer); LOCK(cs_signer); auto it = blockTxs.find(hash); @@ -162,13 +194,12 @@ void ChainLockSigner::UpdateBlockHashTxidMap(const uint256& hash, const std::vec it = blockTxs.emplace(hash, std::make_shared()).first; } auto& txids = *it->second; - for (const auto& tx : vtx) { + for (const auto& tx : block->vtx) { if (!tx->IsCoinBase() && !tx->vin.empty()) { txids.emplace(tx->GetHash()); } } } - ChainLockSigner::BlockTxs::mapped_type ChainLockSigner::GetBlockTxs(const uint256& blockHash) { AssertLockNotHeld(cs_signer); @@ -222,7 +253,7 @@ ChainLockSigner::BlockTxs::mapped_type ChainLockSigner::GetBlockTxs(const uint25 MessageProcessingResult ChainLockSigner::HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) { - if (!m_clhandler.IsEnabled()) { + if (!m_chainlocks.IsEnabled()) { return {}; } @@ -234,18 +265,27 @@ MessageProcessingResult ChainLockSigner::HandleNewRecoveredSig(const llmq::CReco // this is not what we signed, so lets not create a CLSIG for it return {}; } - if (m_clhandler.GetBestChainLockHeight() >= lastSignedHeight) { + if (m_chainlocks.GetBestChainLockHeight() >= lastSignedHeight) { // already got the same or a better CLSIG through the CLSIG message return {}; } clsig = ChainLockSig(lastSignedHeight, lastSignedMsgHash, recoveredSig.sig.Get()); } - return m_clhandler.ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig)); + return m_clhandler.ProcessNewChainLock(-1, clsig, m_qman, ::SerializeHash(clsig)); } -std::vector> ChainLockSigner::Cleanup() +void ChainLockSigner::Cleanup() { + constexpr auto CLEANUP_INTERVAL{30s}; + if (!m_mn_sync.IsBlockchainSynced()) { + return; + } + + if (!m_cleanup_throttler.TryCleanup(CLEANUP_INTERVAL)) { + return; + } + AssertLockNotHeld(cs_signer); std::vector> removed; LOCK2(::cs_main, cs_signer); @@ -253,15 +293,15 @@ std::vector> ChainLockSigner::Cleanup() const auto* pindex = m_chainstate.m_blockman.LookupBlockIndex(it->first); if (!pindex) { it = blockTxs.erase(it); - } else if (m_clhandler.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { + } else if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { removed.push_back(it->second); it = blockTxs.erase(it); - } else if (m_clhandler.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { + } else if (m_chainlocks.HasConflictingChainLock(pindex->nHeight, pindex->GetBlockHash())) { it = blockTxs.erase(it); } else { ++it; } } - return removed; + m_clhandler.CleanupFromSigner(removed); } } // namespace chainlock diff --git a/src/chainlock/signing.h b/src/chainlock/signing.h index a6fac9de17d6..b3f540f44f64 100644 --- a/src/chainlock/signing.h +++ b/src/chainlock/signing.h @@ -5,14 +5,16 @@ #ifndef BITCOIN_CHAINLOCK_SIGNING_H #define BITCOIN_CHAINLOCK_SIGNING_H +#include #include #include +#include +#include +class CScheduler; class CMasternodeSync; -class CSporkManager; struct MessageProcessingResult; namespace llmq { -class CChainLocksHandler; class CInstantSendManager; class CRecoveredSig; class CSigningManager; @@ -20,31 +22,18 @@ class CSigSharesManager; } // namespace llmq namespace chainlock { -//! Depth of block including transactions before it's considered safe -static constexpr int32_t TX_CONFIRM_THRESHOLD{5}; +class ChainlockHandler; -class ChainLockSignerParent -{ -public: - virtual ~ChainLockSignerParent() = default; - - virtual int32_t GetBestChainLockHeight() const = 0; - virtual bool HasChainLock(int nHeight, const uint256& blockHash) const = 0; - virtual bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const = 0; - virtual bool IsEnabled() const = 0; - virtual bool IsTxSafeForMining(const uint256& txid) const = 0; - [[nodiscard]] virtual MessageProcessingResult ProcessNewChainLock(NodeId from, const ChainLockSig& clsig, const uint256& hash) = 0; - virtual void UpdateTxFirstSeenMap(const Uint256HashSet& tx, const int64_t& time) = 0; -}; - -class ChainLockSigner final : public llmq::CRecoveredSigsListener +class ChainLockSigner final : public llmq::CRecoveredSigsListener, public CValidationInterface { private: CChainState& m_chainstate; - ChainLockSignerParent& m_clhandler; + const chainlock::Chainlocks& m_chainlocks; + ChainlockHandler& m_clhandler; + const llmq::CInstantSendManager& m_isman; + const llmq::CQuorumManager& m_qman; llmq::CSigningManager& m_sigman; llmq::CSigSharesManager& m_shareman; - CSporkManager& m_sporkman; const CMasternodeSync& m_mn_sync; private: @@ -57,6 +46,10 @@ class ChainLockSigner final : public llmq::CRecoveredSigsListener private: mutable Mutex cs_signer; + std::unique_ptr m_scheduler; + std::unique_ptr m_scheduler_thread; + CleanupThrottler m_cleanup_throttler; + BlockTxs blockTxs GUARDED_BY(cs_signer); int32_t lastSignedHeight GUARDED_BY(cs_signer){-1}; uint256 lastSignedRequestId GUARDED_BY(cs_signer); @@ -66,26 +59,34 @@ class ChainLockSigner final : public llmq::CRecoveredSigsListener ChainLockSigner() = delete; ChainLockSigner(const ChainLockSigner&) = delete; ChainLockSigner& operator=(const ChainLockSigner&) = delete; - explicit ChainLockSigner(CChainState& chainstate, ChainLockSignerParent& clhandler, llmq::CSigningManager& sigman, - llmq::CSigSharesManager& shareman, CSporkManager& sporkman, const CMasternodeSync& mn_sync); + explicit ChainLockSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks, + ChainlockHandler& clhandler, const llmq::CInstantSendManager& isman, + const llmq::CQuorumManager& qman, llmq::CSigningManager& sigman, + llmq::CSigSharesManager& shareman, const CMasternodeSync& mn_sync); ~ChainLockSigner(); + void Start(); + void Stop(); + void RegisterRecoveryInterface(); void UnregisterRecoveryInterface(); - void EraseFromBlockHashTxidMap(const uint256& hash) + // implements validation interface: + void BlockConnected(const std::shared_ptr& block, const CBlockIndex* pindex) override EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); - void UpdateBlockHashTxidMap(const uint256& hash, const std::vector& vtx) + void BlockDisconnected(const std::shared_ptr& block, const CBlockIndex* pindex) override EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); - - void TrySignChainTip(const llmq::CInstantSendManager& isman) + void UpdatedBlockTip(const CBlockIndex* pindexNew, const CBlockIndex* pindexFork, bool fInitialDownload) override EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); + [[nodiscard]] MessageProcessingResult HandleNewRecoveredSig(const llmq::CRecoveredSig& recoveredSig) override EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); - [[nodiscard]] std::vector> Cleanup() EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); + void Cleanup() EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); private: + void TrySignChainTip() EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); + [[nodiscard]] BlockTxs::mapped_type GetBlockTxs(const uint256& blockHash) EXCLUSIVE_LOCKS_REQUIRED(!cs_signer); }; diff --git a/src/coinjoin/coinjoin.cpp b/src/coinjoin/coinjoin.cpp index 1d129d8521e6..9c5d99ea3f81 100644 --- a/src/coinjoin/coinjoin.cpp +++ b/src/coinjoin/coinjoin.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include @@ -81,14 +80,6 @@ bool CCoinJoinBroadcastTx::CheckSignature(const CBLSPublicKey& blsPubKey) const return true; } -bool CCoinJoinBroadcastTx::IsExpired(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) const -{ - // expire confirmed DSTXes after ~1h since confirmation or chainlocked confirmation - if (!nConfirmedHeight.has_value() || pindex->nHeight < *nConfirmedHeight) return false; // not mined yet - if (pindex->nHeight - *nConfirmedHeight > 24) return true; // mined more than an hour ago - return clhandler.HasChainLock(pindex->nHeight, *pindex->phashBlock); -} - bool CCoinJoinBroadcastTx::IsValidStructure() const { // some trivial checks only @@ -398,8 +389,10 @@ bilingual_str CoinJoin::GetMessageByID(PoolMessage nMessageID) } } -CDSTXManager::CDSTXManager() = default; - +CDSTXManager::CDSTXManager(const chainlock::Chainlocks& chainlocks) : + m_chainlocks{chainlocks} +{ +} CDSTXManager::~CDSTXManager() = default; void CDSTXManager::AddDSTX(const CCoinJoinBroadcastTx& dstx) @@ -417,13 +410,22 @@ CCoinJoinBroadcastTx CDSTXManager::GetDSTX(const uint256& hash) return (it == mapDSTX.end()) ? CCoinJoinBroadcastTx() : it->second; } -void CDSTXManager::CheckDSTXes(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) +bool CDSTXManager::IsTxExpired(const CCoinJoinBroadcastTx& tx, const CBlockIndex* pindex) const +{ + // expire confirmed DSTXes after ~1h since confirmation or chainlocked + const auto& opt_confirmed_height = tx.GetConfirmedHeight(); + if (!opt_confirmed_height.has_value() || pindex->nHeight < *opt_confirmed_height) return false; // not mined yet + return (pindex->nHeight - *opt_confirmed_height > 24) || + m_chainlocks.HasChainLock(pindex->nHeight, *pindex->phashBlock); // mined more than an hour ago or chainlocked +} + +void CDSTXManager::CheckDSTXes(const CBlockIndex* pindex) { AssertLockNotHeld(cs_mapdstx); LOCK(cs_mapdstx); auto it = mapDSTX.begin(); while (it != mapDSTX.end()) { - if (it->second.IsExpired(pindex, clhandler)) { + if (IsTxExpired(it->second, pindex)) { mapDSTX.erase(it++); } else { ++it; @@ -432,17 +434,17 @@ void CDSTXManager::CheckDSTXes(const CBlockIndex* pindex, const llmq::CChainLock LogPrint(BCLog::COINJOIN, "CoinJoin::CheckDSTXes -- mapDSTX.size()=%llu\n", mapDSTX.size()); } -void CDSTXManager::UpdatedBlockTip(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, const CMasternodeSync& mn_sync) +void CDSTXManager::UpdatedBlockTip(const CBlockIndex* pindex) { - if (pindex && mn_sync.IsBlockchainSynced()) { - CheckDSTXes(pindex, clhandler); + if (pindex) { + CheckDSTXes(pindex); } } -void CDSTXManager::NotifyChainLock(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, const CMasternodeSync& mn_sync) +void CDSTXManager::NotifyChainLock(const CBlockIndex* pindex) { - if (pindex && mn_sync.IsBlockchainSynced()) { - CheckDSTXes(pindex, clhandler); + if (pindex) { + CheckDSTXes(pindex); } } diff --git a/src/coinjoin/coinjoin.h b/src/coinjoin/coinjoin.h index 461aa16ec991..6e176ebfe302 100644 --- a/src/coinjoin/coinjoin.h +++ b/src/coinjoin/coinjoin.h @@ -26,11 +26,13 @@ class CChainState; class CBLSPublicKey; class CBlockIndex; class ChainstateManager; -class CMasternodeSync; class CTxMemPool; +namespace chainlock { +class Chainlocks; +} // namespace chainlock + namespace llmq { -class CChainLocksHandler; class CInstantSendManager; } // namespace llmq @@ -277,10 +279,8 @@ class CCoinJoinBroadcastTx [[nodiscard]] bool CheckSignature(const CBLSPublicKey& blsPubKey) const; - // Used only for unit tests - [[nodiscard]] std::optional GetConfirmedHeight() const { return nConfirmedHeight; } + [[nodiscard]] const std::optional& GetConfirmedHeight() const { return nConfirmedHeight; } void SetConfirmedHeight(std::optional nConfirmedHeightIn) { assert(nConfirmedHeightIn == std::nullopt || *nConfirmedHeightIn > 0); nConfirmedHeight = nConfirmedHeightIn; } - bool IsExpired(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) const; [[nodiscard]] bool IsValidStructure() const; }; @@ -368,25 +368,22 @@ namespace CoinJoin class CDSTXManager { + const chainlock::Chainlocks& m_chainlocks; Mutex cs_mapdstx; std::map mapDSTX GUARDED_BY(cs_mapdstx); public: CDSTXManager(const CDSTXManager&) = delete; CDSTXManager& operator=(const CDSTXManager&) = delete; - CDSTXManager(); + CDSTXManager(const chainlock::Chainlocks& chainlocks); ~CDSTXManager(); void AddDSTX(const CCoinJoinBroadcastTx& dstx) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); CCoinJoinBroadcastTx GetDSTX(const uint256& hash) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); - void UpdatedBlockTip(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, - const CMasternodeSync& mn_sync) - EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); - void NotifyChainLock(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler, - const CMasternodeSync& mn_sync) - EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); - + // CDSNotificationInterface + void UpdatedBlockTip(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); + void NotifyChainLock(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); void TransactionAddedToMempool(const CTransactionRef& tx) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); @@ -394,8 +391,8 @@ class CDSTXManager EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); private: - void CheckDSTXes(const CBlockIndex* pindex, const llmq::CChainLocksHandler& clhandler) - EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); + bool IsTxExpired(const CCoinJoinBroadcastTx& tx, const CBlockIndex* pindex) const EXCLUSIVE_LOCKS_REQUIRED(cs_mapdstx); + void CheckDSTXes(const CBlockIndex* pindex) EXCLUSIVE_LOCKS_REQUIRED(!cs_mapdstx); void UpdateDSTXConfirmedHeight(const CTransactionRef& tx, std::optional nHeight) EXCLUSIVE_LOCKS_REQUIRED(cs_mapdstx); }; diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index d3de81ca2031..f2566970adc3 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -8,7 +8,6 @@ #include #include -#include #include #include #include @@ -47,7 +46,6 @@ void CDSNotificationInterface::InitializeCurrentBlockTip() void CDSNotificationInterface::AcceptedBlockHeader(const CBlockIndex *pindexNew) { - Assert(m_llmq_ctx)->clhandler->AcceptedBlockHeader(pindexNew); m_mn_sync.AcceptedBlockHeader(pindexNew); } @@ -74,11 +72,11 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con if (fInitialDownload) return; - m_dstxman.UpdatedBlockTip(pindexNew, *Assert(m_llmq_ctx)->clhandler, m_mn_sync); + if (m_mn_sync.IsBlockchainSynced()) { + m_dstxman.UpdatedBlockTip(pindexNew); + } m_llmq_ctx->isman->UpdatedBlockTip(pindexNew); - m_llmq_ctx->clhandler->UpdatedBlockTip(*m_llmq_ctx->isman); - if (m_govman.IsValid()) { m_govman.UpdatedBlockTip(pindexNew); } @@ -88,7 +86,6 @@ void CDSNotificationInterface::TransactionAddedToMempool(const CTransactionRef& uint64_t mempool_sequence) { Assert(m_llmq_ctx)->isman->TransactionAddedToMempool(ptx); - m_llmq_ctx->clhandler->TransactionAddedToMempool(ptx, nAcceptTime); m_dstxman.TransactionAddedToMempool(ptx); } @@ -101,14 +98,12 @@ void CDSNotificationInterface::TransactionRemovedFromMempool(const CTransactionR void CDSNotificationInterface::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindex) { Assert(m_llmq_ctx)->isman->BlockConnected(pblock, pindex); - m_llmq_ctx->clhandler->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_llmq_ctx->clhandler->BlockDisconnected(pblock, pindexDisconnected); m_dstxman.BlockDisconnected(pblock, pindexDisconnected); } @@ -124,7 +119,9 @@ void CDSNotificationInterface::NotifyChainLock(const CBlockIndex* pindex, const std::shared_ptr& clsig) { Assert(m_llmq_ctx)->isman->NotifyChainLock(pindex); - m_dstxman.NotifyChainLock(pindex, *m_llmq_ctx->clhandler, m_mn_sync); + if (m_mn_sync.IsBlockchainSynced()) { + m_dstxman.NotifyChainLock(pindex); + } } std::unique_ptr g_ds_notification_interface; diff --git a/src/evo/chainhelper.cpp b/src/evo/chainhelper.cpp index 9c11fc4e6a3c..d978e2492254 100644 --- a/src/evo/chainhelper.cpp +++ b/src/evo/chainhelper.cpp @@ -17,28 +17,28 @@ CChainstateHelper::CChainstateHelper(CCreditPoolManager& cpoolman, CDeterministi llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, const Consensus::Params& consensus_params, const CMasternodeSync& mn_sync, const CSporkManager& sporkman, - const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman) : + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman) : isman{isman}, - clhandler{clhandler}, + m_chainlocks{chainlocks}, mn_payments{std::make_unique(dmnman, govman, chainman, consensus_params, mn_sync, sporkman)}, special_tx{std::make_unique(cpoolman, dmnman, mnhfman, qblockman, qsnapman, chainman, - consensus_params, clhandler, qman)} + consensus_params, chainlocks, qman)} {} CChainstateHelper::~CChainstateHelper() = default; -/** Passthrough functions to CChainLocksHandler */ +/** Passthrough functions to chainlock::Chainlocks */ bool CChainstateHelper::HasConflictingChainLock(int nHeight, const uint256& blockHash) const { - return clhandler.HasConflictingChainLock(nHeight, blockHash); + return m_chainlocks.HasConflictingChainLock(nHeight, blockHash); } bool CChainstateHelper::HasChainLock(int nHeight, const uint256& blockHash) const { - return clhandler.HasChainLock(nHeight, blockHash); + return m_chainlocks.HasChainLock(nHeight, blockHash); } -int32_t CChainstateHelper::GetBestChainLockHeight() const { return clhandler.GetBestChainLockHeight(); } +int32_t CChainstateHelper::GetBestChainLockHeight() const { return m_chainlocks.GetBestChainLockHeight(); } /** Passthrough functions to CInstantSendManager */ std::optional> CChainstateHelper::ConflictingISLockIfAny( diff --git a/src/evo/chainhelper.h b/src/evo/chainhelper.h index 66bee994652f..ab865f272a6a 100644 --- a/src/evo/chainhelper.h +++ b/src/evo/chainhelper.h @@ -20,9 +20,11 @@ class CSporkManager; class CTransaction; class uint256; +namespace chainlock { +class Chainlocks; +} namespace Consensus { struct Params; } namespace llmq { -class CChainLocksHandler; class CInstantSendManager; class CQuorumBlockProcessor; class CQuorumManager; @@ -33,7 +35,9 @@ class CChainstateHelper { private: llmq::CInstantSendManager& isman; - const llmq::CChainLocksHandler& clhandler; + +public: + const chainlock::Chainlocks& m_chainlocks; public: CChainstateHelper() = delete; @@ -44,10 +48,10 @@ class CChainstateHelper llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, const Consensus::Params& consensus_params, const CMasternodeSync& mn_sync, const CSporkManager& sporkman, - const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman); + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman); ~CChainstateHelper(); - /** Passthrough functions to CChainLocksHandler */ + /** Passthrough functions to chainlock::Chainlocks*/ bool HasConflictingChainLock(int nHeight, const uint256& blockHash) const; bool HasChainLock(int nHeight, const uint256& blockHash) const; int32_t GetBestChainLockHeight() const; diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index 3cdb9e7e79d8..6d12d5f6a629 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -27,8 +28,9 @@ #include #include -static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, - const llmq::CChainLocksHandler& chainlock_handler, BlockValidationState& state) +static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const Consensus::Params& consensus_params, + const CChain& chain, const llmq::CQuorumManager& qman, + const chainlock::Chainlocks& chainlocks, BlockValidationState& state) { if (cbTx.nVersion < CCbTx::Version::CLSIG_AND_BALANCE) { return true; @@ -38,7 +40,7 @@ static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, static const CBlockIndex* cached_pindex GUARDED_BY(cached_mutex){nullptr}; static std::optional> cached_chainlock GUARDED_BY(cached_mutex){std::nullopt}; - auto best_clsig = chainlock_handler.GetBestChainLock(); + auto best_clsig = chainlocks.GetBestChainLock(); if (best_clsig.getHeight() == pindex->nHeight - 1 && cbTx.bestCLHeightDiff == 0 && cbTx.bestCLSignature == best_clsig.getSig()) { // matches our best clsig which still hold values for the previous block @@ -78,9 +80,9 @@ static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, return true; } uint256 curBlockCoinbaseCLBlockHash = pindex->GetAncestor(curBlockCoinbaseCLHeight)->GetBlockHash(); - if (chainlock_handler.VerifyChainLock( - chainlock::ChainLockSig(curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, cbTx.bestCLSignature)) != - llmq::VerifyRecSigStatus::Valid) { + chainlock::ChainLockSig clsig{curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, cbTx.bestCLSignature}; + llmq::VerifyRecSigStatus ret = chainlock::VerifyChainLock(consensus_params, chain, qman, clsig); + if (ret != llmq::VerifyRecSigStatus::Valid) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig"); } LOCK(cached_mutex); @@ -657,7 +659,8 @@ bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CB LogPrint(BCLog::BENCHMARK, " - CalcCbTxMerkleRootQuorums: %.2fms [%.2fs]\n", 0.001 * (nTime6_2 - nTime6_1), nTimeMerkleQuorums * 0.000001); - if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_clhandler, state)) { + if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_consensus_params, m_chainman.ActiveChain(), m_qman, + m_chainlocks, state)) { // pass the state returned by the function above return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index 4980c11324aa..7cd56e3b637e 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -25,9 +25,11 @@ class CMNHFManager; class TxValidationState; struct MNListUpdates; +namespace chainlock { +class Chainlocks; +} namespace Consensus { struct Params; } namespace llmq { -class CChainLocksHandler; class CQuorumBlockProcessor; class CQuorumManager; class CQuorumSnapshotManager; @@ -45,14 +47,14 @@ class CSpecialTxProcessor llmq::CQuorumSnapshotManager& m_qsnapman; const ChainstateManager& m_chainman; const Consensus::Params& m_consensus_params; - const llmq::CChainLocksHandler& m_clhandler; + const chainlock::Chainlocks& m_chainlocks; const llmq::CQuorumManager& m_qman; public: explicit CSpecialTxProcessor(CCreditPoolManager& cpoolman, CDeterministicMNManager& dmnman, CMNHFManager& mnhfman, llmq::CQuorumBlockProcessor& qblockman, llmq::CQuorumSnapshotManager& qsnapman, const ChainstateManager& chainman, const Consensus::Params& consensus_params, - const llmq::CChainLocksHandler& clhandler, const llmq::CQuorumManager& qman) : + const chainlock::Chainlocks& chainlocks, const llmq::CQuorumManager& qman) : m_cpoolman(cpoolman), m_dmnman{dmnman}, m_mnhfman{mnhfman}, @@ -60,7 +62,7 @@ class CSpecialTxProcessor m_qsnapman{qsnapman}, m_chainman(chainman), m_consensus_params{consensus_params}, - m_clhandler{clhandler}, + m_chainlocks{chainlocks}, m_qman{qman} { } diff --git a/src/init.cpp b/src/init.cpp index f97d98f8a766..5c9d37cdcb63 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -23,6 +23,8 @@ #include #include #include +#include +#include #include #include #include @@ -293,8 +295,9 @@ void PrepareShutdown(NodeContext& node) if (node.observer_ctx) node.observer_ctx->Stop(); if (node.active_ctx) node.active_ctx->Stop(); + if (node.clhandler) node.clhandler->Stop(); if (node.peerman) node.peerman->StopHandlers(); - if (node.llmq_ctx) node.llmq_ctx->Stop(); + for (const auto& client : node.chain_clients) { client->flush(); @@ -303,6 +306,7 @@ void PrepareShutdown(NodeContext& node) // Because these depend on each-other, we make sure that neither can be // using the other before destroying them. + if (node.clhandler) UnregisterValidationInterface(node.clhandler.get()); if (node.peerman) UnregisterValidationInterface(node.peerman.get()); if (node.connman) node.connman->Stop(); @@ -1692,6 +1696,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) assert(!node.sporkman); node.sporkman = std::make_unique(); + node.chainlocks = std::make_unique(*node.sporkman); std::vector vSporkAddresses; if (args.IsArgSet("-sporkaddr")) { @@ -1989,6 +1994,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) *node.mn_metaman, *node.mn_sync, *node.sporkman, + *node.chainlocks, node.chain_helper, node.cpoolman, node.dmnman, @@ -2163,12 +2169,15 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) ChainstateManager& chainman = *Assert(node.chainman); assert(!node.dstxman); - node.dstxman = std::make_unique(); + node.dstxman = std::make_unique(*node.chainlocks); + + node.clhandler = std::make_unique(*node.chainlocks, chainman, *node.mempool, *node.mn_sync); + RegisterValidationInterface(node.clhandler.get()); assert(!node.peerman); node.peerman = PeerManager::make(chainparams, *node.connman, *node.addrman, node.banman.get(), *node.dstxman, chainman, *node.mempool, *node.mn_metaman, *node.mn_sync, - *node.govman, *node.sporkman, node.active_ctx, node.dmnman, + *node.govman, *node.sporkman, *node.chainlocks, *node.clhandler, node.active_ctx, node.dmnman, node.cj_walletman, node.llmq_ctx, node.observer_ctx, ignores_incoming_txs); RegisterValidationInterface(node.peerman.get()); @@ -2192,7 +2201,7 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } // Will init later in ThreadImport node.active_ctx = std::make_unique(*node.llmq_ctx->bls_worker, chainman, *node.connman, *node.dmnman, *node.govman, *node.mn_metaman, - *node.mnhf_manager, *node.sporkman, *node.mempool, *node.llmq_ctx->clhandler, *node.llmq_ctx->isman, + *node.mnhf_manager, *node.sporkman, *node.chainlocks, *node.mempool, *node.clhandler, *node.llmq_ctx->isman, *node.llmq_ctx->quorum_block_processor, *node.llmq_ctx->qman, *node.llmq_ctx->qsnapman, *node.llmq_ctx->sigman, *node.peerman, *node.mn_sync, operator_sk, sync_map, dash_db_params, quorums_recovery, quorums_watch); RegisterValidationInterface(node.active_ctx.get()); @@ -2317,8 +2326,8 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) // ********************************************************* Step 10a: schedule Dash-specific tasks - node.llmq_ctx->Start(); node.peerman->StartHandlers(); + node.clhandler->Start(); if (node.observer_ctx) node.observer_ctx->Start(); 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 495eefd68119..72193bad5267 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -48,11 +48,11 @@ Uint256HashSet GetIdsFromLockable(const std::vector& vec) } } // anonymous namespace -CInstantSendManager::CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, +CInstantSendManager::CInstantSendManager(const chainlock::Chainlocks& chainlocks, CChainState& chainstate, CSigningManager& _sigman, CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params) : db{db_params}, - clhandler{_clhandler}, + m_chainlocks{chainlocks}, m_chainstate{chainstate}, sigman{_sigman}, spork_manager{sporkman}, @@ -158,7 +158,7 @@ std::variant CInstantSendManager::Proc } // 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() && clhandler.HasChainLock(*minedHeight, hashBlock)) { + 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", @@ -261,7 +261,7 @@ void CInstantSendManager::BlockConnected(const std::shared_ptr& pb CacheTipHeight(pindex); if (m_mn_sync.IsBlockchainSynced()) { - const bool has_chainlock = clhandler.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 @@ -437,7 +437,7 @@ void CInstantSendManager::UpdatedBlockTip(const CBlockIndex* pindexNew) bool fDIP0008Active = pindexNew->pprev && pindexNew->pprev->nHeight >= Params().GetConsensus().DIP0008Height; - if (AreChainLocksEnabled(spork_manager) && fDIP0008Active) { + if (m_chainlocks.IsEnabled() && fDIP0008Active) { // Nothing to do here. We should keep all islocks and let chainlocks handle them. return; } @@ -554,7 +554,7 @@ void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const bool hasChainLockedConflict = false; for (const auto& p : conflicts) { const auto* pindex = p.first; - if (clhandler.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { + if (m_chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash())) { hasChainLockedConflict = true; break; } @@ -604,7 +604,7 @@ void CInstantSendManager::ResolveBlockConflicts(const uint256& islockHash, const if (activateBestChain) { BlockValidationState state; if (!m_chainstate.ActivateBestChain(state)) { - LogPrintf("CChainLocksHandler::%s -- ActivateBestChain failed: %s\n", __func__, state.ToString()); + 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); } diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 22421c8fc34f..af326ce1949e 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -37,6 +37,10 @@ namespace util { struct DbWrapperParams; } // namespace util +namespace chainlock { +class Chainlocks; +} + namespace instantsend { class InstantSendSigner; @@ -52,7 +56,6 @@ struct PendingState { } // namespace instantsend namespace llmq { -class CChainLocksHandler; class CSigningManager; class CInstantSendManager final : public instantsend::InstantSendSignerParent @@ -60,7 +63,7 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent private: instantsend::CInstantSendDb db; - CChainLocksHandler& clhandler; + const chainlock::Chainlocks& m_chainlocks; CChainState& m_chainstate; CSigningManager& sigman; CSporkManager& spork_manager; @@ -105,9 +108,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent CInstantSendManager() = delete; CInstantSendManager(const CInstantSendManager&) = delete; CInstantSendManager& operator=(const CInstantSendManager&) = delete; - explicit CInstantSendManager(CChainLocksHandler& _clhandler, CChainState& chainstate, CSigningManager& _sigman, - CSporkManager& sporkman, CTxMemPool& _mempool, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params); + explicit CInstantSendManager(const chainlock::Chainlocks& chainlocks, CChainState& chainstate, + CSigningManager& _sigman, CSporkManager& sporkman, CTxMemPool& _mempool, + const CMasternodeSync& mn_sync, const util::DbWrapperParams& db_params); ~CInstantSendManager(); void ConnectSigner(gsl::not_null signer) diff --git a/src/instantsend/signing.cpp b/src/instantsend/signing.cpp index 783b5757ca22..40dcc58d7589 100644 --- a/src/instantsend/signing.cpp +++ b/src/instantsend/signing.cpp @@ -28,12 +28,12 @@ using node::fReindex; using node::GetTransaction; namespace instantsend { -InstantSendSigner::InstantSendSigner(CChainState& chainstate, llmq::CChainLocksHandler& clhandler, +InstantSendSigner::InstantSendSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks, InstantSendSignerParent& isman, llmq::CSigningManager& sigman, llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman, CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync) : m_chainstate{chainstate}, - m_clhandler{clhandler}, + m_chainlocks{chainlocks}, m_isman{isman}, m_sigman{sigman}, m_shareman{shareman}, @@ -225,7 +225,7 @@ bool InstantSendSigner::CheckCanLock(const COutPoint& outpoint, bool printDebug, const int nTxAge = tipHeight - *blockHeight + 1; - if (nTxAge < nInstantSendConfirmationsRequired && !m_clhandler.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 d954daaae535..18860a22ffbd 100644 --- a/src/instantsend/signing.h +++ b/src/instantsend/signing.h @@ -18,8 +18,10 @@ struct MessageProcessingResult; namespace Consensus { struct Params; } // namespace Consensus +namespace chainlock { +class Chainlocks; +} namespace llmq { -class CChainLocksHandler; class CInstantSendManager; class CSigningManager; class CSigSharesManager; @@ -44,7 +46,7 @@ class InstantSendSigner final : public llmq::CRecoveredSigsListener { private: CChainState& m_chainstate; - llmq::CChainLocksHandler& m_clhandler; + const chainlock::Chainlocks& m_chainlocks; InstantSendSignerParent& m_isman; llmq::CSigningManager& m_sigman; llmq::CSigSharesManager& m_shareman; @@ -76,7 +78,7 @@ class InstantSendSigner final : public llmq::CRecoveredSigsListener InstantSendSigner() = delete; InstantSendSigner(const InstantSendSigner&) = delete; InstantSendSigner& operator=(const InstantSendSigner&) = delete; - explicit InstantSendSigner(CChainState& chainstate, llmq::CChainLocksHandler& clhandler, + explicit InstantSendSigner(CChainState& chainstate, const chainlock::Chainlocks& chainlocks, InstantSendSignerParent& isman, llmq::CSigningManager& sigman, llmq::CSigSharesManager& shareman, llmq::CQuorumManager& qman, CSporkManager& sporkman, CTxMemPool& mempool, const CMasternodeSync& mn_sync); diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index dd3c4a4b3a3d..cc43675a29ef 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -13,9 +12,10 @@ #include #include -LLMQContext::LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool, - const ChainstateManager& chainman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params, int8_t bls_threads, int64_t max_recsigs_age) : +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, + 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,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)}, - clhandler{std::make_unique(chainman.ActiveChainstate(), *qman, sporkman, mempool, mn_sync)}, - isman{std::make_unique(*clhandler, chainman.ActiveChainstate(), *sigman, sporkman, + isman{std::make_unique(chainlocks, chainman.ActiveChainstate(), *sigman, sporkman, mempool, mn_sync, db_params)} { // Have to start it early to let VerifyDB check ChainLock signatures in coinbase @@ -35,13 +34,3 @@ LLMQContext::~LLMQContext() { bls_worker->Stop(); } - -void LLMQContext::Start() -{ - clhandler->Start(*isman); -} - -void LLMQContext::Stop() -{ - clhandler->Stop(); -} diff --git a/src/llmq/context.h b/src/llmq/context.h index a3bae616a319..9bf3983158cd 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -19,8 +19,11 @@ class CSporkManager; class CTxMemPool; class PeerManager; +namespace chainlock { +class Chainlocks; +} + namespace llmq { -class CChainLocksHandler; class CInstantSendManager; class CQuorumBlockProcessor; class CQuorumManager; @@ -36,30 +39,23 @@ struct LLMQContext { LLMQContext() = delete; LLMQContext(const LLMQContext&) = delete; LLMQContext& operator=(const LLMQContext&) = delete; - explicit LLMQContext(CDeterministicMNManager& dmnman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool, - const ChainstateManager& chainman, const CMasternodeSync& mn_sync, - const util::DbWrapperParams& db_params, int8_t bls_threads, int64_t max_recsigs_age); + 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, + int64_t max_recsigs_age); ~LLMQContext(); - void Start(); - void Stop(); - /** Guaranteed if LLMQContext is initialized then all members are valid too * - * Please note, that members here should not be re-ordered, because initialization - * some of them requires other member initialized. + * Please note, that members here should not be re-ordered without careful consideration, + * because initialization some of them requires other member initialized. * For example, constructor `qman` requires `bls_worker`. - * - * Some objects are still global variables and their de-globalization is not trivial - * at this point. LLMQContext keeps just a pointer to them and doesn't own these objects, - * but it still guarantees that objects are created and valid */ const std::shared_ptr bls_worker; const std::unique_ptr qsnapman; const std::unique_ptr quorum_block_processor; const std::unique_ptr qman; const std::unique_ptr sigman; - const std::unique_ptr clhandler; const std::unique_ptr isman; }; diff --git a/src/net_processing.cpp b/src/net_processing.cpp index e5b83dc0f88f..2816e0ec91a6 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -42,6 +42,7 @@ #include #include +#include #include #include #include @@ -586,7 +587,8 @@ class PeerManagerImpl final : public PeerManager PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, BanMan* banman, CDSTXManager& dstxman, ChainstateManager& chainman, CTxMemPool& pool, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CGovernanceManager& govman, - CSporkManager& sporkman, + CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, + chainlock::ChainlockHandler& clhandler, const std::unique_ptr& active_ctx, const std::unique_ptr& dmnman, const std::unique_ptr& cj_walletman, @@ -820,6 +822,9 @@ class PeerManagerImpl final : public PeerManager CMasternodeSync& m_mn_sync; CGovernanceManager& m_govman; CSporkManager& m_sporkman; + const chainlock::Chainlocks& m_chainlocks; + // TODO: consider further refactoring ChainlockHandler to NetHandler to avoid boiler code in PeerManager + chainlock::ChainlockHandler& m_clhandler; /** The height of the best chain */ std::atomic m_best_height{-1}; @@ -2042,20 +2047,23 @@ std::unique_ptr PeerManager::make(const CChainParams& chainparams, BanMan* banman, CDSTXManager& dstxman, ChainstateManager& chainman, CTxMemPool& pool, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CGovernanceManager& govman, - CSporkManager& sporkman, + CSporkManager& sporkman, const chainlock::Chainlocks& chainlocks, + chainlock::ChainlockHandler& clhandler, const std::unique_ptr& active_ctx, const std::unique_ptr& dmnman, const std::unique_ptr& cj_walletman, const std::unique_ptr& llmq_ctx, const std::unique_ptr& observer_ctx, bool ignore_incoming_txs) { - return std::make_unique(chainparams, connman, addrman, banman, dstxman, chainman, pool, mn_metaman, mn_sync, govman, sporkman, active_ctx, dmnman, cj_walletman, llmq_ctx, observer_ctx, ignore_incoming_txs); + return std::make_unique(chainparams, connman, addrman, banman, dstxman, chainman, pool, mn_metaman, mn_sync, govman, sporkman, chainlocks, clhandler, active_ctx, dmnman, cj_walletman, llmq_ctx, observer_ctx, ignore_incoming_txs); } PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& connman, AddrMan& addrman, BanMan* banman, CDSTXManager& dstxman, ChainstateManager& chainman, CTxMemPool& pool, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CGovernanceManager& govman, CSporkManager& sporkman, + const chainlock::Chainlocks& chainlocks, + chainlock::ChainlockHandler& clhandler, const std::unique_ptr& active_ctx, const std::unique_ptr& dmnman, const std::unique_ptr& cj_walletman, @@ -2077,6 +2085,8 @@ PeerManagerImpl::PeerManagerImpl(const CChainParams& chainparams, CConnman& conn m_mn_sync(mn_sync), m_govman(govman), m_sporkman(sporkman), + m_chainlocks(chainlocks), + m_clhandler{clhandler}, m_ignore_incoming_txs(ignore_incoming_txs) { // While Erlay support is incomplete, it must be enabled explicitly via -txreconciliation. @@ -2357,7 +2367,7 @@ bool PeerManagerImpl::AlreadyHave(const CInv& inv) // TODO: move it to NetSigning return m_llmq_ctx->sigman->AlreadyHave(inv); case MSG_CLSIG: - return m_llmq_ctx->clhandler->AlreadyHave(inv); + return m_clhandler.AlreadyHave(inv); // TODO: move it to NetInstantSend case MSG_ISDLOCK: return m_llmq_ctx->isman->AlreadyHave(inv); @@ -2971,7 +2981,7 @@ void PeerManagerImpl::ProcessGetData(CNode& pfrom, Peer& peer, const std::atomic if (!push && (inv.type == MSG_CLSIG)) { chainlock::ChainLockSig o; - if (m_llmq_ctx->clhandler->GetChainLockByHash(inv.hash, o)) { + if (m_chainlocks.GetChainLockByHash(inv.hash, o)) { m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::CLSIG, o)); push = true; } @@ -5479,12 +5489,12 @@ void PeerManagerImpl::ProcessMessage( PostProcessMessage(ProcessPlatformBanMessage(pfrom.GetId(), msg_type, vRecv), pfrom.GetId()); if (msg_type == NetMsgType::CLSIG) { - if (llmq::AreChainLocksEnabled(m_sporkman)) { + if (m_chainlocks.IsEnabled()) { chainlock::ChainLockSig clsig; vRecv >> clsig; const uint256& hash = ::SerializeHash(clsig); WITH_LOCK(::cs_main, EraseObjectRequest(pfrom.GetId(), CInv{MSG_CLSIG, hash})); - PostProcessMessage(m_llmq_ctx->clhandler->ProcessNewChainLock(pfrom.GetId(), clsig, hash), pfrom.GetId()); + PostProcessMessage(m_clhandler.ProcessNewChainLock(pfrom.GetId(), clsig, *m_llmq_ctx->qman, hash), pfrom.GetId()); } return; // CLSIG } @@ -6313,7 +6323,7 @@ bool PeerManagerImpl::SendMessages(CNode* pto) } // Send an inv for the best ChainLock we have - const auto& clsig = m_llmq_ctx->clhandler->GetBestChainLock(); + const auto& clsig = m_chainlocks.GetBestChainLock(); if (!clsig.IsNull()) { uint256 chainlockHash{::SerializeHash(clsig)}; tx_relay->m_tx_inventory_known_filter.insert(chainlockHash); diff --git a/src/net_processing.h b/src/net_processing.h index 54795ab803c0..7bcd7cbc5bf3 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -34,6 +34,10 @@ struct LLMQContext; namespace llmq { struct ObserverContext; } // namespace llmq +namespace chainlock { +class Chainlocks; +class ChainlockHandler; +} // namespace chainlock /** Default for -maxorphantxsize, maximum size in megabytes the orphan map can grow before entries are removed */ static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS_SIZE = 10; // this allows around 100 TXs of max size (and many more of normal size) @@ -106,6 +110,8 @@ class PeerManager : public CValidationInterface, public NetEventsInterface, publ BanMan* banman, CDSTXManager& dstxman, ChainstateManager& chainman, CTxMemPool& pool, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CGovernanceManager& govman, CSporkManager& sporkman, + const chainlock::Chainlocks& chainlocks, + chainlock::ChainlockHandler& clhandler, const std::unique_ptr& active_ctx, const std::unique_ptr& dmnman, const std::unique_ptr& cj_walletman, diff --git a/src/node/chainstate.cpp b/src/node/chainstate.cpp index e60eaace5630..d34cfbddc77e 100644 --- a/src/node/chainstate.cpp +++ b/src/node/chainstate.cpp @@ -40,6 +40,7 @@ std::optional LoadChainstate(bool fReset, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CSporkManager& sporkman, + chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, std::unique_ptr& cpoolman, std::unique_ptr& dmnman, @@ -87,7 +88,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, chain_helper, cpoolman, + DashChainstateSetup(chainman, govman, mn_metaman, mn_sync, sporkman, chainlocks, chain_helper, cpoolman, dmnman, evodb, mnhf_manager, llmq_ctx, mempool, data_dir, dash_dbs_in_memory, /*llmq_dbs_wipe=*/fReset || fReindexChainState, bls_threads, max_recsigs_age, consensus_params); @@ -214,6 +215,7 @@ void DashChainstateSetup(ChainstateManager& chainman, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CSporkManager& sporkman, + chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, std::unique_ptr& cpoolman, std::unique_ptr& dmnman, @@ -235,11 +237,8 @@ void DashChainstateSetup(ChainstateManager& chainman, cpoolman.reset(); cpoolman = std::make_unique(*evodb, chainman); - if (llmq_ctx) { - llmq_ctx->Stop(); - } llmq_ctx.reset(); - llmq_ctx = std::make_unique(*dmnman, *evodb, sporkman, *mempool, chainman, mn_sync, + llmq_ctx = std::make_unique(*dmnman, *evodb, sporkman, chainlocks, *mempool, chainman, mn_sync, util::DbWrapperParams{.path = data_dir, .memory = llmq_dbs_in_memory, .wipe = llmq_dbs_wipe}, bls_threads, max_recsigs_age); mempool->ConnectManagers(dmnman.get(), llmq_ctx->isman.get()); @@ -248,7 +247,7 @@ void DashChainstateSetup(ChainstateManager& chainman, chain_helper.reset(); chain_helper = std::make_unique(*cpoolman, *dmnman, *mnhf_manager, govman, *(llmq_ctx->isman), *(llmq_ctx->quorum_block_processor), - *(llmq_ctx->qsnapman), chainman, consensus_params, mn_sync, sporkman, *(llmq_ctx->clhandler), + *(llmq_ctx->qsnapman), chainman, consensus_params, mn_sync, sporkman, chainlocks, *(llmq_ctx->qman)); } diff --git a/src/node/chainstate.h b/src/node/chainstate.h index 0022b7637d87..4bd42375f51f 100644 --- a/src/node/chainstate.h +++ b/src/node/chainstate.h @@ -25,6 +25,7 @@ class CSporkManager; class CTxMemPool; struct LLMQContext; +namespace chainlock { class Chainlocks; } namespace Consensus { struct Params; } // namespace Consensus @@ -84,6 +85,7 @@ std::optional LoadChainstate(bool fReset, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CSporkManager& sporkman, + chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, std::unique_ptr& cpoolman, std::unique_ptr& dmnman, @@ -115,6 +117,7 @@ void DashChainstateSetup(ChainstateManager& chainman, CMasternodeMetaMan& mn_metaman, CMasternodeSync& mn_sync, CSporkManager& sporkman, + chainlock::Chainlocks& chainlocks, std::unique_ptr& chain_helper, std::unique_ptr& cpoolman, std::unique_ptr& dmnman, diff --git a/src/node/context.cpp b/src/node/context.cpp index f786b71d9aa3..025bb23e8687 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include #include #include diff --git a/src/node/context.h b/src/node/context.h index f2d1bdbb9722..ba3c1ae0063b 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -35,9 +35,11 @@ class NetGroupManager; class PeerManager; struct ActiveContext; struct LLMQContext; -namespace llmq { -struct ObserverContext; -} // namespace llmq + +namespace chainlock { +class Chainlocks; +class ChainlockHandler; +} // namespace chainlock namespace interfaces { class Chain; @@ -49,6 +51,10 @@ class Loader; } // namspace CoinJoin } // namespace interfaces +namespace llmq { +struct ObserverContext; +} // namespace llmq + namespace node { //! NodeContext struct containing references to chain state and connection //! state. @@ -94,6 +100,8 @@ struct NodeContext { std::unique_ptr mnhf_manager; std::unique_ptr netfulfilledman; std::unique_ptr sporkman; + std::unique_ptr chainlocks; + std::unique_ptr clhandler; //! Dash contexts std::unique_ptr active_ctx; std::unique_ptr llmq_ctx; diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 88b7edf3bb56..cb3894db59db 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1074,8 +1074,8 @@ class ChainImpl : public Chain } bool hasChainLock(int height, const uint256& hash) override { - if (m_node.llmq_ctx == nullptr || m_node.llmq_ctx->clhandler == nullptr) return false; - return m_node.llmq_ctx->clhandler->HasChainLock(height, hash); + if (m_node.chainlocks == nullptr) return false; + return m_node.chainlocks->HasChainLock(height, hash); } std::vector listMNCollaterials(const std::vector>& outputs) override { diff --git a/src/node/miner.cpp b/src/node/miner.cpp index a90b88ba3613..429db2d84743 100644 --- a/src/node/miner.cpp +++ b/src/node/miner.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -76,7 +77,8 @@ BlockAssembler::BlockAssembler(CChainState& chainstate, const NodeContext& node, m_chainstate(chainstate), m_evoDb(*Assert(node.evodb)), m_mnhfman(*Assert(node.mnhf_manager)), - m_clhandler(*Assert(Assert(node.llmq_ctx)->clhandler)), + m_chainlocks(*Assert(node.chainlocks)), + m_clhandler(*Assert(node.clhandler)), m_isman(*Assert(Assert(node.llmq_ctx)->isman)), chainparams(params), m_mempool(mempool), @@ -121,10 +123,10 @@ void BlockAssembler::resetBlock() } // Helper to calculate best chainlock -static bool CalcCbTxBestChainlock(const llmq::CChainLocksHandler& chainlock_handler, const CBlockIndex* pindexPrev, +static bool CalcCbTxBestChainlock(const chainlock::Chainlocks& chainlocks, const CBlockIndex* pindexPrev, uint32_t& bestCLHeightDiff, CBLSSignature& bestCLSignature) { - auto best_clsig = chainlock_handler.GetBestChainLock(); + auto best_clsig = chainlocks.GetBestChainLock(); if (best_clsig.getHeight() < Params().GetConsensus().DeploymentHeight(Consensus::DEPLOYMENT_V19)) { // We don't want legacy BLS ChainLocks in CbTx (can happen on regtest/devenets) best_clsig = chainlock::ChainLockSig{}; @@ -299,7 +301,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc throw std::runtime_error(strprintf("%s: CalcCbTxMerkleRootQuorums failed: %s", __func__, state.ToString())); } if (fV20Active_context) { - if (CalcCbTxBestChainlock(m_clhandler, pindexPrev, cbTx.bestCLHeightDiff, cbTx.bestCLSignature)) { + if (CalcCbTxBestChainlock(m_chainlocks, pindexPrev, cbTx.bestCLHeightDiff, cbTx.bestCLSignature)) { LogPrintf("CreateNewBlock() h[%d] CbTx bestCLHeightDiff[%d] CLSig[%s]\n", nHeight, cbTx.bestCLHeightDiff, cbTx.bestCLSignature.ToString()); } else { // not an error @@ -334,7 +336,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc pblocktemplate->vTxSigOps[0] = GetLegacySigOpCount(*pblock->vtx[0]); BlockValidationState state; - if (!TestBlockValidity(state, m_clhandler, m_evoDb, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { + if (!TestBlockValidity(state, m_chainlocks, m_evoDb, chainparams, m_chainstate, *pblock, pindexPrev, false, false)) { throw std::runtime_error(strprintf("%s: TestBlockValidity failed: %s", __func__, state.ToString())); } int64_t nTime2 = GetTimeMicros(); diff --git a/src/node/miner.h b/src/node/miner.h index eea4d69884bd..8b7f7e096177 100644 --- a/src/node/miner.h +++ b/src/node/miner.h @@ -29,9 +29,13 @@ class CMNHFManager; class CScript; struct LLMQContext; +namespace chainlock +{ +class Chainlocks; +class ChainlockHandler; +} // namespace chainlock namespace Consensus { struct Params; }; namespace llmq { -class CChainLocksHandler; class CInstantSendManager; class CQuorumBlockProcessor; class CQuorumManager; @@ -172,7 +176,8 @@ class BlockAssembler CChainState& m_chainstate; CEvoDB& m_evoDb; CMNHFManager& m_mnhfman; - llmq::CChainLocksHandler& m_clhandler; + const chainlock::Chainlocks& m_chainlocks; + chainlock::ChainlockHandler& m_clhandler; llmq::CInstantSendManager& m_isman; const CChainParams& chainparams; const CTxMemPool* const m_mempool; diff --git a/src/rest.cpp b/src/rest.cpp index 68f5fc79425d..207a2f247400 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -282,12 +282,12 @@ static bool rest_headers(const CoreContext& context, return true; } case RESTResponseFormat::JSON: { - const LLMQContext* llmq_ctx = GetLLMQContext(context, req); - if (!llmq_ctx) return false; + const NodeContext* const node = GetNodeContext(context, req); + if (!node || !node->chainlocks) return false; UniValue jsonHeaders(UniValue::VARR); for (const CBlockIndex *pindex : headers) { - jsonHeaders.push_back(blockheaderToJSON(tip, pindex, *llmq_ctx->clhandler)); + jsonHeaders.push_back(blockheaderToJSON(tip, pindex, *node->chainlocks)); } std::string strJSON = jsonHeaders.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); @@ -356,10 +356,13 @@ static bool rest_block(const CoreContext& context, } case RESTResponseFormat::JSON: { + const NodeContext* const node = GetNodeContext(context, req); + if (!node || !node->chainlocks) return false; + const LLMQContext* llmq_ctx = GetLLMQContext(context, req); if (!llmq_ctx) return false; - UniValue objBlock = blockToJSON(chainman.m_blockman, block, tip, pblockindex, *llmq_ctx->clhandler, *llmq_ctx->isman, tx_verbosity); + UniValue objBlock = blockToJSON(chainman.m_blockman, block, tip, pblockindex, *node->chainlocks, *llmq_ctx->isman, tx_verbosity); std::string strJSON = objBlock.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(HTTP_OK, strJSON); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 203e20da3ba2..ab74d75431b9 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -82,7 +82,7 @@ static GlobalMutex cs_blockchange; static std::condition_variable cond_blockchange; static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); -extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, const CTxMemPool& mempool, const CChainState& active_chainstate, const llmq::CChainLocksHandler& clhandler, const llmq::CInstantSendManager& isman, UniValue& entry, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS); +extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, const CTxMemPool& mempool, const CChainState& active_chainstate, const chainlock::Chainlocks& chainlocks, const llmq::CInstantSendManager& isman, UniValue& entry, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS); /* Calculate the difficulty for a given block index. */ @@ -131,7 +131,7 @@ static const CBlockIndex* ParseHashOrHeight(const UniValue& param, ChainstateMan } } -UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex, const llmq::CChainLocksHandler& clhandler) +UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex, const chainlock::Chainlocks& chainlocks) { // Serialize passed information without accessing chain state of the active chain! AssertLockNotHeld(cs_main); // For performance reasons @@ -158,14 +158,14 @@ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex if (pnext) result.pushKV("nextblockhash", pnext->GetBlockHash().GetHex()); - result.pushKV("chainlock", clhandler.HasChainLock(blockindex->nHeight, blockindex->GetBlockHash())); + result.pushKV("chainlock", chainlocks.HasChainLock(blockindex->nHeight, blockindex->GetBlockHash())); return result; } -UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, const llmq::CChainLocksHandler& clhandler, const llmq::CInstantSendManager& isman, TxVerbosity verbosity) +UniValue blockToJSON(BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, const chainlock::Chainlocks& chainlocks, const llmq::CInstantSendManager& isman, TxVerbosity verbosity) { - UniValue result = blockheaderToJSON(tip, blockindex, clhandler); + UniValue result = blockheaderToJSON(tip, blockindex, chainlocks); result.pushKV("size", (int)::GetSerializeSize(block, PROTOCOL_VERSION)); UniValue txs(UniValue::VARR); @@ -268,8 +268,8 @@ static RPCHelpMan getbestchainlock() { const NodeContext& node = EnsureAnyNodeContext(request.context); - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); - const chainlock::ChainLockSig clsig = llmq_ctx.clhandler->GetBestChainLock(); + CHECK_NONFATAL(node.chainlocks); + const chainlock::ChainLockSig clsig = node.chainlocks->GetBestChainLock(); if (clsig.IsNull()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to find any ChainLock"); } @@ -667,8 +667,8 @@ static RPCHelpMan getblockheader() return strHex; } - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); - return blockheaderToJSON(tip, pblockindex, *llmq_ctx.clhandler); + CHECK_NONFATAL(node.chainlocks); + return blockheaderToJSON(tip, pblockindex, *node.chainlocks); }, }; } @@ -766,10 +766,10 @@ static RPCHelpMan getblockheaders() return arrHeaders; } - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); + CHECK_NONFATAL(node.chainlocks); for (; pblockindex; pblockindex = active_chain.Next(pblockindex)) { - arrHeaders.push_back(blockheaderToJSON(tip, pblockindex, *llmq_ctx.clhandler)); + arrHeaders.push_back(blockheaderToJSON(tip, pblockindex, *node.chainlocks)); if (--nCount <= 0) break; } @@ -1026,6 +1026,7 @@ static RPCHelpMan getblock() } const LLMQContext& llmq_ctx = EnsureLLMQContext(node); + CHECK_NONFATAL(node.chainlocks); TxVerbosity tx_verbosity; if (verbosity == 1) { tx_verbosity = TxVerbosity::SHOW_TXID; @@ -1035,7 +1036,7 @@ static RPCHelpMan getblock() tx_verbosity = TxVerbosity::SHOW_DETAILS_AND_PREVOUT; } - return blockToJSON(chainman.m_blockman, block, tip, pblockindex, *llmq_ctx.clhandler, *llmq_ctx.isman, tx_verbosity); + return blockToJSON(chainman.m_blockman, block, tip, pblockindex, *node.chainlocks, *llmq_ctx.isman, tx_verbosity); }, }; } @@ -2290,6 +2291,7 @@ static RPCHelpMan getspecialtxes() const CTxMemPool& mempool = EnsureMemPool(node); const LLMQContext& llmq_ctx = EnsureLLMQContext(node); + CHECK_NONFATAL(node.chainlocks); const uint256 blockhash(ParseHashV(request.params[0], "blockhash")); @@ -2348,7 +2350,7 @@ static RPCHelpMan getspecialtxes() case 2 : { UniValue objTx(UniValue::VOBJ); - TxToJSON(*tx, blockhash, mempool, chainman.ActiveChainstate(), *llmq_ctx.clhandler, *llmq_ctx.isman, objTx); + TxToJSON(*tx, blockhash, mempool, chainman.ActiveChainstate(), *node.chainlocks, *llmq_ctx.isman, objTx); result.push_back(objTx); break; } diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index f84cb6794fbe..7c2051bd1d2e 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -23,11 +23,11 @@ class CBlock; class CBlockIndex; class CChainState; class CCoinsView; +namespace chainlock { class Chainlocks; } namespace kernel { enum class CoinStatsHashType : uint8_t; } namespace llmq { -class CChainLocksHandler; class CInstantSendManager; } // namespace llmq namespace node { @@ -51,10 +51,10 @@ double GetDifficulty(const CBlockIndex* blockindex); void RPCNotifyBlockChange(const CBlockIndex*); /** Block description to JSON */ -UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, const llmq::CChainLocksHandler& clhandler, const llmq::CInstantSendManager& isman, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); +UniValue blockToJSON(node::BlockManager& blockman, const CBlock& block, const CBlockIndex* tip, const CBlockIndex* blockindex, const chainlock::Chainlocks& chainlocks, const llmq::CInstantSendManager& isman, TxVerbosity verbosity) LOCKS_EXCLUDED(cs_main); /** Block header to JSON */ -UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex, const llmq::CChainLocksHandler& clhandler) LOCKS_EXCLUDED(cs_main); +UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex, const chainlock::Chainlocks& chainlocks) LOCKS_EXCLUDED(cs_main); /** Used by getblockstats to get feerates at different percentiles by weight */ void CalculatePercentilesBySize(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES], std::vector>& scores, int64_t total_size); diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index cb39dae14489..c05badf975b8 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -5,7 +5,6 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include -#include #include #include #include @@ -357,7 +356,6 @@ static RPCHelpMan generateblock() } const CChainParams& chainparams(Params()); - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); ChainstateManager& chainman = EnsureChainman(node); CChainState& active_chainstate = chainman.ActiveChainstate(); @@ -383,7 +381,7 @@ static RPCHelpMan generateblock() LOCK(cs_main); BlockValidationState state; - if (!TestBlockValidity(state, *llmq_ctx.clhandler, *CHECK_NONFATAL(node.evodb), chainparams, active_chainstate, + if (!TestBlockValidity(state, *CHECK_NONFATAL(node.chainlocks), *CHECK_NONFATAL(node.evodb), chainparams, active_chainstate, block, chainman.m_blockman.LookupBlockIndex(block.hashPrevBlock), false, false)) { throw JSONRPCError(RPC_VERIFY_ERROR, strprintf("TestBlockValidity failed: %s", state.GetRejectReason())); } @@ -699,14 +697,12 @@ static RPCHelpMan getblocktemplate() return "duplicate-inconclusive"; } - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); - CBlockIndex* const pindexPrev = active_chain.Tip(); // TestBlockValidity only supports blocks built on the current Tip if (block.hashPrevBlock != pindexPrev->GetBlockHash()) return "inconclusive-not-best-prevblk"; BlockValidationState state; - TestBlockValidity(state, *llmq_ctx.clhandler, *CHECK_NONFATAL(node.evodb), Params(), active_chainstate, + TestBlockValidity(state, *CHECK_NONFATAL(node.chainlocks), *CHECK_NONFATAL(node.evodb), Params(), active_chainstate, block, pindexPrev, false, true); return BIP22ValidationResult(state); } diff --git a/src/rpc/quorums.cpp b/src/rpc/quorums.cpp index f82ec32def8d..f35bd6952f4b 100644 --- a/src/rpc/quorums.cpp +++ b/src/rpc/quorums.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -1114,7 +1115,8 @@ static RPCHelpMan verifychainlock() } const LLMQContext& llmq_ctx = EnsureLLMQContext(node); - return llmq_ctx.clhandler->VerifyChainLock(chainlock::ChainLockSig(nBlockHeight, nBlockHash, sig)) == + return chainlock::VerifyChainLock(Params().GetConsensus(), chainman.ActiveChain(), *CHECK_NONFATAL(llmq_ctx.qman), + chainlock::ChainLockSig{nBlockHeight, nBlockHash, sig}) == llmq::VerifyRecSigStatus::Valid; }, }; @@ -1217,7 +1219,8 @@ static RPCHelpMan submitchainlock() } const NodeContext& node = EnsureAnyNodeContext(request.context); const LLMQContext& llmq_ctx = EnsureLLMQContext(node); - const int32_t bestCLHeight = llmq_ctx.clhandler->GetBestChainLock().getHeight(); + CHECK_NONFATAL(node.chainlocks); + const int32_t bestCLHeight = node.chainlocks->GetBestChainLock().getHeight(); if (nBlockHeight <= bestCLHeight) return bestCLHeight; CBLSSignature sig; @@ -1225,12 +1228,12 @@ static RPCHelpMan submitchainlock() throw JSONRPCError(RPC_INVALID_PARAMETER, "invalid signature format"); } - + const ChainstateManager& chainman = EnsureChainman(node); const auto clsig{chainlock::ChainLockSig(nBlockHeight, nBlockHash, sig)}; - const llmq::VerifyRecSigStatus ret{llmq_ctx.clhandler->VerifyChainLock(clsig)}; + const llmq::VerifyRecSigStatus ret{ + chainlock::VerifyChainLock(Params().GetConsensus(), chainman.ActiveChain(), *llmq_ctx.qman, clsig)}; if (ret == llmq::VerifyRecSigStatus::NoQuorum) { LOCK(cs_main); - const ChainstateManager& chainman = EnsureChainman(node); const CBlockIndex* pIndex{chainman.ActiveChain().Tip()}; throw JSONRPCError(RPC_MISC_ERROR, strprintf("No quorum found. Current tip height: %d hash: %s\n", pIndex->nHeight, pIndex->GetBlockHash().ToString())); } @@ -1239,8 +1242,9 @@ static RPCHelpMan submitchainlock() } PeerManager& peerman = EnsurePeerman(node); - peerman.PostProcessMessage(llmq_ctx.clhandler->ProcessNewChainLock(-1, clsig, ::SerializeHash(clsig))); - return llmq_ctx.clhandler->GetBestChainLock().getHeight(); + CHECK_NONFATAL(node.clhandler); + peerman.PostProcessMessage(node.clhandler->ProcessNewChainLock(-1, clsig, *llmq_ctx.qman, ::SerializeHash(clsig))); + return node.chainlocks->GetBestChainLock().getHeight(); }, }; } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 5db203c01832..554f4bd3d53e 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -69,7 +69,7 @@ using node::GetTransaction; using node::NodeContext; using node::PSBTAnalysis; -void TxToJSON(const CTransaction& tx, const uint256 hashBlock, const CTxMemPool& mempool, const CChainState& active_chainstate, const llmq::CChainLocksHandler& clhandler, const llmq::CInstantSendManager& isman, UniValue& entry, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS) +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, const CTxMemPool& mempool, const CChainState& active_chainstate, const chainlock::Chainlocks& chainlocks, const llmq::CInstantSendManager& isman, UniValue& entry, TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS) { CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS); LOCK(::cs_main); @@ -117,7 +117,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, const CTxMemPool entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight); entry.pushKV("time", pindex->GetBlockTime()); entry.pushKV("blocktime", pindex->GetBlockTime()); - chainLock = clhandler.HasChainLock(pindex->nHeight, pindex->GetBlockHash()); + chainLock = chainlocks.HasChainLock(pindex->nHeight, pindex->GetBlockHash()); } else { entry.pushKV("height", -1); entry.pushKV("confirmations", 0); @@ -329,10 +329,11 @@ static RPCHelpMan getrawtransaction() const LLMQContext& llmq_ctx = EnsureLLMQContext(node); const CTxMemPool& mempool = EnsureMemPool(node); + CHECK_NONFATAL(node.chainlocks); UniValue result(UniValue::VOBJ); if (blockindex) result.pushKV("in_active_chain", in_active_chain); - TxToJSON(*tx, hash_block, mempool, chainman.ActiveChainstate(), *llmq_ctx.clhandler, *llmq_ctx.isman, result); + TxToJSON(*tx, hash_block, mempool, chainman.ActiveChainstate(), *node.chainlocks, *llmq_ctx.isman, result); return result; }, }; @@ -390,6 +391,7 @@ static RPCHelpMan getrawtransactionmulti() { const NodeContext& node{EnsureAnyNodeContext(request.context)}; const ChainstateManager& chainman{EnsureChainman(node)}; const LLMQContext& llmq_ctx{EnsureLLMQContext(node)}; + CHECK_NONFATAL(node.chainlocks); CTxMemPool& mempool{EnsureMemPool(node)}; if (transactions.size() > 100) { @@ -424,7 +426,7 @@ static RPCHelpMan getrawtransactionmulti() { result.pushKV(txid_str, "None"); } else if (fVerbose) { UniValue tx_data{UniValue::VOBJ}; - TxToJSON(*tx, hash_block, mempool, chainman.ActiveChainstate(), *llmq_ctx.clhandler, *llmq_ctx.isman, tx_data); + TxToJSON(*tx, hash_block, mempool, chainman.ActiveChainstate(), *node.chainlocks, *llmq_ctx.isman, tx_data); result.pushKV(txid_str, tx_data); } else { result.pushKV(txid_str, EncodeHexTx(*tx)); @@ -548,9 +550,9 @@ static RPCHelpMan gettxchainlocks() [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue { const NodeContext& node = EnsureAnyNodeContext(request.context); - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); const ChainstateManager& chainman = EnsureChainman(node); const CChainState& active_chainstate = chainman.ActiveChainstate(); + CHECK_NONFATAL(node.chainlocks); UniValue result_arr(UniValue::VARR); UniValue txids = request.params[0].get_array(); @@ -592,7 +594,7 @@ static RPCHelpMan gettxchainlocks() } } if (height != -1) { - chainLock = llmq_ctx.clhandler->HasChainLock(height, hash_block); + chainLock = node.chainlocks->HasChainLock(height, hash_block); } result.pushKV("height", height); result.pushKV("chainlock", chainLock); @@ -635,7 +637,7 @@ static RPCHelpMan getassetunlockstatuses() { const NodeContext& node = EnsureAnyNodeContext(request.context); const CTxMemPool& mempool = EnsureMemPool(node); - const LLMQContext& llmq_ctx = EnsureLLMQContext(node); + CHECK_NONFATAL(node.chainlocks); const ChainstateManager& chainman = EnsureChainman(node); UniValue result_arr(UniValue::VARR); @@ -667,7 +669,7 @@ static RPCHelpMan getassetunlockstatuses() } else { const auto pBlockIndexBestCL = [&]() -> const CBlockIndex* { - const auto best_clsig = llmq_ctx.clhandler->GetBestChainLock(); + const auto best_clsig = node.chainlocks->GetBestChainLock(); if (!best_clsig.IsNull()) { return pTipBlockIndex->GetAncestor(best_clsig.getHeight()); } diff --git a/src/spork.cpp b/src/spork.cpp index a407879c38a5..874a3e4b6ff9 100644 --- a/src/spork.cpp +++ b/src/spork.cpp @@ -17,7 +17,6 @@ #include // for MESSAGE_MAGIC #include #include -#include #include diff --git a/src/test/coinjoin_dstxmanager_tests.cpp b/src/test/coinjoin_dstxmanager_tests.cpp index 24afd2f8fe69..68bf245dec4b 100644 --- a/src/test/coinjoin_dstxmanager_tests.cpp +++ b/src/test/coinjoin_dstxmanager_tests.cpp @@ -49,6 +49,47 @@ BOOST_AUTO_TEST_CASE(add_get_dstx) BOOST_CHECK_EQUAL(got.tx->GetHash().ToString(), dstx.tx->GetHash().ToString()); } +BOOST_AUTO_TEST_CASE(broadcasttx_expiry_height_logic) +{ + // Build a valid-looking CCoinJoinBroadcastTx with confirmed height + CCoinJoinBroadcastTx dstx; + { + CMutableTransaction mtx; + const int participants = std::max(3, CoinJoin::GetMinPoolParticipants()); + for (int i = 0; i < participants; ++i) { + mtx.vin.emplace_back(COutPoint(uint256::TWO, i)); + CScript spk; + spk << OP_DUP << OP_HASH160 << std::vector(20, i) << OP_EQUALVERIFY << OP_CHECKSIG; + mtx.vout.emplace_back(CoinJoin::GetSmallestDenomination(), spk); + } + dstx.tx = MakeTransactionRef(mtx); + dstx.m_protxHash = uint256::ONE; + // mark as confirmed at height 100 + dstx.SetConfirmedHeight(100); + } + + int expiry_height = 124; // 125 - 100 == 25 > 24 → expired by height + + // Minimal CBlockIndex with required fields + // Create a minimal block index to satisfy the interface + CBlockIndex index; + uint256 blk_hash = uint256S("03"); + index.nHeight = expiry_height; + index.phashBlock = &blk_hash; + + auto& man = *Assert(m_node.dstxman); + auto& hash = dstx.tx->GetHash(); + man.AddDSTX(dstx); + BOOST_CHECK(static_cast(man.GetDSTX(hash))); + + man.UpdatedBlockTip(&index); + BOOST_CHECK(static_cast(man.GetDSTX(hash))); + + index.nHeight = expiry_height + 1; + man.UpdatedBlockTip(&index); + BOOST_CHECK(!static_cast(man.GetDSTX(hash))); +} + BOOST_AUTO_TEST_CASE(update_heights_block_connect_disconnect) { CCoinJoinBroadcastTx dstx = MakeDSTX(); diff --git a/src/test/coinjoin_inouts_tests.cpp b/src/test/coinjoin_inouts_tests.cpp index 94bbedc3caa8..8a41bd710c58 100644 --- a/src/test/coinjoin_inouts_tests.cpp +++ b/src/test/coinjoin_inouts_tests.cpp @@ -10,10 +10,9 @@ #include #include -#include #include #include -#include +#include #include