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..6893dacf8962 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].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 + 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 @@ -591,6 +593,7 @@ class CDevNetParams : 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("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 = 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 // 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..131f050cf4bf 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 calculate 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/llmq/context.cpp b/src/llmq/context.cpp index 5d322180b3cb..2162f36ed366 100644 --- a/src/llmq/context.cpp +++ b/src/llmq/context.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -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, 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 f1639fc4b790..10747651dd85 100644 --- a/src/llmq/context.h +++ b/src/llmq/context.h @@ -12,25 +12,27 @@ class CChainState; class CConnman; class CDBWrapper; class CEvoDB; -class CTxMemPool; 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, + CTxMemPool& mempool, const std::unique_ptr& peerman, bool unit_tests, bool wipe); ~LLMQContext(); @@ -57,6 +59,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..2274032e3c5e --- /dev/null +++ b/src/llmq/ehf_signals.cpp @@ -0,0 +1,132 @@ +// 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 +#include + +namespace llmq { + + +CEHFSignalsHandler::CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, + CSigningManager& sigman, CSigSharesManager& shareman, + const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool) : + chainstate(chainstate), + connman(connman), + sigman(sigman), + shareman(shareman), + sporkman(sporkman), + qman(qman), + mempool(mempool) +{ + sigman.RegisterRecoveredSigsListener(this); +} + + +CEHFSignalsHandler::~CEHFSignalsHandler() +{ + sigman.UnregisterRecoveredSigsListener(this); +} + +void CEHFSignalsHandler::UpdatedBlockTip(const CBlockIndex* const pindexNew) +{ + if (!fMasternodeMode || !llmq::utils::IsV20Active(pindexNew) || !sporkman.IsSporkActive(SPORK_24_EHF)) { + return; + } + + // 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) +{ + 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)) { + LOCK(cs); + 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(); + + { + LOCK(cs); + ids.insert(requestId); + } + sigman.AsyncSignIfMember(llmqType, shareman, requestId, msgHash); +} + +void CEHFSignalsHandler::HandleNewRecoveredSig(const CRecoveredSig& recoveredSig) +{ + if (g_txindex) { + g_txindex->BlockUntilSyncedToCurrentChain(); + } + + if (WITH_LOCK(cs, return ids.find(recoveredSig.getId()) == ids.end())) { + // Do nothing, it's not for this handler + return; + } + + 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(); + 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..91e0d9ac0599 --- /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 CSporkManager; +class CTxMemPool; + +namespace llmq +{ +class CQuorumManager; +class CSigSharesManager; +class CSigningManager; + +class CEHFSignalsHandler : public CRecoveredSigsListener +{ +private: + CChainState& chainstate; + CConnman& connman; + CSigningManager& sigman; + CSigSharesManager& shareman; + const CSporkManager& sporkman; + const CQuorumManager& qman; + CTxMemPool& mempool; + + /** + * keep freshly generated IDs for easier filter sigs in HandleNewRecoveredSig + */ + mutable Mutex cs; + std::set ids GUARDED_BY(cs); +public: + explicit CEHFSignalsHandler(CChainState& chainstate, CConnman& connman, + CSigningManager& sigman, CSigSharesManager& shareman, + const CSporkManager& sporkman, const CQuorumManager& qman, CTxMemPool& mempool); + ~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) LOCKS_EXCLUDED(cs); + +}; + +} // namespace llmq + +#endif // BITCOIN_LLMQ_EHF_SIGNALS_H 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; } } diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 2a2168b167f1..3a7717cd689a 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/src/spork.h b/src/spork.h index 7d63de440cb9..2b8f94d1d949 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_EHF = 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_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 565ebadaf727..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) @@ -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 bfbd2e3b12a9..a367bf666468 100755 --- a/test/functional/feature_asset_locks.py +++ b/test/functional/feature_asset_locks.py @@ -41,6 +41,7 @@ assert_equal, assert_greater_than, assert_greater_than_or_equal, + get_bip9_details, hex_str_to_bytes, ) @@ -83,7 +84,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 +295,8 @@ def run_test(self): self.log.info("Mine a quorum...") self.mine_quorum() - self.validate_credit_pool_balance(locked_1) + self.validate_credit_pool_balance(locked_1) self.log.info("Testing asset unlock...") @@ -428,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) @@ -455,6 +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')}") new_total = self.get_credit_pool_balance() amount_actually_withdrawn = total - new_total @@ -499,14 +500,17 @@ 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(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 @@ -515,7 +519,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 9498013e8666..2fabdf367a73 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -160,11 +160,16 @@ 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}") 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}") @@ -251,6 +256,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_EHF", 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") @@ -263,6 +274,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/rpc_blockchain.py b/test/functional/rpc_blockchain.py index a342615b3520..1c16d6ce1bac 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}, }) diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 3ab804ab028b..7ab5e47a0c64 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, @@ -1070,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 @@ -1082,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,6 +1125,14 @@ 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.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() self.activate_by_name('mn_rr', expected_activation_height) def set_dash_llmq_test_params(self, llmq_size, llmq_threshold):