From 13db37af7768f29ae4f200819ef7e3e73c64b047 Mon Sep 17 00:00:00 2001 From: Odysseas Gabrielides Date: Mon, 4 Sep 2023 12:11:24 +0300 Subject: [PATCH] feat!: masternode node hard-fork activation DIP-0023 --- src/chainparams.cpp | 46 +++- src/chainparams.h | 1 + src/chainparamsbase.cpp | 4 +- src/consensus/params.h | 7 + src/evo/mnhftx.cpp | 180 ++++++++++++++ src/evo/mnhftx.h | 46 ++++ src/evo/specialtxman.cpp | 19 +- src/evo/specialtxman.h | 11 +- src/init.cpp | 8 +- src/node/context.cpp | 1 + src/node/context.h | 2 + .../dynamic_activation_thresholds_tests.cpp | 2 +- src/test/fuzz/versionbits.cpp | 1 + src/test/util/setup_common.cpp | 5 +- src/test/validation_chainstate_tests.cpp | 2 +- .../validation_chainstatemanager_tests.cpp | 8 +- src/test/validation_flush_tests.cpp | 2 +- src/test/versionbits_tests.cpp | 1 + src/validation.cpp | 29 ++- src/validation.h | 4 + src/versionbits.cpp | 67 +++--- src/versionbits.h | 1 + test/functional/feature_llmq_data_recovery.py | 2 +- test/functional/feature_mnehf.py | 219 ++++++++++++++++++ .../feature_new_quorum_type_activation.py | 2 +- test/functional/test_runner.py | 1 + test/lint/lint-circular-dependencies.sh | 1 + 27 files changed, 606 insertions(+), 66 deletions(-) create mode 100755 test/functional/feature_mnehf.py diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 8f0b848e67..7e11e56f1f 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -105,6 +105,30 @@ static CBlock FindDevNetGenesisBlock(const CBlock &prevBlock, const CAmount& rew assert(false); } +bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const +{ + for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) { + if (consensus.vDeployments[index].bit == nBit) { + auto& deployment = consensus.vDeployments[index]; + if (timePast > deployment.nTimeout) { + LogPrintf("%s: activation by bit=%d for deployment %s timed out at height=%d\n", __func__, nBit, VersionBitsDeploymentInfo[Consensus::DeploymentPos(index)].name, height); + continue; + } + if (deployment.nMNActivationHeight < 0) { + LogPrintf("%s: trying to set MnEHF height=%d for non-masternode activation fork bit=%d\n", __func__, height, nBit); + return false; + } + if (!fJustCheck) { + LogPrintf("%s: set MnEHF height=%d for bit=%d successfuly while fJustCheck=%d\n", __func__, height, nBit, fJustCheck); + deployment.nMNActivationHeight = height; + } + return true; + } + } + LogPrintf("%s: not found MnEHF fork bit=%d\n", __func__, nBit); + return false; +} + void CChainParams::AddLLMQ(Consensus::LLMQType llmqType) { assert(!GetLLMQ(llmqType).has_value()); @@ -909,7 +933,7 @@ class CRegTestParams : public CChainParams { /** * Allows modifying the Version Bits regtest parameters. */ - void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff) + void UpdateVersionBitsParameters(Consensus::DeploymentPos d, int64_t nStartTime, int64_t nTimeout, int64_t nWindowSize, int64_t nThresholdStart, int64_t nThresholdMin, int64_t nFalloffCoeff, int64_t nMNActivationHeight) { consensus.vDeployments[d].nStartTime = nStartTime; consensus.vDeployments[d].nTimeout = nTimeout; @@ -925,6 +949,9 @@ class CRegTestParams : public CChainParams { if (nFalloffCoeff != -1) { consensus.vDeployments[d].nFalloffCoeff = nFalloffCoeff; } + if (nMNActivationHeight != -1) { + consensus.vDeployments[d].nMNActivationHeight = nMNActivationHeight; + } } void UpdateActivationParametersFromArgs(const ArgsManager& args); @@ -998,13 +1025,13 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) for (const std::string& strDeployment : args.GetArgs("-vbparams")) { std::vector vDeploymentParams = SplitString(strDeployment, ':'); - if (vDeploymentParams.size() != 3 && vDeploymentParams.size() != 5 && vDeploymentParams.size() != 7) { + if (vDeploymentParams.size() != 3 && vDeploymentParams.size() != 5 && vDeploymentParams.size() != 8) { throw std::runtime_error("Version bits parameters malformed, expecting " ":: or " ":::: or " - "::::::"); + ":::::::"); } - int64_t nStartTime, nTimeout, nWindowSize = -1, nThresholdStart = -1, nThresholdMin = -1, nFalloffCoeff = -1; + int64_t nStartTime, nTimeout, nWindowSize = -1, nThresholdStart = -1, nThresholdMin = -1, nFalloffCoeff = -1, nMNActivationHeight = -1; if (!ParseInt64(vDeploymentParams[1], &nStartTime)) { throw std::runtime_error(strprintf("Invalid nStartTime (%s)", vDeploymentParams[1])); } @@ -1019,21 +1046,24 @@ void CRegTestParams::UpdateActivationParametersFromArgs(const ArgsManager& args) throw std::runtime_error(strprintf("Invalid nThresholdStart (%s)", vDeploymentParams[4])); } } - if (vDeploymentParams.size() == 7) { + if (vDeploymentParams.size() == 8) { if (!ParseInt64(vDeploymentParams[5], &nThresholdMin)) { throw std::runtime_error(strprintf("Invalid nThresholdMin (%s)", vDeploymentParams[5])); } if (!ParseInt64(vDeploymentParams[6], &nFalloffCoeff)) { throw std::runtime_error(strprintf("Invalid nFalloffCoeff (%s)", vDeploymentParams[6])); } + if (!ParseInt64(vDeploymentParams[7], &nMNActivationHeight)) { + throw std::runtime_error(strprintf("Invalid nMNActivationHeight (%s)", vDeploymentParams[7])); + } } bool found = false; for (int j=0; j < (int)Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++j) { if (vDeploymentParams[0] == VersionBitsDeploymentInfo[j].name) { - UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff); + UpdateVersionBitsParameters(Consensus::DeploymentPos(j), nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff, nMNActivationHeight); found = true; - LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, window=%ld, thresholdstart=%ld, thresholdmin=%ld, falloffcoeff=%ld\n", - vDeploymentParams[0], nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff); + LogPrintf("Setting version bits activation parameters for %s to start=%ld, timeout=%ld, window=%ld, thresholdstart=%ld, thresholdmin=%ld, falloffcoeff=%ld, mnactivationHeight=%ld\n", + vDeploymentParams[0], nStartTime, nTimeout, nWindowSize, nThresholdStart, nThresholdMin, nFalloffCoeff, nMNActivationHeight); break; } } diff --git a/src/chainparams.h b/src/chainparams.h index bcdcf664dc..e3941aaa9f 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -138,6 +138,7 @@ class CChainParams void UpdateDIP8Parameters(int nActivationHeight); void UpdateBudgetParameters(int nMasternodePaymentsStartBlock, int nBudgetPaymentsStartBlock, int nSuperblockStartBlock); void UpdateLLMQInstantSend(Consensus::LLMQType llmqType); + bool UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const; int PoolMinParticipants() const { return nPoolMinParticipants; } int PoolMaxParticipants() const { return nPoolMaxParticipants; } int FulfilledRequestExpireTime() const { return nFulfilledRequestExpireTime; } diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 9a6e33b982..b834330f1b 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -36,9 +36,9 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-regtest", "Enter regression test mode, which uses a special chain in which blocks can be solved instantly. " "This is intended for regression testing tools and app development. Equivalent to -chain=regtest", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); - argsman.AddArg("-vbparams=::(::(::))", + argsman.AddArg("-vbparams=::(::(:::))", "Use given start/end times for specified version bits deployment (regtest-only). " - "Specifying window, threshold/thresholdstart, thresholdmin and falloffcoeff is optional.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + "Specifying window, threshold/thresholdstart, thresholdmin, falloffcoeff and mnactivation is optional.", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); } diff --git a/src/consensus/params.h b/src/consensus/params.h index 4e3442cb0e..6d79ea211f 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -49,6 +49,13 @@ struct BIP9Deployment { * process (which takes at least 3 BIP9 intervals). Only tests that specifically test the * behaviour during activation cannot use this. */ static constexpr int64_t ALWAYS_ACTIVE = -1; + + /** this value is used for forks activated by master nodes. + * negative values means it is regular fork, no masternodes confirmation is needed. + * 0 means that there's no approval from masternodes yet. + * Otherwise it shows minimum height when miner's signals for this block can be assumed + */ + mutable int64_t nMNActivationHeight{-1}; }; /** diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 665e600f73..ef2ebff68c 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -15,9 +15,12 @@ #include #include +#include #include +#include extern const std::string MNEHF_REQUESTID_PREFIX = "mnhf"; +static const std::string DB_SIGNALS = "mnhf_s"; bool MNHFTx::Verify(const CBlockIndex* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const { @@ -80,6 +83,183 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida return true; } +static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, std::vector& signals_to_process, BlockValidationState& state) +{ + AssertLockHeld(cs_main); + + // we skip the coinbase + for (size_t i = 1; i < block.vtx.size(); ++i) { + const CTransaction& tx = *block.vtx[i]; + + if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) { + // only interested in special TXs 'TRANSACTION_MNHF_SIGNAL' + continue; + } + + TxValidationState tx_state; + if (!CheckMNHFTx(tx, pindex, tx_state)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage()); + } + + MNHFTxPayload mnhfTx; + if (!GetTxPayload(tx, mnhfTx)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-tx-payload"); + } + signals_to_process.push_back(mnhfTx.signal.versionBit); + } + + // Checking that there's no any duplicates... + std::sort(signals_to_process.begin(), signals_to_process.end()); + const auto it = std::unique(signals_to_process.begin(), signals_to_process.end()); + if (std::distance(signals_to_process.begin(), it) != signals_to_process.size()) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates"); + } + + return true; +} + +bool CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pindex, bool fJustCheck, BlockValidationState& state) +{ + try { + std::vector new_signals; + if (!extractSignals(block, pindex, new_signals, state)) { + // state is set inside extractSignals + return false; + } + if (new_signals.empty()) { + if (!fJustCheck) { + AddToCache(GetFromCache(pindex->pprev), pindex); + } + return true; + } + + Signals signals = GetFromCache(pindex->pprev); + int mined_height = pindex->nHeight; + + // Extra validation of signals to be sure that it can succeed + for (const auto& versionBit : new_signals) { + LogPrintf("%s: add mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size()); + if (signals.find(versionBit) != signals.end()) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicate"); + } + + if (!Params().UpdateMNActivationParam(versionBit, mined_height, pindex->GetMedianTimePast(), true /* fJustCheck */)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-non-mn-fork"); + } + } + if (fJustCheck) { + // We are done, no need actually update any params + return true; + } + for (const auto& versionBit : new_signals) { + signals.insert({versionBit, mined_height}); + + if (!Params().UpdateMNActivationParam(versionBit, mined_height, pindex->GetMedianTimePast(), false /* fJustCheck */)) { + // it should not ever fail - all checks are done above + assert(false); + } + + } + + AddToCache(signals, pindex); + return true; + } catch (const std::exception& e) { + LogPrintf("%s -- failed: %s\n", __func__, e.what()); + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-proc-mnhf-inblock"); + } +} + +bool CMNHFManager::UndoBlock(const CBlock& block, const CBlockIndex* const pindex) +{ + std::vector excluded_signals; + BlockValidationState state; + if (!extractSignals(block, pindex, excluded_signals, state)) { + LogPrintf("%s: failed to extract signals\n", __func__); + return false; + } + if (excluded_signals.empty()) { + return true; + } + + const Signals signals = GetFromCache(pindex); + for (const auto& versionBit : excluded_signals) { + assert(versionBit < VERSIONBITS_NUM_BITS); + + LogPrintf("%s: exclude mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size()); + assert(signals.find(versionBit) != signals.end()); + + bool update_ret = Params().UpdateMNActivationParam(versionBit, 0, pindex->GetMedianTimePast(), false /* fJustCheck */); + assert(update_ret); + } + + return true; +} + +void CMNHFManager::UpdateChainParams(const CBlockIndex* const pindex, const CBlockIndex* const pindexOld) +{ + LogPrintf("%s: update chain params %s -> %s\n", __func__, pindexOld ? pindexOld->GetBlockHash().ToString() : "", pindex ? pindex->GetBlockHash().ToString() : ""); + Signals signals_old{GetFromCache(pindexOld)}; + for (const auto& signal: signals_old) { + uint8_t versionBit = signal.first; + assert(versionBit < VERSIONBITS_NUM_BITS); + + LogPrintf("%s: unload mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals_old.size()); + + bool update_ret = Params().UpdateMNActivationParam(versionBit, 0, pindex->GetMedianTimePast(), false); + assert(update_ret); + } + + Signals signals{GetFromCache(pindex)}; + for (const auto& signal: signals) { + uint8_t versionBit = signal.first; + int value = signal.second; + assert(versionBit < VERSIONBITS_NUM_BITS); + + LogPrintf("%s: load mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size()); + + bool update_ret = Params().UpdateMNActivationParam(versionBit, value, pindex->GetMedianTimePast(), false); + assert(update_ret); + } +} + +CMNHFManager::Signals CMNHFManager::GetFromCache(const CBlockIndex* const pindex) +{ + if (pindex == nullptr) return {}; + const uint256& blockHash = pindex->GetBlockHash(); + Signals signals{}; + { + LOCK(cs_cache); + if (mnhfCache.get(blockHash, signals)) { + LogPrintf("CMNHFManager::GetFromCache: mnhf get for block %s from cache: %lld signals\n", pindex->GetBlockHash().ToString(), signals.size()); + return signals; + } + } + if (VersionBitsState(pindex->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_V20, versionbitscache) != ThresholdState::ACTIVE) { + LOCK(cs_cache); + mnhfCache.insert(blockHash, signals); + LogPrintf("CMNHFManager::GetFromCache: mnhf feature is disabled: return empty for block %s\n", pindex->GetBlockHash().ToString()); + return signals; + } + if (!m_evoDb.Read(std::make_pair(DB_SIGNALS, blockHash), signals)) { + LogPrintf("CMNHFManager::GetFromCache: failure: can't read MnEHF signals from db for %s\n", pindex->GetBlockHash().ToString()); + } + LogPrintf("CMNHFManager::GetFromCache: mnhf for block %s read from evo: %lld\n", pindex->GetBlockHash().ToString(), signals.size()); + LOCK(cs_cache); + mnhfCache.insert(blockHash, signals); + return signals; +} + +void CMNHFManager::AddToCache(const Signals& signals, const CBlockIndex* const pindex) +{ + const uint256& blockHash = pindex->GetBlockHash(); + { + LOCK(cs_cache); + LogPrintf("%s: mnhf for block %s add to cache: %lld\n", __func__, pindex->GetBlockHash().ToString(), signals.size()); + mnhfCache.insert(blockHash, signals); + } + m_evoDb.Write(std::make_pair(DB_SIGNALS, blockHash), signals); +} + std::string MNHFTx::ToString() const { return strprintf("MNHFTx(versionBit=%d, quorumHash=%s, sig=%s)", diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index 392fe5ff85..5ce5d7ba61 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -11,7 +11,14 @@ #include #include +#include +#include +#include + +class BlockValidationState; +class CBlock; class CBlockIndex; +class CEvoDB; class TxValidationState; extern RecursiveMutex cs_main; @@ -71,6 +78,45 @@ class MNHFTxPayload } }; +class CMNHFManager +{ +public: + using Signals = std::unordered_map; + +private: + CEvoDB& m_evoDb; + + static constexpr size_t MNHFCacheSize = 1000; + Mutex cs_cache; + // versionBit <-> height + unordered_lru_cache mnhfCache GUARDED_BY(cs_cache) {MNHFCacheSize}; + +public: + explicit CMNHFManager(CEvoDB& evoDb) : + m_evoDb(evoDb) {} + ~CMNHFManager() = default; + + /** + * Every new block should be processed when Tip() is updated by calling of CMNHFManager::ProcessBlock + */ + bool ProcessBlock(const CBlock& block, const CBlockIndex* const pindex, bool fJustCheck, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** + * Every undo block should be processed when Tip() is updated by calling of CMNHFManager::UndoBlock + */ + bool UndoBlock(const CBlock& block, const CBlockIndex* const pindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + + /** + * Once app is started, need to initialize dictionary will all known signals at the current Tip() + * by calling UpdateChainParams() + */ + void UpdateChainParams(const CBlockIndex* const pindex, const CBlockIndex* const pindexOld) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + +private: + void AddToCache(const Signals& signals, const CBlockIndex* const pindex); + Signals GetFromCache(const CBlockIndex* const pindex); +}; + bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); #endif // BITCOIN_EVO_MNHFTX_H diff --git a/src/evo/specialtxman.cpp b/src/evo/specialtxman.cpp index e6604ccef3..62122888e2 100644 --- a/src/evo/specialtxman.cpp +++ b/src/evo/specialtxman.cpp @@ -122,7 +122,8 @@ static bool UndoSpecialTx(const CTransaction& tx, const CBlockIndex* pindex) return false; } -bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler, +bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager, + llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler, const Consensus::Params& consensusParams, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots, BlockValidationState& state) { @@ -134,6 +135,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll static int64_t nTimeDMN = 0; static int64_t nTimeMerkle = 0; static int64_t nTimeCbTxCL = 0; + static int64_t nTimeMnehf = 0; int64_t nTime1 = GetTimeMicros(); @@ -215,6 +217,15 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll nTimeCbTxCL += nTime6 - nTime5; LogPrint(BCLog::BENCHMARK, " - CheckCbTxBestChainlock: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5), nTimeCbTxCL * 0.000001); + if (!mnhfManager.ProcessBlock(block, pindex, fJustCheck, state)) { + // pass the state returned by the function above + return false; + } + + int64_t nTime7 = GetTimeMicros(); + nTimeMnehf += nTime7 - nTime6; + LogPrint(BCLog::BENCHMARK, " - mnhfManager: %.2fms [%.2fs]\n", 0.001 * (nTime7 - nTime6), nTimeMnehf * 0.000001); + if (Params().GetConsensus().V19Height == pindex->nHeight + 1) { // NOTE: The block next to the activation is the one that is using new rules. // V19 activated just activated, so we must switch to the new rules here. @@ -229,7 +240,7 @@ bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, ll return true; } -bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor) +bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager, llmq::CQuorumBlockProcessor& quorum_block_processor) { AssertLockHeld(cs_main); @@ -250,6 +261,10 @@ bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq: } } + if (!mnhfManager.UndoBlock(block, pindex)) { + return false; + } + if (!deterministicMNManager->UndoBlock(pindex)) { return false; } diff --git a/src/evo/specialtxman.h b/src/evo/specialtxman.h index bceb7f719c..8f28d43b0d 100644 --- a/src/evo/specialtxman.h +++ b/src/evo/specialtxman.h @@ -13,6 +13,7 @@ class BlockValidationState; class CBlock; class CBlockIndex; class CCoinsViewCache; +class CMNHFManager; class TxValidationState; namespace llmq { class CQuorumBlockProcessor; @@ -26,9 +27,11 @@ extern RecursiveMutex cs_main; bool CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler, - const Consensus::Params& consensusParams, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots, - BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); -bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, llmq::CQuorumBlockProcessor& quorum_block_processor) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +bool ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager, + llmq::CQuorumBlockProcessor& quorum_block_processor, const llmq::CChainLocksHandler& chainlock_handler, + const Consensus::Params& consensusParams, const CCoinsViewCache& view, bool fJustCheck, bool fCheckCbTxMerleRoots, + BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); +bool UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, CMNHFManager& mnhfManager, + llmq::CQuorumBlockProcessor& quorum_block_processor) EXCLUSIVE_LOCKS_REQUIRED(cs_main); #endif // BITCOIN_EVO_SPECIALTXMAN_H diff --git a/src/init.cpp b/src/init.cpp index 92b7cb64ff..413625f308 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -85,6 +85,7 @@ #include #include +#include #include #include #include @@ -359,6 +360,7 @@ void PrepareShutdown(NodeContext& node) llmq::quorumSnapshotManager.reset(); deterministicMNManager.reset(); creditPoolManager.reset(); + node.mnhf_manager.reset(); node.evodb.reset(); } for (const auto& client : node.chain_clients) { @@ -1922,9 +1924,10 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc LOCK(cs_main); node.evodb.reset(); node.evodb = std::make_unique(nEvoDbCache, false, fReset || fReindexChainState); + node.mnhf_manager = std::make_unique(*node.evodb); chainman.Reset(); - chainman.InitializeChainstate(Assert(node.mempool.get()), *node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor); + chainman.InitializeChainstate(Assert(node.mempool.get()), *node.mnhf_manager, *node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor); chainman.m_total_coinstip_cache = nCoinCacheUsage; chainman.m_total_coinsdb_cache = nCoinDBCache; @@ -2112,6 +2115,9 @@ bool AppInitMain(const CoreContext& context, NodeContext& node, interfaces::Bloc LogPrintf("%s: bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load()); } + LogPrintf("init.cpp: update chain params right after bls\n"); + node.mnhf_manager->UpdateChainParams(::ChainActive().Tip(), nullptr); + if (!CVerifyDB().VerifyDB( *chainstate, chainparams, chainstate->CoinsDB(), *node.evodb, diff --git a/src/node/context.cpp b/src/node/context.cpp index ed3d4b9ac8..9c93c9f622 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/node/context.h b/src/node/context.h index 32f2b511bb..ff74f386ad 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -20,6 +20,7 @@ class CScheduler; class CTxMemPool; class ChainstateManager; struct LLMQContext; +class CMNHFManager; class PeerManager; class CEvoDB; namespace interfaces { @@ -58,6 +59,7 @@ struct NodeContext { //! Dash std::unique_ptr llmq_ctx; std::unique_ptr creditPoolManager; + std::unique_ptr mnhf_manager; std::unique_ptr evodb; diff --git a/src/test/dynamic_activation_thresholds_tests.cpp b/src/test/dynamic_activation_thresholds_tests.cpp index ceccd8bd60..a746d86e99 100644 --- a/src/test/dynamic_activation_thresholds_tests.cpp +++ b/src/test/dynamic_activation_thresholds_tests.cpp @@ -34,7 +34,7 @@ static constexpr int threshold(int attempt) struct TestChainDATSetup : public TestChainSetup { - TestChainDATSetup() : TestChainSetup(window - 2, {"-vbparams=testdummy:0:999999999999:100:80:60:5"}) {} + TestChainDATSetup() : TestChainSetup(window - 2, {"-vbparams=testdummy:0:999999999999:100:80:60:5:-1"}) {} void signal(int num_blocks, bool expected_lockin) { diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index 9563908679..1841a7d301 100644 --- a/src/test/fuzz/versionbits.cpp +++ b/src/test/fuzz/versionbits.cpp @@ -42,6 +42,7 @@ class TestConditionChecker : public AbstractThresholdConditionChecker bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return Condition(pindex->nVersion); } int64_t BeginTime(const Consensus::Params& params) const override { return m_begin; } + int MasternodeBeginHeight(const Consensus::Params& params) const override { return 0; } int64_t EndTime(const Consensus::Params& params) const override { return m_end; } int Period(const Consensus::Params& params) const override { return m_period; } int Threshold(const Consensus::Params& params, int nAttempt) const override { return m_threshold; } diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index bc96ae7f14..c5e138cf7a 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -61,6 +61,7 @@ #include #include #include +#include #include #include @@ -158,6 +159,7 @@ BasicTestingSetup::BasicTestingSetup(const std::string& chainName, const std::ve g_wallet_init_interface.Construct(m_node); fCheckBlockIndex = true; m_node.evodb = std::make_unique(1 << 20, true, true); + m_node.mnhf_manager = std::make_unique(*m_node.evodb); connman = std::make_unique(0x1337, 0x1337, *m_node.addrman); llmq::quorumSnapshotManager.reset(new llmq::CQuorumSnapshotManager(*m_node.evodb)); creditPoolManager = std::make_unique(*m_node.evodb); @@ -174,6 +176,7 @@ BasicTestingSetup::~BasicTestingSetup() connman.reset(); llmq::quorumSnapshotManager.reset(); creditPoolManager.reset(); + m_node.mnhf_manager.reset(); m_node.evodb.reset(); LogInstance().DisconnectTestLogger(); @@ -247,7 +250,7 @@ TestingSetup::TestingSetup(const std::string& chainName, const std::vectorInitializeChainstate(m_node.mempool.get(), *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor); + m_node.chainman->InitializeChainstate(m_node.mempool.get(), *m_node.mnhf_manager, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor); ::ChainstateActive().InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); assert(!::ChainstateActive().CanFlushToDisk()); diff --git a/src/test/validation_chainstate_tests.cpp b/src/test/validation_chainstate_tests.cpp index 7d42d8b6f4..a79c0f6b1d 100644 --- a/src/test/validation_chainstate_tests.cpp +++ b/src/test/validation_chainstate_tests.cpp @@ -40,7 +40,7 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches) return outp; }; - CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor)); + CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor)); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); WITH_LOCK(::cs_main, c1.InitCoinsCache(1 << 23)); diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 9755fee627..2b7b8a4da7 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // Create a legacy (IBD) chainstate. // - CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor)); + CChainState& c1 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor)); chainstates.push_back(&c1); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -76,7 +76,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager) // const uint256 snapshot_blockhash = GetRandHash(); CChainState& c2 = *WITH_LOCK(::cs_main, return &manager.InitializeChainstate( - &mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor, + &mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor, snapshot_blockhash) ); chainstates.push_back(&c2); @@ -145,7 +145,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a legacy (IBD) chainstate. // - CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor)); + CChainState& c1 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor)); chainstates.push_back(&c1); c1.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); @@ -163,7 +163,7 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches) // Create a snapshot-based chainstate. // - CChainState& c2 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor, GetRandHash())); + CChainState& c2 = *WITH_LOCK(cs_main, return &manager.InitializeChainstate(&mempool, *m_node.mnhf_manager, evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor, GetRandHash())); chainstates.push_back(&c2); c2.InitCoinsDB( /* cache_size_bytes */ 1 << 23, /* in_memory */ true, /* should_wipe */ false); diff --git a/src/test/validation_flush_tests.cpp b/src/test/validation_flush_tests.cpp index e7dac12919..cea4ffe045 100644 --- a/src/test/validation_flush_tests.cpp +++ b/src/test/validation_flush_tests.cpp @@ -24,7 +24,7 @@ BOOST_AUTO_TEST_CASE(getcoinscachesizestate) { CTxMemPool mempool; BlockManager blockman{}; - CChainState chainstate(&mempool, blockman, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor); + CChainState chainstate(&mempool, blockman, *m_node.mnhf_manager, *m_node.evodb, llmq::chainLocksHandler, llmq::quorumInstantSendManager, llmq::quorumBlockProcessor); chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false); WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10)); diff --git a/src/test/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 4eb61e20b3..d570ea4367 100644 --- a/src/test/versionbits_tests.cpp +++ b/src/test/versionbits_tests.cpp @@ -24,6 +24,7 @@ class TestConditionChecker : public AbstractThresholdConditionChecker public: int64_t BeginTime(const Consensus::Params& params) const override { return TestTime(10000); } int64_t EndTime(const Consensus::Params& params) const override { return TestTime(20000); } + int MasternodeBeginHeight(const Consensus::Params& params) const override { return 0; } int Period(const Consensus::Params& params) const override { return 1000; } int Threshold(const Consensus::Params& params, int nAttempt) const override { return 900; } bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const override { return (pindex->nVersion & 0x100); } diff --git a/src/validation.cpp b/src/validation.cpp index 245561b93a..c5fe88154b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -794,7 +795,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws) // No transactions are allowed below minRelayTxFee except from disconnected // blocks - if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false; + // Checking of fee for MNHF_SIGNAL should be skipped: mnhf does not have + // inputs, outputs, or fee + if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) { + if (!bypass_limits && !CheckFeeRate(nSize, nModifiedFees, state)) return false; + } if (nAbsurdFee && nFees > nAbsurdFee) return state.Invalid(TxValidationResult::TX_NOT_STANDARD, @@ -1244,6 +1249,7 @@ void CoinsViews::InitCache() CChainState::CChainState(CTxMemPool* mempool, BlockManager& blockman, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -1254,6 +1260,7 @@ CChainState::CChainState(CTxMemPool* mempool, m_clhandler(clhandler), m_isman(isman), m_quorum_block_processor(quorum_block_processor), + m_mnhfManager(mnhfManager), m_evoDb(evoDb), m_blockman(blockman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} @@ -1690,7 +1697,7 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI std::vector > addressUnspentIndex; std::vector > spentIndex; - if (!UndoSpecialTxsInBlock(block, pindex, *m_quorum_block_processor)) { + if (!UndoSpecialTxsInBlock(block, pindex, m_mnhfManager, *m_quorum_block_processor)) { error("DisconnectBlock(): UndoSpecialTxsInBlock failed"); return DISCONNECT_FAILED; } @@ -1947,6 +1954,7 @@ class WarningBitsConditionChecker : public AbstractThresholdConditionChecker int64_t BeginTime(const Consensus::Params& params) const override { return 0; } int64_t EndTime(const Consensus::Params& params) const override { return std::numeric_limits::max(); } + int MasternodeBeginHeight(const Consensus::Params& params) const override { return 0; } int Period(const Consensus::Params& params) const override { return params.nMinerConfirmationWindow; } int Threshold(const Consensus::Params& params, int nAttempt) const override { return params.nRuleChangeActivationThreshold; } @@ -2200,7 +2208,7 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state, bool fDIP0001Active_context = pindex->nHeight >= Params().GetConsensus().DIP0001Height; // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing - if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), view, fJustCheck, fScriptChecks, state)) { + if (!ProcessSpecialTxsInBlock(block, pindex, m_mnhfManager, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), view, fJustCheck, fScriptChecks, state)) { return error("ConnectBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -4840,7 +4848,7 @@ bool CChainState::RollforwardBlock(const CBlockIndex* pindex, CCoinsViewCache& i // MUST process special txes before updating UTXO to ensure consistency between mempool and block processing BlockValidationState state; - if (!ProcessSpecialTxsInBlock(block, pindex, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), inputs, false /*fJustCheck*/, false /*fScriptChecks*/, state)) { + if (!ProcessSpecialTxsInBlock(block, pindex, m_mnhfManager, *m_quorum_block_processor, *m_clhandler, m_params.GetConsensus(), inputs, false /*fJustCheck*/, false /*fScriptChecks*/, state)) { return error("RollforwardBlock(DASH): ProcessSpecialTxsInBlock for block %s failed with %s", pindex->GetBlockHash().ToString(), state.ToString()); } @@ -5706,6 +5714,7 @@ std::vector ChainstateManager::GetAll() } CChainState& ChainstateManager::InitializeChainstate(CTxMemPool* mempool, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -5720,7 +5729,7 @@ CChainState& ChainstateManager::InitializeChainstate(CTxMemPool* mempool, throw std::logic_error("should not be overwriting a chainstate"); } - to_modify.reset(new CChainState(mempool, m_blockman, evoDb, clhandler, isman, quorum_block_processor, snapshot_blockhash)); + to_modify.reset(new CChainState(mempool, m_blockman, mnhfManager, evoDb, clhandler, isman, quorum_block_processor, snapshot_blockhash)); // Snapshot chainstates and initial IBD chaintates always become active. if (is_snapshot || (!is_snapshot && !m_active_chainstate)) { @@ -5790,9 +5799,13 @@ bool ChainstateManager::ActivateSnapshot( } auto snapshot_chainstate = WITH_LOCK(::cs_main, return std::make_unique( - /* mempool */ nullptr, m_blockman, this->ActiveChainstate().m_evoDb, - this->ActiveChainstate().m_clhandler, this->ActiveChainstate().m_isman, - this->ActiveChainstate().m_quorum_block_processor, base_blockhash + /* mempool */ nullptr, m_blockman, + this->ActiveChainstate().m_mnhfManager, + this->ActiveChainstate().m_evoDb, + this->ActiveChainstate().m_clhandler, + this->ActiveChainstate().m_isman, + this->ActiveChainstate().m_quorum_block_processor, + base_blockhash ) ); diff --git a/src/validation.h b/src/validation.h index fdbd7ea4ab..b1d84566bf 100644 --- a/src/validation.h +++ b/src/validation.h @@ -55,6 +55,7 @@ class CChainParams; struct CCheckpointData; class CInv; class CConnman; +class CMNHFManager; class CScriptCheck; class CTxMemPool; class TxValidationState; @@ -589,6 +590,7 @@ class CChainState const std::unique_ptr& m_clhandler; const std::unique_ptr& m_isman; const std::unique_ptr& m_quorum_block_processor; + CMNHFManager& m_mnhfManager; CEvoDB& m_evoDb; public: @@ -598,6 +600,7 @@ class CChainState explicit CChainState(CTxMemPool* mempool, BlockManager& blockman, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -945,6 +948,7 @@ class ChainstateManager //! @param[in] snapshot_blockhash If given, signify that this chainstate //! is based on a snapshot. CChainState& InitializeChainstate(CTxMemPool* mempool, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, diff --git a/src/versionbits.cpp b/src/versionbits.cpp index e3910056d8..5ffdb41a3f 100644 --- a/src/versionbits.cpp +++ b/src/versionbits.cpp @@ -5,10 +5,33 @@ #include #include +static int calculateStartHeight(const CBlockIndex* pindexPrev, ThresholdState state, const int nPeriod, const ThresholdConditionCache& cache) { + int nStartHeight{std::numeric_limits::max()}; + + // we are interested only in state STARTED + // For state DEFINED: it is not started yet, nothing to do + // For states LOCKED_IN, FAILED, ACTIVE: it is too late, nothing to do + while (state == ThresholdState::STARTED) { + nStartHeight = std::min(pindexPrev->nHeight + 1, nStartHeight); + + // we can walk back here because the only way for STARTED state to exist + // in cache already is to be calculated in previous runs via "walk forward" + // loop below starting from DEFINED state. + pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); + auto cache_it = cache.find(pindexPrev); + assert(cache_it != cache.end()); + + state = cache_it->second; + } + + return nStartHeight; +} + ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const { int nPeriod = Period(params); int64_t nTimeStart = BeginTime(params); + int masternodeStartHeight = MasternodeBeginHeight(params); int64_t nTimeTimeout = EndTime(params); // Check if this deployment is always active. @@ -29,7 +52,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* cache[pindexPrev] = ThresholdState::DEFINED; break; } - if (pindexPrev->GetMedianTimePast() < nTimeStart) { + if (pindexPrev->GetMedianTimePast() < nTimeStart || pindexPrev->nHeight < masternodeStartHeight) { // Optimization: don't recompute down further, as we know every earlier block will be before the start time cache[pindexPrev] = ThresholdState::DEFINED; break; @@ -42,35 +65,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* assert(cache.count(pindexPrev)); ThresholdState state = cache[pindexPrev]; - auto pindex_search = pindexPrev; - auto state_search = state; - bool do_search{true}; - int nStartHeight{std::numeric_limits::max()}; - while (do_search) { - switch (state_search) { - case ThresholdState::DEFINED: { - // not started yet, nothinig to do - do_search = false; - break; - } - case ThresholdState::STARTED: { - nStartHeight = std::min(nStartHeight, pindex_search->nHeight + 1); - // we can walk back here because the only way for STARTED state to exist - // in cache already is to be calculated in previous runs via "walk forward" - // loop below starting from DEFINED state. - pindex_search = pindex_search->GetAncestor(pindex_search->nHeight - nPeriod); - state_search = cache[pindex_search]; - break; - } - case ThresholdState::LOCKED_IN: - case ThresholdState::FAILED: - case ThresholdState::ACTIVE: { - // too late, nothing to do - do_search = false; - break; - } - } - } + int nStartHeight = calculateStartHeight(pindexPrev, state, nPeriod, cache); // Now walk forward and compute the state of descendants of pindexPrev while (!vToCompute.empty()) { @@ -82,7 +77,7 @@ ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* case ThresholdState::DEFINED: { if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) { stateNext = ThresholdState::FAILED; - } else if (pindexPrev->GetMedianTimePast() >= nTimeStart) { + } else if (pindexPrev->GetMedianTimePast() >= nTimeStart && pindexPrev->nHeight >= masternodeStartHeight) { stateNext = ThresholdState::STARTED; nStartHeight = pindexPrev->nHeight + 1; } @@ -210,6 +205,16 @@ class VersionBitsConditionChecker : public AbstractThresholdConditionChecker { protected: int64_t BeginTime(const Consensus::Params& params) const override { return params.vDeployments[id].nStartTime; } + int MasternodeBeginHeight(const Consensus::Params& params) const override { + const auto& deployment = params.vDeployments[id]; + if (deployment.nMNActivationHeight == 0) { + return std::numeric_limits::max(); + } + if (deployment.nMNActivationHeight < 0) { + return 0; + } + return deployment.nMNActivationHeight; + } int64_t EndTime(const Consensus::Params& params) const override { return params.vDeployments[id].nTimeout; } int Period(const Consensus::Params& params) const override { return params.vDeployments[id].nWindowSize ? params.vDeployments[id].nWindowSize : params.nMinerConfirmationWindow; } int Threshold(const Consensus::Params& params, int nAttempt) const override diff --git a/src/versionbits.h b/src/versionbits.h index cd34d2ecb5..db57fb5e07 100644 --- a/src/versionbits.h +++ b/src/versionbits.h @@ -56,6 +56,7 @@ class AbstractThresholdConditionChecker { protected: virtual bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const =0; virtual int64_t BeginTime(const Consensus::Params& params) const =0; + virtual int MasternodeBeginHeight(const Consensus::Params& params) const = 0; virtual int64_t EndTime(const Consensus::Params& params) const =0; virtual int Period(const Consensus::Params& params) const =0; virtual int Threshold(const Consensus::Params& params, int nAttempt) const =0; diff --git a/test/functional/feature_llmq_data_recovery.py b/test/functional/feature_llmq_data_recovery.py index 38b918d74e..332d348a0b 100755 --- a/test/functional/feature_llmq_data_recovery.py +++ b/test/functional/feature_llmq_data_recovery.py @@ -24,7 +24,7 @@ class QuorumDataRecoveryTest(DashTestFramework): def set_test_params(self): - extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5"] for _ in range(9)] + extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5:-1"] for _ in range(9)] self.set_dash_test_params(9, 7, fast_dip3_enforcement=True, extra_args=extra_args) self.set_dash_llmq_test_params(4, 3) diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py new file mode 100755 index 0000000000..14fbd8033e --- /dev/null +++ b/test/functional/feature_mnehf.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2023 The Dash Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import struct +from io import BytesIO + +from test_framework.authproxy import JSONRPCException +from test_framework.key import ECKey +from test_framework.messages import ( + CMnEhf, + CTransaction, + hash256, + ser_string, +) + +from test_framework.test_framework import DashTestFramework +from test_framework.util import ( + assert_equal, + assert_greater_than, + get_bip9_details, +) + +class MnehfTest(DashTestFramework): + def set_test_params(self): + extra_args = [["-vbparams=testdummy:0:999999999999:12:12:12:5:0"] for _ in range(4)] + self.set_dash_test_params(4, 3, fast_dip3_enforcement=True, extra_args=extra_args) + + def skip_test_if_missing_module(self): + self.skip_if_no_wallet() + + def restart_all_nodes(self): + for inode in range(self.num_nodes): + self.log.info(f"Restart node {inode} with {self.extra_args[inode]}") + self.restart_node(inode, self.extra_args[inode]) + for i in range(self.num_nodes - 1): + self.connect_nodes(i + 1, i) + + def create_mnehf(self, versionBit, pubkey=None): + # request ID = sha256("mnhf", versionBit) + request_id_buf = ser_string(b"mnhf") + struct.pack(" masternode/node -> validationinterface -> llmq/signing" "llmq/debug -> llmq/dkgsessionhandler -> llmq/debug" "llmq/debug -> llmq/dkgsessionhandler -> llmq/dkgsession -> llmq/debug" + "evo/mnhftx -> validation -> evo/mnhftx" ) EXIT_CODE=0