From 1c979169a4c257930b8d51bed43e9393aee32d61 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 5 Oct 2023 18:09:10 +0700 Subject: [PATCH 01/10] feat: provide information about MN EHF forks in RPC blockchaininfo --- src/rpc/blockchain.cpp | 1 + test/functional/rpc_blockchain.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 2a2168b167f1..c37bdb525d81 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1614,6 +1614,7 @@ static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniVal } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); + bip9.pushKV("EHF", consensusParams.vDeployments[id].nMNActivationHeight); int64_t since_height = VersionBitsStateSinceHeight(active_chain_tip, consensusParams, id, versionbitscache); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index a342615b3520..fe758a79116c 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -146,7 +146,8 @@ def _test_getblockchaininfo(self): 'status': 'defined', 'start_time': 0, 'timeout': 9223372036854775807, - 'since': 0 + 'since': 0, + 'EHF': -1, }, 'active': False}, 'mn_rr': { 'type': 'bip9', @@ -154,7 +155,8 @@ def _test_getblockchaininfo(self): 'status': 'defined', 'start_time': 0, 'timeout': 9223372036854775807, - 'since': 0 + 'since': 0, + 'EHF': 0, }, 'active': False}, 'testdummy': { @@ -172,6 +174,7 @@ def _test_getblockchaininfo(self): 'count': 57, 'possible': True, }, + 'EHF': -1, }, 'active': False}, }) From d5d691f1eb870ac75c9935ad3b8ec4cd25fe165c Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 9 Oct 2023 17:17:46 +0700 Subject: [PATCH 02/10] fix: validation of IsTxSafeForMining for txes without input --- src/miner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/miner.cpp b/src/miner.cpp index 431de61f0441..c2a9020c765f 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -310,7 +310,7 @@ bool BlockAssembler::TestPackageTransactions(const CTxMemPool::setEntries& packa const auto& txid = it->GetTx().GetHash(); if (!m_isman.RejectConflictingBlocks() || !m_isman.IsInstantSendEnabled() || m_isman.IsLocked(txid)) continue; - if (!m_clhandler.IsTxSafeForMining(txid)) { + if (!it->GetTx().vin.empty() && !m_clhandler.IsTxSafeForMining(txid)) { return false; } } From 2ba09c9a78aa74a1c0aa45c8c1bc2213d8b2243f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 27 Sep 2023 03:22:42 +0700 Subject: [PATCH 03/10] feat: auto signing EHF signals and submitting TX with that signal to Mempool This commit adds basic implementation for auto-generation of EHF signals. This implementation is a basement for various EHF (include MN_RR). Please, notice that some unit and functional tests are broken after this commit due to unexpected activation of MN_RR (or in opposited expected non-activation) Next commit is adding a new Spork SPORK_24_MN_RR_READY and fixes all Unit and Functional tests accordingly new logic of MN_RR activation --- src/Makefile.am | 14 +-- src/chainparams.cpp | 22 +++-- src/dsnotificationinterface.cpp | 2 + src/evo/mnhftx.cpp | 22 ++++- src/evo/mnhftx.h | 15 ++- src/init.cpp | 2 +- src/llmq/context.cpp | 6 +- src/llmq/context.h | 14 ++- src/llmq/ehf_signals.cpp | 131 +++++++++++++++++++++++++ src/llmq/ehf_signals.h | 61 ++++++++++++ src/test/util/setup_common.cpp | 2 +- test/functional/feature_asset_locks.py | 14 ++- test/functional/feature_mnehf.py | 7 +- 13 files changed, 279 insertions(+), 33 deletions(-) create mode 100644 src/llmq/ehf_signals.cpp create mode 100644 src/llmq/ehf_signals.h diff --git a/src/Makefile.am b/src/Makefile.am index a354b9e6f514..2f223b6c791e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -213,22 +213,24 @@ BITCOIN_CORE_H = \ key_io.h \ dbwrapper.h \ limitedmap.h \ - llmq/quorums.h \ llmq/blockprocessor.h \ - llmq/commitment.h \ llmq/chainlocks.h \ llmq/clsig.h \ + llmq/commitment.h \ + llmq/context.h \ llmq/debug.h \ + llmq/dkgsession.h \ llmq/dkgsessionhandler.h \ llmq/dkgsessionmgr.h \ - llmq/dkgsession.h \ - llmq/context.h \ + llmq/ehf_signals.cpp \ + llmq/ehf_signals.h \ llmq/instantsend.h \ - llmq/snapshot.h \ + llmq/params.h \ + llmq/quorums.h \ llmq/signing.h \ llmq/signing_shares.h \ + llmq/snapshot.h \ llmq/utils.h \ - llmq/params.h \ logging.h \ logging/timer.h \ mapport.h \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index cb1e217b93c2..819ae3ab8bd3 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -219,10 +219,11 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 19999999999; // TODO: To be determined later consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 3226; // 80% of 4032 - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 2420; // 60% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 576; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 460; // 80% of 576 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 346; // 60% of 576 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000008677827656704520eb39"); // 1889000 @@ -420,6 +421,7 @@ class CTestNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00000000000000000000000000000000000000000000000002d68c8cc1b8e54b"); // 851000 @@ -587,10 +589,11 @@ class CDevNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 1661990400; // Sep 1st, 2022 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 120; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100 - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 1; // 80% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 1; // 60% of 100 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x000000000000000000000000000000000000000000000000000000000000000"); @@ -826,10 +829,11 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1030; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 800; // 80% of 1000 - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 600; // 60% of 1000 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 1; // 100% of 1 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 1; // 100% of 1 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation // The best chain should have at least this much work. consensus.nMinimumChainWork = uint256S("0x00"); diff --git a/src/dsnotificationinterface.cpp b/src/dsnotificationinterface.cpp index f32ee68c6657..9bfeb3f82daf 100644 --- a/src/dsnotificationinterface.cpp +++ b/src/dsnotificationinterface.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -78,6 +79,7 @@ void CDSNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, con llmq_ctx->qman->UpdatedBlockTip(pindexNew, fInitialDownload); llmq_ctx->qdkgsman->UpdatedBlockTip(pindexNew, fInitialDownload); + llmq_ctx->ehfSignalsHandler->UpdatedBlockTip(pindexNew); if (!fDisableGovernance) govman.UpdatedBlockTip(pindexNew, connman); } diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 02f6936ca625..c019f18248dc 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -19,9 +19,24 @@ #include #include -extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf"; +static const std::string MNEHF_REQUESTID_PREFIX = "mnhf"; static const std::string DB_SIGNALS = "mnhf_s"; +uint256 MNHFTxPayload::GetRequestId() const +{ + return ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{signal.versionBit})); +} + +CMutableTransaction MNHFTxPayload::PrepareTx() const +{ + CMutableTransaction tx; + tx.nVersion = 3; + tx.nType = SPECIALTX_TYPE; + SetTxPayload(tx, *this); + + return tx; +} + CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev) { Signals signals = GetFromCache(pindexPrev); @@ -53,7 +68,7 @@ CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pin return signals; } -bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const +bool MNHFTx::Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const { if (versionBit >= VERSIONBITS_NUM_BITS) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds"); @@ -62,7 +77,6 @@ bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidat const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf; const auto quorum = llmq::quorumManager->GetQuorum(llmqType, quorumHash); - const uint256 requestId = ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{versionBit})); const uint256 signHash = llmq::utils::BuildSignHash(llmqType, quorum->qc->quorumHash, requestId, msgHash); if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash)) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid"); @@ -104,7 +118,7 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida uint256 msgHash = tx_copy.GetHash(); - if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) { + if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, mnhfTx.GetRequestId(), msgHash, state)) { // set up inside Verify return false; } diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index a6a67d9c7200..1622bf5da60b 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -32,7 +32,7 @@ class MNHFTx CBLSSignature sig{}; MNHFTx() = default; - bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const; + bool Verify(const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const; SERIALIZE_METHODS(MNHFTx, obj) { @@ -63,6 +63,17 @@ class MNHFTxPayload uint8_t nVersion{CURRENT_VERSION}; MNHFTx signal; +public: + /** + * helper function to calculare Request ID used for signing + */ + uint256 GetRequestId() const; + + /** + * helper function to prepare special transaction for signing + */ + CMutableTransaction PrepareTx() const; + SERIALIZE_METHODS(MNHFTxPayload, obj) { READWRITE(obj.nVersion, obj.signal); @@ -120,6 +131,7 @@ class CMNHFManager * This member function is not const because it calls non-const GetFromCache() */ Signals GetSignalsStage(const CBlockIndex* const pindexPrev); + private: void AddToCache(const Signals& signals, const CBlockIndex* const pindex); @@ -129,7 +141,6 @@ class CMNHFManager * validate them by */ Signals GetFromCache(const CBlockIndex* const pindex); - }; std::optional extractEHFSignal(const CTransaction& tx); diff --git a/src/init.cpp b/src/init.cpp index 88ca19588d5d..39b80605e8ba 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1938,7 +1938,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc llmq::quorumSnapshotManager.reset(); llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*node.evodb)); node.llmq_ctx.reset(); - node.llmq_ctx.reset(new LLMQContext(chainman.ActiveChainstate(), *node.connman, *node.evodb, *::sporkManager, *node.mempool, node.peerman, false, fReset || fReindexChainState)); + node.llmq_ctx.reset(new LLMQContext(chainman.ActiveChainstate(), *node.connman, *node.evodb, *::sporkManager, *node.mnhf_manager, *node.mempool, node.peerman, false, fReset || fReindexChainState)); if (fReset) { pblocktree->WriteReindexing(true); diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 5d322180b3cb..9fb47e9ece34 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,7 +20,7 @@ #include #include -LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool, +LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CMNHFManager& mnhfman, CTxMemPool& mempool, const std::unique_ptr& peerman, bool unit_tests, bool wipe) : bls_worker{std::make_shared()}, dkg_debugman{std::make_unique()}, @@ -45,7 +46,8 @@ LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo assert(llmq::quorumInstantSendManager == nullptr); llmq::quorumInstantSendManager = std::make_unique(*llmq::chainLocksHandler, chainstate, connman, *llmq::quorumManager, *sigman, *shareman, sporkman, mempool, *::masternodeSync, peerman, unit_tests, wipe); return llmq::quorumInstantSendManager.get(); - }()} + }()}, + ehfSignalsHandler{std::make_unique(chainstate, connman, *sigman, *shareman, *llmq::quorumManager, mempool, mnhfman)} { // NOTE: we use this only to wipe the old db, do NOT use it for anything else // TODO: remove it in some future version diff --git a/src/llmq/context.h b/src/llmq/context.h index f1639fc4b790..f29df912f9de 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -12,25 +12,28 @@ class CChainState; class CConnman; class CDBWrapper; class CEvoDB; -class CTxMemPool; +class CMNHFManager; class CSporkManager; +class CTxMemPool; class PeerManager; namespace llmq { +class CChainLocksHandler; class CDKGDebugManager; -class CQuorumBlockProcessor; class CDKGSessionManager; +class CEHFSignalsHandler; +class CInstantSendManager; +class CQuorumBlockProcessor; class CQuorumManager; class CSigSharesManager; class CSigningManager; -class CChainLocksHandler; -class CInstantSendManager; } struct LLMQContext { LLMQContext() = delete; LLMQContext(const LLMQContext&) = delete; - LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool, + LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, + CMNHFManager& mnhfman, CTxMemPool& mempool, const std::unique_ptr& peerman, bool unit_tests, bool wipe); ~LLMQContext(); @@ -57,6 +60,7 @@ struct LLMQContext { const std::unique_ptr shareman; llmq::CChainLocksHandler* const clhandler; llmq::CInstantSendManager* const isman; + const std::unique_ptr ehfSignalsHandler; }; #endif // BITCOIN_LLMQ_CONTEXT_H diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp new file mode 100644 index 000000000000..4b6c76459cf3 --- /dev/null +++ b/src/llmq/ehf_signals.cpp @@ -0,0 +1,131 @@ +// Copyright (c) 2023 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 // g_txindex + +#include +#include +#include + +namespace llmq { + + +CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, + CSigningManager& sigman, CSigSharesManager& shareman, + CQuorumManager& qman, CTxMemPool& mempool, + CMNHFManager& mnhfManager) : + chainstate(chainstate), + connman(connman), + sigman(sigman), + shareman(shareman), + qman(qman), + mempool(mempool), + mnhfManager(mnhfManager) +{ + sigman.RegisterRecoveredSigsListener(this); +} + + +CEHFSignalsHandler::~CEHFSignalsHandler() +{ + sigman.UnregisterRecoveredSigsListener(this); +} + +void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew) +{ + if (!fMasternodeMode) { + return; + } + + if (!llmq::utils::IsV20Active(pindexNew)) { + return; + } + + + trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew); +} + +void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex) +{ + MNHFTxPayload mnhfPayload; + mnhfPayload.signal.versionBit = bit; + const uint256 requestId = mnhfPayload.GetRequestId(); + + LogPrintf("CEHFSignalsHandler::trySignEHFSignal: bit=%d at height=%d id=%s\n", bit, pindex->nHeight, requestId.ToString()); + + const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf; + const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType); + if (!llmq_params_opt.has_value()) { + return; + } + if (sigman.HasRecoveredSigForId(llmqType, requestId)) { + ids.insert(requestId); + + // no need to sign same message one more time + return; + } + + const auto quorum = sigman.SelectQuorumForSigning(llmq_params_opt.value(), qman, requestId); + if (!quorum) { + LogPrintf("CEHFSignalsHandler::trySignEHFSignal no quorum for id=%s\n", requestId.ToString()); + return; + } + + mnhfPayload.signal.quorumHash = quorum->qc->quorumHash; + const uint256 msgHash = mnhfPayload.PrepareTx().GetHash(); + + ids.insert(requestId); + sigman.AsyncSignIfMember(llmqType, shareman, requestId, msgHash); +} + +void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) +{ + if (g_txindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } + + if (ids.find(recoveredSig.getId()) == ids.end()) { + // Do nothing, it's not for this handler + return; + } + + MNHFTxPayload mnhfPayload; + mnhfPayload.signal.versionBit = Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit; + + const uint256 expectedId = mnhfPayload.GetRequestId(); + LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig expecting ID=%s received=%s\n", expectedId.ToString(), recoveredSig.getId().ToString()); + if (recoveredSig.getId() != mnhfPayload.GetRequestId()) { + // there's nothing interesting for CEHFSignalsHandler + LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig id is known but it's not MN_RR, expected: %s\n", mnhfPayload.GetRequestId().ToString()); + return; + } + + mnhfPayload.signal.quorumHash = recoveredSig.getQuorumHash(); + mnhfPayload.signal.sig = recoveredSig.sig.Get(); + + CMutableTransaction tx = mnhfPayload.PrepareTx(); + + { + CTransactionRef tx_to_sent = MakeTransactionRef(std::move(tx)); + LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig Special EHF TX is created hash=%s\n", tx_to_sent->GetHash().ToString()); + LOCK(cs_main); + TxValidationState state; + if (AcceptToMemoryPool(chainstate, mempool, state, tx_to_sent, /* bypass_limits=*/ false, /* nAbsurdFee=*/ 0)) { + connman.RelayTransaction(*tx_to_sent); + } else { + LogPrintf("CEHFSignalsHandler::HandleNewRecoveredSig -- AcceptToMemoryPool failed: %s\n", state.ToString()); + } + } +} +} // namespace llmq diff --git a/src/llmq/ehf_signals.h b/src/llmq/ehf_signals.h new file mode 100644 index 000000000000..49242e35dc55 --- /dev/null +++ b/src/llmq/ehf_signals.h @@ -0,0 +1,61 @@ +// Copyright (c) 2023 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_LLMQ_EHF_SIGNALS_H +#define BITCOIN_LLMQ_EHF_SIGNALS_H + +#include + +#include + +class CBlockIndex; +class CChainState; +class CConnman; +class CMNHFManager; +class CTxMemPool; + +namespace llmq +{ +class CQuorumManager; +class CSigSharesManager; +class CSigningManager; + +class CEHFSignalsHandler : public CRecoveredSigsListener +{ +private: + CChainState& chainstate; + CConnman& connman; + CSigningManager& sigman; + CSigSharesManager& shareman; + CQuorumManager& qman; + CTxMemPool& mempool; + CMNHFManager& mnhfManager; + + /** + * keep freshly generated IDs for easier filter sigs in HandleNewRecoveredSig + */ + std::set ids; +public: + explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, + CSigningManager& sigman, CSigSharesManager& shareman, + CQuorumManager& qman, CTxMemPool& mempool, + CMNHFManager& mnhfManager); + ~CEHFSignalsHandler(); + + + /** + * Since Tip is updated it could be a time to generate EHF Signal + */ + void UpdatedBlockTip(const CBlockIndex* const pindexNew); + + void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) override LOCKS_EXCLUDED(cs); + +private: + void trySignEHFSignal(int bit, const CBlockIndex* const pindex); + +}; + +} // namespace llmq + +#endif // BITCOIN_LLMQ_EHF_SIGNALS_H diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index c077f7bd7f34..1e33da979710 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -110,7 +110,7 @@ void DashTestSetup(NodeContext& node) node.cj_ctx = std::make_unique(chainstate, *node.connman, *node.mempool, *::masternodeSync, /* relay_txes */ true); ::deterministicMNManager = std::make_unique(chainstate, *node.connman, *node.evodb); - node.llmq_ctx = std::make_unique(chainstate, *node.connman, *node.evodb, *sporkManager, *node.mempool, node.peerman, true, false); + node.llmq_ctx = std::make_unique(chainstate, *node.connman, *node.evodb, *sporkManager, *node.mnhf_manager, *node.mempool, node.peerman, true, false); } void DashTestSetupClose(NodeContext& node) diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index bfbd2e3b12a9..0b6eb3e8e7e1 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -5,6 +5,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import copy +import time import struct from decimal import Decimal from io import BytesIO @@ -41,6 +42,7 @@ assert_equal, assert_greater_than, assert_greater_than_or_equal, + get_bip9_details, hex_str_to_bytes, ) @@ -83,7 +85,6 @@ def create_assetlock(self, coin, amount, pubkey): lock_tx.vExtraPayload = lockTx_payload.serialize() lock_tx = node_wallet.signrawtransactionwithwallet(lock_tx.serialize().hex()) - self.log.info(f"next tx: {lock_tx} payload: {lockTx_payload}") return FromHex(CTransaction(), lock_tx["hex"]) @@ -295,8 +296,16 @@ def run_test(self): self.log.info("Mine a quorum...") self.mine_quorum() - self.validate_credit_pool_balance(locked_1) + self.log.info("Quorum is mined, need to wait until EHF MN RR will be mined to avoid influence to mempool") + mn_rr_status = 0 + while mn_rr_status == 0: + time.sleep(1) + mn_rr_status = get_bip9_details(node, 'mn_rr')['EHF'] + self.log.info(f"BIP9 MN_RR status: {mn_rr_status}") + node.generate(1) + self.sync_all() + self.validate_credit_pool_balance(locked_1) self.log.info("Testing asset unlock...") @@ -455,6 +464,7 @@ def run_test(self): self.sync_mempools() node.generate(1) self.sync_all() + self.log.info(f"MN RR status: {get_bip9_details(node, 'mn_rr')}") new_total = self.get_credit_pool_balance() amount_actually_withdrawn = total - new_total diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 9498013e8666..21556ee24f41 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -162,9 +162,13 @@ def run_test(self): assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') ehf_tx_sent = self.send_tx(ehf_tx) + self.log.info(f"ehf tx: {ehf_tx_sent}") ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx) + self.log.info(f"unknown ehf tx: {ehf_unknown_tx_sent}") self.send_tx(ehf_invalid_tx, expected_error='bad-mnhf-non-ehf') - ehf_blockhash = node.generate(1)[0] + self.sync_all() + ehf_blockhash = self.nodes[1].generate(1)[0] + self.sync_blocks() self.sync_all() self.log.info(f"Check MnEhfTx {ehf_tx_sent} was mined in {ehf_blockhash}") @@ -198,6 +202,7 @@ def run_test(self): self.restart_all_nodes() self.check_fork('active') + assert_equal('active', get_bip9_details(node, 'mn_rr')['status']) fork_active_blockhash = node.getbestblockhash() self.log.info(f"Invalidate block: {ehf_blockhash} with tip {fork_active_blockhash}") From 7b942f7d660b6632305f138ca3f0b23e75c456cf Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 6 Oct 2023 03:07:18 +0700 Subject: [PATCH 04/10] feat: new spork SPORK_24_MN_RR_READY and related logic This spork is used to signalize EhfSignalsManager that EHF signal for fork MN_RR can be signed and send to network now (platform is ready) --- src/llmq/context.cpp | 2 +- src/llmq/ehf_signals.cpp | 13 +++++++----- src/llmq/ehf_signals.h | 4 +++- src/spork.h | 4 +++- src/test/block_reward_reallocation_tests.cpp | 3 +++ test/functional/feature_asset_locks.py | 20 ++++++------------- test/functional/feature_llmq_evo.py | 1 + test/functional/feature_mnehf.py | 11 ++++++++-- .../test_framework/test_framework.py | 17 ++++++++++++++-- 9 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 9fb47e9ece34..209941c601ab 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -47,7 +47,7 @@ LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo llmq::quorumInstantSendManager = std::make_unique(*llmq::chainLocksHandler, chainstate, connman, *llmq::quorumManager, *sigman, *shareman, sporkman, mempool, *::masternodeSync, peerman, unit_tests, wipe); return llmq::quorumInstantSendManager.get(); }()}, - ehfSignalsHandler{std::make_unique(chainstate, connman, *sigman, *shareman, *llmq::quorumManager, mempool, mnhfman)} + ehfSignalsHandler{std::make_unique(chainstate, connman, *sigman, *shareman, sporkman, *llmq::quorumManager, mempool, mnhfman)} { // NOTE: we use this only to wipe the old db, do NOT use it for anything else // TODO: remove it in some future version diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index 4b6c76459cf3..96daf94ec7d3 100644 --- a/src/llmq/ehf_signals.cpp +++ b/src/llmq/ehf_signals.cpp @@ -14,21 +14,23 @@ #include // g_txindex -#include -#include #include +#include +#include +#include namespace llmq { CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, CSigningManager& sigman, CSigSharesManager& shareman, - CQuorumManager& qman, CTxMemPool& mempool, + CSporkManager& sporkman, CQuorumManager& qman, CTxMemPool& mempool, CMNHFManager& mnhfManager) : chainstate(chainstate), connman(connman), sigman(sigman), shareman(shareman), + sporkman(sporkman), qman(qman), mempool(mempool), mnhfManager(mnhfManager) @@ -52,8 +54,9 @@ void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew) return; } - - trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew); + if (sporkman.IsSporkActive(SPORK_24_MN_RR_READY)) { + trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew); + } } void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex) diff --git a/src/llmq/ehf_signals.h b/src/llmq/ehf_signals.h index 49242e35dc55..f5e033eddc65 100644 --- a/src/llmq/ehf_signals.h +++ b/src/llmq/ehf_signals.h @@ -13,6 +13,7 @@ class CBlockIndex; class CChainState; class CConnman; class CMNHFManager; +class CSporkManager; class CTxMemPool; namespace llmq @@ -28,6 +29,7 @@ class CEHFSignalsHandler : public CRecoveredSigsListener CConnman& connman; CSigningManager& sigman; CSigSharesManager& shareman; + CSporkManager& sporkman; CQuorumManager& qman; CTxMemPool& mempool; CMNHFManager& mnhfManager; @@ -39,7 +41,7 @@ class CEHFSignalsHandler : public CRecoveredSigsListener public: explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, CSigningManager& sigman, CSigSharesManager& shareman, - CQuorumManager& qman, CTxMemPool& mempool, + CSporkManager& sporkman, CQuorumManager& qman, CTxMemPool& mempool, CMNHFManager& mnhfManager); ~CEHFSignalsHandler(); diff --git a/src/spork.h b/src/spork.h index 7d63de440cb9..8c8e5d796a06 100644 --- a/src/spork.h +++ b/src/spork.h @@ -41,6 +41,7 @@ enum SporkId : int32_t { SPORK_19_CHAINLOCKS_ENABLED = 10018, SPORK_21_QUORUM_ALL_CONNECTED = 10020, SPORK_23_QUORUM_POSE = 10022, + SPORK_24_MN_RR_READY = 10023, SPORK_INVALID = -1, }; @@ -66,7 +67,7 @@ struct CSporkDef }; #define MAKE_SPORK_DEF(name, defaultValue) CSporkDef{name, defaultValue, #name} -[[maybe_unused]] static constexpr std::array sporkDefs = { +[[maybe_unused]] static constexpr std::array sporkDefs = { MAKE_SPORK_DEF(SPORK_2_INSTANTSEND_ENABLED, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_3_INSTANTSEND_BLOCK_FILTERING, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_9_SUPERBLOCKS_ENABLED, 4070908800ULL), // OFF @@ -74,6 +75,7 @@ struct CSporkDef MAKE_SPORK_DEF(SPORK_19_CHAINLOCKS_ENABLED, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_21_QUORUM_ALL_CONNECTED, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_23_QUORUM_POSE, 4070908800ULL), // OFF + MAKE_SPORK_DEF(SPORK_24_MN_RR_READY, 4070908800ULL), // OFF }; #undef MAKE_SPORK_DEF extern std::unique_ptr sporkManager; diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index 565ebadaf727..d77ff0d88f36 100644 --- a/src/test/block_reward_reallocation_tests.cpp +++ b/src/test/block_reward_reallocation_tests.cpp @@ -265,6 +265,9 @@ BOOST_FIXTURE_TEST_CASE(block_reward_reallocation, TestChainBRRBeforeActivationS } BOOST_CHECK(!llmq::utils::IsMNRewardReallocationActive(m_node.chainman->ActiveChain().Tip())); + // Activate EHF "MN_RR" + Params().UpdateMNActivationParam(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, ::ChainActive().Height(), ::ChainActive().Tip()->GetMedianTimePast(), /*fJustCheck=*/ false); + // Reward split should stay ~60/40 after reallocation is done, // check 10 next superblocks for ([[maybe_unused]] auto i : irange::range(10)) { diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index 0b6eb3e8e7e1..0e1f5a69ddad 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -5,7 +5,6 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. import copy -import time import struct from decimal import Decimal from io import BytesIO @@ -296,14 +295,6 @@ def run_test(self): self.log.info("Mine a quorum...") self.mine_quorum() - self.log.info("Quorum is mined, need to wait until EHF MN RR will be mined to avoid influence to mempool") - mn_rr_status = 0 - while mn_rr_status == 0: - time.sleep(1) - mn_rr_status = get_bip9_details(node, 'mn_rr')['EHF'] - self.log.info(f"BIP9 MN_RR status: {mn_rr_status}") - node.generate(1) - self.sync_all() self.validate_credit_pool_balance(locked_1) @@ -437,8 +428,8 @@ def run_test(self): self.log.info(f"Collecting coins in pool... Collected {total}/{10_900 * COIN}") coin = coins.pop() to_lock = int(coin['amount'] * COIN) - tiny_amount - if to_lock > 50 * COIN: - to_lock = 50 * COIN + if to_lock > 99 * COIN: + to_lock = 99 * COIN total += to_lock tx = self.create_assetlock(coin, to_lock, pubkey) self.send_tx_simple(tx) @@ -509,13 +500,14 @@ def run_test(self): node.generate(1) self.sync_all() - self.log.info("generate many blocks to be sure that mempool is empty afterwards...") + self.log.info("generate many blocks to be sure that mempool is empty after expiring txes...") self.slowly_generate_batch(60) self.log.info("Checking that credit pool is not changed...") assert_equal(new_total, self.get_credit_pool_balance()) self.check_mempool_size() - self.activate_mn_rr(expected_activation_height=3090) + # activate MN_RR reallocation + self.activate_mn_rr() self.log.info(f'height: {node.getblockcount()} credit: {self.get_credit_pool_balance()}') bt = node.getblocktemplate() platform_reward = bt['masternode'][0]['amount'] @@ -525,7 +517,7 @@ def run_test(self): all_mn_rewards = platform_reward + owner_reward + operator_reward assert_equal(all_mn_rewards, bt['coinbasevalue'] * 3 // 4) # 75/25 mn/miner reward split assert_equal(platform_reward, all_mn_rewards * 375 // 1000) # 0.375 platform share - assert_equal(platform_reward, 25553999) + assert_equal(platform_reward, 29636590) assert_equal(new_total, self.get_credit_pool_balance()) node.generate(1) self.sync_all() diff --git a/test/functional/feature_llmq_evo.py b/test/functional/feature_llmq_evo.py index 70017950f59f..13086fd4cf83 100755 --- a/test/functional/feature_llmq_evo.py +++ b/test/functional/feature_llmq_evo.py @@ -114,6 +114,7 @@ def run_test(self): self.log.info("Test that EvoNodes are paid 4x blocks in a row") self.test_evo_payments(window_analysis=256) + self.activate_v20() self.activate_mn_rr() self.log.info("Activated MN RewardReallocation at height:" + str(self.nodes[0].getblockcount())) diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 21556ee24f41..11e0a4c7c97a 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -160,6 +160,7 @@ def run_test(self): assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') self.activate_v20() assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') + assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'defined') ehf_tx_sent = self.send_tx(ehf_tx) self.log.info(f"ehf tx: {ehf_tx_sent}") @@ -202,8 +203,6 @@ def run_test(self): self.restart_all_nodes() self.check_fork('active') - assert_equal('active', get_bip9_details(node, 'mn_rr')['status']) - fork_active_blockhash = node.getbestblockhash() self.log.info(f"Invalidate block: {ehf_blockhash} with tip {fork_active_blockhash}") for inode in self.nodes: @@ -256,6 +255,12 @@ def run_test(self): self.mine_quorum() ehf_tx_new_start = self.create_mnehf(28, pubkey) + + self.log.info("activate MN_RR also by enabling spork 24") + assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'defined') + self.nodes[0].sporkupdate("SPORK_24_MN_RR_READY", 0) + self.wait_for_sporks_same() + self.check_fork('defined') self.log.info("Mine one block and ensure EHF tx for the new deployment is mined") @@ -268,6 +273,8 @@ def run_test(self): self.check_fork('defined') self.slowly_generate_batch(12 * 4) self.check_fork('active') + self.log.info(f"bip9: {get_bip9_details(node, 'mn_rr')}") + assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'active') if __name__ == '__main__': diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 3ab804ab028b..3e233b566137 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -44,6 +44,7 @@ check_json_precision, copy_datadir, force_finish_mnsync, + get_bip9_details, get_datadir_path, hex_str_to_bytes, initialize_datadir, @@ -1121,8 +1122,20 @@ def activate_v19(self, expected_activation_height=None): def activate_v20(self, expected_activation_height=None): self.activate_by_name('v20', expected_activation_height) - def activate_mn_rr(self, expected_activation_height=None): - self.activate_by_name('mn_rr', expected_activation_height) + def activate_mn_rr(self): + self.nodes[0].sporkupdate("SPORK_24_MN_RR_READY", 0) + self.wait_for_sporks_same() + + mn_rr_status = 0 + while mn_rr_status == 0: + time.sleep(1) + mn_rr_status = get_bip9_details(self.nodes[0], 'mn_rr')['EHF'] + self.nodes[0].generate(1) + self.sync_all() + # only couple extra blocks are needed, so, helper `activate_by_name` is not needed + while get_bip9_details(self.nodes[0], 'mn_rr')['status'] != 'active': + self.nodes[0].generate(1) + self.sync_all() def set_dash_llmq_test_params(self, llmq_size, llmq_threshold): self.llmq_size = llmq_size From 2ca70f1e39a7b4746ae0f8a7bfe92f864ca36565 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 9 Oct 2023 21:52:05 +0700 Subject: [PATCH 05/10] fix: adds a mutex to avoid using `ids` in ehf_signals without locks --- src/llmq/ehf_signals.cpp | 8 ++++++-- src/llmq/ehf_signals.h | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index 96daf94ec7d3..5329a9b55b63 100644 --- a/src/llmq/ehf_signals.cpp +++ b/src/llmq/ehf_signals.cpp @@ -73,6 +73,7 @@ void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pind return; } if (sigman.HasRecoveredSigForId(llmqType, requestId)) { + LOCK(cs); ids.insert(requestId); // no need to sign same message one more time @@ -88,7 +89,10 @@ void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pind mnhfPayload.signal.quorumHash = quorum->qc->quorumHash; const uint256 msgHash = mnhfPayload.PrepareTx().GetHash(); - ids.insert(requestId); + { + LOCK(cs); + ids.insert(requestId); + } sigman.AsyncSignIfMember(llmqType, shareman, requestId, msgHash); } @@ -98,7 +102,7 @@ void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig g_txindex->BlockUntilSyncedToCurrentChain(); } - if (ids.find(recoveredSig.getId()) == ids.end()) { + if (WITH_LOCK(cs, return ids.find(recoveredSig.getId()) == ids.end())) { // Do nothing, it's not for this handler return; } diff --git a/src/llmq/ehf_signals.h b/src/llmq/ehf_signals.h index f5e033eddc65..dfb0fe6503e2 100644 --- a/src/llmq/ehf_signals.h +++ b/src/llmq/ehf_signals.h @@ -38,6 +38,7 @@ class CEHFSignalsHandler : public CRecoveredSigsListener * keep freshly generated IDs for easier filter sigs in HandleNewRecoveredSig */ std::set ids; + mutable Mutex cs; public: explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, CSigningManager& sigman, CSigSharesManager& shareman, From f42fb83604696d87dbd80d16a0898f63c37ca512 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 10 Oct 2023 14:35:12 +0700 Subject: [PATCH 06/10] fix: missing LOCKS_EXCLUDED --- src/llmq/ehf_signals.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llmq/ehf_signals.h b/src/llmq/ehf_signals.h index dfb0fe6503e2..abfb4e7b53b2 100644 --- a/src/llmq/ehf_signals.h +++ b/src/llmq/ehf_signals.h @@ -55,7 +55,7 @@ class CEHFSignalsHandler : public CRecoveredSigsListener void HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) override LOCKS_EXCLUDED(cs); private: - void trySignEHFSignal(int bit, const CBlockIndex* const pindex); + void trySignEHFSignal(int bit, const CBlockIndex* const pindex) LOCKS_EXCLUDED(cs); }; From f8718694502ca9ff94a4252ee2321b8dd1dfd821 Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Mon, 9 Oct 2023 21:32:07 +0300 Subject: [PATCH 07/10] suggestions --- src/chainparams.cpp | 18 +++++++++--------- src/llmq/ehf_signals.cpp | 12 ++++-------- src/llmq/ehf_signals.h | 2 +- src/rpc/blockchain.cpp | 2 +- src/spork.h | 4 ++-- src/test/block_reward_reallocation_tests.cpp | 2 +- test/functional/feature_asset_locks.py | 6 ++++-- test/functional/feature_mnehf.py | 3 ++- test/functional/rpc_blockchain.py | 6 +++--- .../test_framework/test_framework.py | 14 ++++++-------- 10 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 819ae3ab8bd3..25d78636b2be 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -219,9 +219,9 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 19999999999; // TODO: To be determined later consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 576; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 460; // 80% of 576 - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 346; // 60% of 576 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 3226; // 80% of 4032 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 2420; // 60% of 4032 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation @@ -589,9 +589,9 @@ class CDevNetParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 1661990400; // Sep 1st, 2022 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 1; // 80% of 100 - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 1; // 60% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 120; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 80; // 80% of 100 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 60; // 60% of 100 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation @@ -829,9 +829,9 @@ class CRegTestParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 0; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 1; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 1; // 100% of 1 - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 1; // 100% of 1 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 12; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 9; // 80% of 12 + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 7; // 60% of 7 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nMNActivationHeight = 0; // requires EHF activation diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index 5329a9b55b63..e1ef798096ec 100644 --- a/src/llmq/ehf_signals.cpp +++ b/src/llmq/ehf_signals.cpp @@ -46,17 +46,12 @@ CEHFSignalsHandler::~CEHFSignalsHandler() void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew) { - if (!fMasternodeMode) { + if (!fMasternodeMode || !llmq::utils::IsV20Active(pindexNew) || !sporkman.IsSporkActive(SPORK_24_EHF)) { return; } - if (!llmq::utils::IsV20Active(pindexNew)) { - return; - } - - if (sporkman.IsSporkActive(SPORK_24_MN_RR_READY)) { - trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew); - } + // TODO: should do this for all not-yet-signied bits + trySignEHFSignal(Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit, pindexNew); } void CEHFSignalsHandler::trySignEHFSignal(int bit, const CBlockIndex* const pindex) @@ -108,6 +103,7 @@ void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig } MNHFTxPayload mnhfPayload; + // TODO: should do this for all not-yet-signied bits mnhfPayload.signal.versionBit = Params().GetConsensus().vDeployments[Consensus::DEPLOYMENT_MN_RR].bit; const uint256 expectedId = mnhfPayload.GetRequestId(); diff --git a/src/llmq/ehf_signals.h b/src/llmq/ehf_signals.h index abfb4e7b53b2..32a3174d9a24 100644 --- a/src/llmq/ehf_signals.h +++ b/src/llmq/ehf_signals.h @@ -37,8 +37,8 @@ class CEHFSignalsHandler : public CRecoveredSigsListener /** * keep freshly generated IDs for easier filter sigs in HandleNewRecoveredSig */ - std::set ids; mutable Mutex cs; + std::set ids GUARDED_BY(cs); public: explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, CSigningManager& sigman, CSigSharesManager& shareman, diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c37bdb525d81..3a7717cd689a 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -1614,7 +1614,7 @@ static void BIP9SoftForkDescPushBack(const CBlockIndex* active_chain_tip, UniVal } bip9.pushKV("start_time", consensusParams.vDeployments[id].nStartTime); bip9.pushKV("timeout", consensusParams.vDeployments[id].nTimeout); - bip9.pushKV("EHF", consensusParams.vDeployments[id].nMNActivationHeight); + bip9.pushKV("ehf", consensusParams.vDeployments[id].nMNActivationHeight); int64_t since_height = VersionBitsStateSinceHeight(active_chain_tip, consensusParams, id, versionbitscache); bip9.pushKV("since", since_height); if (ThresholdState::STARTED == thresholdState) diff --git a/src/spork.h b/src/spork.h index 8c8e5d796a06..2b8f94d1d949 100644 --- a/src/spork.h +++ b/src/spork.h @@ -41,7 +41,7 @@ enum SporkId : int32_t { SPORK_19_CHAINLOCKS_ENABLED = 10018, SPORK_21_QUORUM_ALL_CONNECTED = 10020, SPORK_23_QUORUM_POSE = 10022, - SPORK_24_MN_RR_READY = 10023, + SPORK_24_EHF = 10023, SPORK_INVALID = -1, }; @@ -75,7 +75,7 @@ struct CSporkDef MAKE_SPORK_DEF(SPORK_19_CHAINLOCKS_ENABLED, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_21_QUORUM_ALL_CONNECTED, 4070908800ULL), // OFF MAKE_SPORK_DEF(SPORK_23_QUORUM_POSE, 4070908800ULL), // OFF - MAKE_SPORK_DEF(SPORK_24_MN_RR_READY, 4070908800ULL), // OFF + MAKE_SPORK_DEF(SPORK_24_EHF, 4070908800ULL), // OFF }; #undef MAKE_SPORK_DEF extern std::unique_ptr sporkManager; diff --git a/src/test/block_reward_reallocation_tests.cpp b/src/test/block_reward_reallocation_tests.cpp index d77ff0d88f36..4092dad8ffa5 100644 --- a/src/test/block_reward_reallocation_tests.cpp +++ b/src/test/block_reward_reallocation_tests.cpp @@ -40,7 +40,7 @@ using SimpleUTXOMap = std::map>; struct TestChainBRRBeforeActivationSetup : public TestChainSetup { // Force fast DIP3 activation - TestChainBRRBeforeActivationSetup() : TestChainSetup(497, {"-dip3params=30:50"}) {} + TestChainBRRBeforeActivationSetup() : TestChainSetup(497, {"-dip3params=30:50", "-vbparams=mn_rr:0:999999999999:20:16:12:5:0"}) {} }; static SimpleUTXOMap BuildSimpleUtxoMap(const std::vector& txs) diff --git a/test/functional/feature_asset_locks.py b/test/functional/feature_asset_locks.py index 0e1f5a69ddad..a367bf666468 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -455,7 +455,7 @@ def run_test(self): self.sync_mempools() node.generate(1) self.sync_all() - self.log.info(f"MN RR status: {get_bip9_details(node, 'mn_rr')}") + self.log.info(f"MN_RR status: {get_bip9_details(node, 'mn_rr')}") new_total = self.get_credit_pool_balance() amount_actually_withdrawn = total - new_total @@ -507,8 +507,10 @@ def run_test(self): self.check_mempool_size() # activate MN_RR reallocation - self.activate_mn_rr() + self.activate_mn_rr(expected_activation_height=node.getblockcount() + 12 * 3) self.log.info(f'height: {node.getblockcount()} credit: {self.get_credit_pool_balance()}') + assert_equal(new_total, self.get_credit_pool_balance()) + bt = node.getblocktemplate() platform_reward = bt['masternode'][0]['amount'] assert_equal(bt['masternode'][0]['script'], '6a') # empty OP_RETURN diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 11e0a4c7c97a..2fabdf367a73 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -203,6 +203,7 @@ def run_test(self): self.restart_all_nodes() self.check_fork('active') + fork_active_blockhash = node.getbestblockhash() self.log.info(f"Invalidate block: {ehf_blockhash} with tip {fork_active_blockhash}") for inode in self.nodes: @@ -258,7 +259,7 @@ def run_test(self): self.log.info("activate MN_RR also by enabling spork 24") assert_equal(get_bip9_details(node, 'mn_rr')['status'], 'defined') - self.nodes[0].sporkupdate("SPORK_24_MN_RR_READY", 0) + self.nodes[0].sporkupdate("SPORK_24_EHF", 0) self.wait_for_sporks_same() self.check_fork('defined') diff --git a/test/functional/rpc_blockchain.py b/test/functional/rpc_blockchain.py index fe758a79116c..1c16d6ce1bac 100755 --- a/test/functional/rpc_blockchain.py +++ b/test/functional/rpc_blockchain.py @@ -147,7 +147,7 @@ def _test_getblockchaininfo(self): 'start_time': 0, 'timeout': 9223372036854775807, 'since': 0, - 'EHF': -1, + 'ehf': -1, }, 'active': False}, 'mn_rr': { 'type': 'bip9', @@ -156,7 +156,7 @@ def _test_getblockchaininfo(self): 'start_time': 0, 'timeout': 9223372036854775807, 'since': 0, - 'EHF': 0, + 'ehf': 0, }, 'active': False}, 'testdummy': { @@ -174,7 +174,7 @@ def _test_getblockchaininfo(self): 'count': 57, 'possible': True, }, - 'EHF': -1, + 'ehf': -1, }, 'active': False}, }) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 3e233b566137..7ab5e47a0c64 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1071,6 +1071,7 @@ def activate_dip8(self, slow_mode=False): self.sync_blocks() def activate_by_name(self, name, expected_activation_height=None): + assert not softfork_active(self.nodes[0], name) self.log.info("Wait for " + name + " activation") # disable spork17 while mining blocks to activate "name" to prevent accidental quorum formation @@ -1083,6 +1084,7 @@ def activate_by_name(self, name, expected_activation_height=None): batch_size = 10 if expected_activation_height is not None: height = self.nodes[0].getblockcount() + assert height < expected_activation_height # NOTE: getblockchaininfo shows softforks active at block (window * 3 - 1) # since it's returning whether a softwork is active for the _next_ block. # Hence the last block prior to the activation is (expected_activation_height - 2). @@ -1122,20 +1124,16 @@ def activate_v19(self, expected_activation_height=None): def activate_v20(self, expected_activation_height=None): self.activate_by_name('v20', expected_activation_height) - def activate_mn_rr(self): - self.nodes[0].sporkupdate("SPORK_24_MN_RR_READY", 0) + def activate_mn_rr(self, expected_activation_height=None): + self.nodes[0].sporkupdate("SPORK_24_EHF", 0) self.wait_for_sporks_same() - mn_rr_status = 0 while mn_rr_status == 0: time.sleep(1) - mn_rr_status = get_bip9_details(self.nodes[0], 'mn_rr')['EHF'] - self.nodes[0].generate(1) - self.sync_all() - # only couple extra blocks are needed, so, helper `activate_by_name` is not needed - while get_bip9_details(self.nodes[0], 'mn_rr')['status'] != 'active': + mn_rr_status = get_bip9_details(self.nodes[0], 'mn_rr')['ehf'] self.nodes[0].generate(1) self.sync_all() + self.activate_by_name('mn_rr', expected_activation_height) def set_dash_llmq_test_params(self, llmq_size, llmq_threshold): self.llmq_size = llmq_size From bc68f4d7c4d9128fb596b28718431ec192c3f396 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 11 Oct 2023 16:10:26 +0700 Subject: [PATCH 08/10] fix: add TODO about window size. 2016 to setup maybe? --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 25d78636b2be..6893dacf8962 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -219,7 +219,7 @@ class CMainParams : public CChainParams { consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].bit = 10; consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nStartTime = 19999999999; // TODO: To be determined later consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nTimeout = Consensus::BIP9Deployment::NO_TIMEOUT; - consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; + consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nWindowSize = 4032; // TODO to be determined before v20 release: choose nWindowSize/nThresholdStart/nThresholdMin consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdStart = 3226; // 80% of 4032 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nThresholdMin = 2420; // 60% of 4032 consensus.vDeployments[Consensus::DEPLOYMENT_MN_RR].nFalloffCoeff = 5; // this corresponds to 10 periods From 34c66ff3693f90b315d1fa9c62addf5913b7e543 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Tue, 17 Oct 2023 20:34:07 +0700 Subject: [PATCH 09/10] fix(trivial): comment src/evo/mnhftx.h Co-authored-by: PastaPastaPasta <6443210+PastaPastaPasta@users.noreply.github.com> --- src/evo/mnhftx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index 1622bf5da60b..131f050cf4bf 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -65,7 +65,7 @@ class MNHFTxPayload public: /** - * helper function to calculare Request ID used for signing + * helper function to calculate Request ID used for signing */ uint256 GetRequestId() const; From 7e96c96a60c06ab0692a4a419cfd92015923e75f Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 18 Oct 2023 00:48:56 +0700 Subject: [PATCH 10/10] fix: added missing const and removed non-used argument in CEHFSignalsHandler constructor --- src/init.cpp | 2 +- src/llmq/context.cpp | 4 ++-- src/llmq/context.h | 3 +-- src/llmq/ehf_signals.cpp | 6 ++---- src/llmq/ehf_signals.h | 9 +++------ src/test/util/setup_common.cpp | 2 +- 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/init.cpp b/src/init.cpp index 39b80605e8ba..88ca19588d5d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -1938,7 +1938,7 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc llmq::quorumSnapshotManager.reset(); llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*node.evodb)); node.llmq_ctx.reset(); - node.llmq_ctx.reset(new LLMQContext(chainman.ActiveChainstate(), *node.connman, *node.evodb, *::sporkManager, *node.mnhf_manager, *node.mempool, node.peerman, false, fReset || fReindexChainState)); + node.llmq_ctx.reset(new LLMQContext(chainman.ActiveChainstate(), *node.connman, *node.evodb, *::sporkManager, *node.mempool, node.peerman, false, fReset || fReindexChainState)); if (fReset) { pblocktree->WriteReindexing(true); diff --git a/src/llmq/context.cpp b/src/llmq/context.cpp index 209941c601ab..2162f36ed366 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -20,7 +20,7 @@ #include #include -LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CMNHFManager& mnhfman, CTxMemPool& mempool, +LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, CTxMemPool& mempool, const std::unique_ptr& peerman, bool unit_tests, bool wipe) : bls_worker{std::make_shared()}, dkg_debugman{std::make_unique()}, @@ -47,7 +47,7 @@ LLMQContext::LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo llmq::quorumInstantSendManager = std::make_unique(*llmq::chainLocksHandler, chainstate, connman, *llmq::quorumManager, *sigman, *shareman, sporkman, mempool, *::masternodeSync, peerman, unit_tests, wipe); return llmq::quorumInstantSendManager.get(); }()}, - ehfSignalsHandler{std::make_unique(chainstate, connman, *sigman, *shareman, sporkman, *llmq::quorumManager, mempool, mnhfman)} + ehfSignalsHandler{std::make_unique(chainstate, connman, *sigman, *shareman, sporkman, *llmq::quorumManager, mempool)} { // NOTE: we use this only to wipe the old db, do NOT use it for anything else // TODO: remove it in some future version diff --git a/src/llmq/context.h b/src/llmq/context.h index f29df912f9de..10747651dd85 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -12,7 +12,6 @@ class CChainState; class CConnman; class CDBWrapper; class CEvoDB; -class CMNHFManager; class CSporkManager; class CTxMemPool; class PeerManager; @@ -33,7 +32,7 @@ struct LLMQContext { LLMQContext() = delete; LLMQContext(const LLMQContext&) = delete; LLMQContext(CChainState& chainstate, CConnman& connman, CEvoDB& evo_db, CSporkManager& sporkman, - CMNHFManager& mnhfman, CTxMemPool& mempool, + CTxMemPool& mempool, const std::unique_ptr& peerman, bool unit_tests, bool wipe); ~LLMQContext(); diff --git a/src/llmq/ehf_signals.cpp b/src/llmq/ehf_signals.cpp index e1ef798096ec..2274032e3c5e 100644 --- a/src/llmq/ehf_signals.cpp +++ b/src/llmq/ehf_signals.cpp @@ -24,16 +24,14 @@ namespace llmq { CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, CSigningManager& sigman, CSigSharesManager& shareman, - CSporkManager& sporkman, CQuorumManager& qman, CTxMemPool& mempool, - CMNHFManager& mnhfManager) : + const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool) : chainstate(chainstate), connman(connman), sigman(sigman), shareman(shareman), sporkman(sporkman), qman(qman), - mempool(mempool), - mnhfManager(mnhfManager) + mempool(mempool) { sigman.RegisterRecoveredSigsListener(this); } diff --git a/src/llmq/ehf_signals.h b/src/llmq/ehf_signals.h index 32a3174d9a24..91e0d9ac0599 100644 --- a/src/llmq/ehf_signals.h +++ b/src/llmq/ehf_signals.h @@ -12,7 +12,6 @@ class CBlockIndex; class CChainState; class CConnman; -class CMNHFManager; class CSporkManager; class CTxMemPool; @@ -29,10 +28,9 @@ class CEHFSignalsHandler : public CRecoveredSigsListener CConnman& connman; CSigningManager& sigman; CSigSharesManager& shareman; - CSporkManager& sporkman; - CQuorumManager& qman; + const CSporkManager& sporkman; + const CQuorumManager& qman; CTxMemPool& mempool; - CMNHFManager& mnhfManager; /** * keep freshly generated IDs for easier filter sigs in HandleNewRecoveredSig @@ -42,8 +40,7 @@ class CEHFSignalsHandler : public CRecoveredSigsListener public: explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, CSigningManager& sigman, CSigSharesManager& shareman, - CSporkManager& sporkman, CQuorumManager& qman, CTxMemPool& mempool, - CMNHFManager& mnhfManager); + const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool); ~CEHFSignalsHandler(); diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 1e33da979710..c077f7bd7f34 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -110,7 +110,7 @@ void DashTestSetup(NodeContext& node) node.cj_ctx = std::make_unique(chainstate, *node.connman, *node.mempool, *::masternodeSync, /* relay_txes */ true); ::deterministicMNManager = std::make_unique(chainstate, *node.connman, *node.evodb); - node.llmq_ctx = std::make_unique(chainstate, *node.connman, *node.evodb, *sporkManager, *node.mnhf_manager, *node.mempool, node.peerman, true, false); + node.llmq_ctx = std::make_unique(chainstate, *node.connman, *node.evodb, *sporkManager, *node.mempool, node.peerman, true, false); } void DashTestSetupClose(NodeContext& node)