From 612faa886813a80f54c98b666f9b45ab880302eb Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 4 Aug 2023 03:35:17 +0700 Subject: [PATCH 01/21] feat: imlemented new hard-fork mechanism that uses MN Activation Height Altough, it's still disabled because no calls of related methods after processing MnEHF tx --- src/chainparams.cpp | 46 ++++++++++--- src/chainparams.h | 1 + src/chainparamsbase.cpp | 4 +- src/consensus/params.h | 7 ++ .../dynamic_activation_thresholds_tests.cpp | 2 +- src/test/versionbits_tests.cpp | 1 + src/validation.cpp | 1 + src/versionbits.cpp | 67 ++++++++++--------- src/versionbits.h | 1 + test/functional/feature_llmq_data_recovery.py | 2 +- .../feature_new_quorum_type_activation.py | 2 +- 11 files changed, 90 insertions(+), 44 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 151ba48622060..d0c32fbfc8b7e 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 time-outed at height=%d\n", __func__, nBit, 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 3218f81b07992..1aa84dfaf7845 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -134,6 +134,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 9a6e33b9823c2..b834330f1b903 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 208d2afba6bdb..f06f4352defcf 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -48,6 +48,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/test/dynamic_activation_thresholds_tests.cpp b/src/test/dynamic_activation_thresholds_tests.cpp index ceccd8bd60dc1..a746d86e99402 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/versionbits_tests.cpp b/src/test/versionbits_tests.cpp index 4eb61e20b3534..d570ea436768a 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 daa092653df86..4d7bea53f99ed 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1927,6 +1927,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; } diff --git a/src/versionbits.cpp b/src/versionbits.cpp index e3910056d8234..5ffdb41a3f4b0 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 cd34d2ecb5b42..db57fb5e07402 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 38b918d74e18b..332d348a0b808 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_new_quorum_type_activation.py b/test/functional/feature_new_quorum_type_activation.py index 4d078d4cda9f6..a255319b09f89 100755 --- a/test/functional/feature_new_quorum_type_activation.py +++ b/test/functional/feature_new_quorum_type_activation.py @@ -17,7 +17,7 @@ class NewQuorumTypeActivationTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 - self.extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5"]] + self.extra_args = [["-vbparams=testdummy:0:999999999999:10:8:6:5:-1"]] def run_test(self): self.log.info(get_bip9_details(self.nodes[0], 'testdummy')) From c8d84a8c33a768f301e3f603fbdf1690cd05e553 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 4 Aug 2023 03:55:59 +0700 Subject: [PATCH 02/21] fix: MnEHF transaction should be accepted to mempool bypass fee limits --- src/validation.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/validation.cpp b/src/validation.cpp index 4d7bea53f99ed..3dbfe1a205d80 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -796,7 +796,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, From 33ab3187b22a9c6998e9ad13cfbad112c16d027d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 4 Aug 2023 03:54:54 +0700 Subject: [PATCH 03/21] feat: add CMNHFManager and logic to make hard-forks accordingly received signals --- 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 + 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/validation.cpp | 22 ++- src/validation.h | 4 + test/lint/lint-circular-dependencies.sh | 1 + 14 files changed, 290 insertions(+), 21 deletions(-) diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 665e600f734cb..ef2ebff68c0ee 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 7ea0c771fc00d..ef70bb40620d0 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; @@ -72,6 +79,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 3a895f7c8705c..387b13e64ce37 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(); @@ -198,6 +200,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. @@ -212,7 +223,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); @@ -233,6 +244,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 ca7a3801606db..7604599c72ad2 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,10 +27,12 @@ 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); bool CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const Consensus::Params& consensusParams, const CAmount blockReward, BlockValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); diff --git a/src/init.cpp b/src/init.cpp index 962681200ee15..88ca19588d5d6 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -87,6 +87,7 @@ #include #include +#include #include #include #include @@ -341,6 +342,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) { @@ -1914,9 +1916,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; @@ -2104,6 +2107,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 7772fcf8a3dfb..715272f9f18e9 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include diff --git a/src/node/context.h b/src/node/context.h index 91ff6df05d6d8..ef9f88e6c3839 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -20,6 +20,7 @@ class ChainstateManager; class CEvoDB; class CScheduler; class CTxMemPool; +class CMNHFManager; class PeerManager; struct CJContext; struct LLMQContext; @@ -60,6 +61,7 @@ struct NodeContext { //! Dash std::unique_ptr llmq_ctx; std::unique_ptr creditPoolManager; + std::unique_ptr mnhf_manager; std::unique_ptr cj_ctx; std::unique_ptr evodb; diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp index 7a66cf10b151a..c077f7bd7f348 100644 --- a/src/test/util/setup_common.cpp +++ b/src/test/util/setup_common.cpp @@ -67,6 +67,7 @@ #include #include #include +#include #include #include @@ -164,6 +165,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); @@ -180,6 +182,7 @@ BasicTestingSetup::~BasicTestingSetup() connman.reset(); llmq::quorumSnapshotManager.reset(); creditPoolManager.reset(); + m_node.mnhf_manager.reset(); m_node.evodb.reset(); LogInstance().DisconnectTestLogger(); @@ -251,7 +254,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 7d42d8b6f499c..a79c0f6b1db4a 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 9755fee627fc7..2b7b8a4da75ea 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 e7dac12919291..cea4ffe0457df 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/validation.cpp b/src/validation.cpp index 3dbfe1a205d80..0e41cdbb80412 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -1260,12 +1261,14 @@ void CoinsViews::InitCache() CChainState::CChainState(CTxMemPool* mempool, BlockManager& blockman, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, const std::unique_ptr& quorum_block_processor, std::optional from_snapshot_blockhash) : m_mempool(mempool), + m_mnhfManager(mnhfManager), m_params(::Params()), m_clhandler(clhandler), m_isman(isman), @@ -1706,7 +1709,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; } @@ -2186,7 +2189,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()); } @@ -4809,7 +4812,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()); } @@ -5651,6 +5654,7 @@ std::vector ChainstateManager::GetAll() } CChainState& ChainstateManager::InitializeChainstate(CTxMemPool* mempool, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -5665,7 +5669,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)) { @@ -5735,9 +5739,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 17ff9cb866ad1..3f58c5ed7648f 100644 --- a/src/validation.h +++ b/src/validation.h @@ -56,6 +56,7 @@ class CChainParams; struct CCheckpointData; class CInv; class CConnman; +class CMNHFManager; class CScriptCheck; class CTxMemPool; class TxValidationState; @@ -584,6 +585,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: @@ -593,6 +595,7 @@ class CChainState explicit CChainState(CTxMemPool* mempool, BlockManager& blockman, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -940,6 +943,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/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index f715ba437ad8f..ed25df4f214a8 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -104,6 +104,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "llmq/debug -> llmq/dkgsessionhandler -> llmq/debug" "llmq/debug -> llmq/dkgsessionhandler -> llmq/dkgsession -> llmq/debug" "llmq/utils -> validation -> llmq/utils" + "evo/mnhftx -> validation -> evo/mnhftx" ) EXIT_CODE=0 From b85a497ccac0f7816276150d114c8c1fbfa41794 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Fri, 4 Aug 2023 04:01:47 +0700 Subject: [PATCH 04/21] feat: new functional test for feature MnEHF --- test/functional/feature_mnehf.py | 184 +++++++++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 185 insertions(+) create mode 100755 test/functional/feature_mnehf.py diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py new file mode 100755 index 0000000000000..b3d611706c4de --- /dev/null +++ b/test/functional/feature_mnehf.py @@ -0,0 +1,184 @@ +#!/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(" Date: Tue, 8 Aug 2023 15:44:16 +0700 Subject: [PATCH 05/21] feat: improve functional tests for MnEHF to check block reconsideration --- test/functional/feature_mnehf.py | 41 +++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index b3d611706c4de..14fbd8033e5c8 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -144,9 +144,9 @@ def run_test(self): node.generate(1) self.sync_all() - self.log.info(f"Check MnEhfTx {tx_sent} was mined...") - block = node.getblock(node.getbestblockhash()) - assert tx_sent in block['tx'] + ehf_block = node.getbestblockhash() + self.log.info(f"Check MnEhfTx {tx_sent} was mined in {ehf_block}") + assert tx_sent in node.getblock(ehf_block)['tx'] self.log.info(f"MnEhf tx: '{tx}' is sent: {tx_sent}") self.log.info(f"mempool: {node.getmempoolinfo()}") @@ -173,6 +173,41 @@ def run_test(self): if i == 7: self.restart_all_nodes() + self.check_fork('active') + + block_fork_active = node.getbestblockhash() + self.log.info(f"Invalidate block: {ehf_block} with tip {block_fork_active}") + for inode in self.nodes: + inode.invalidateblock(ehf_block) + + self.log.info("Expecting for fork to be defined in next blocks because no MnEHF tx here") + for i in range(12): + self.check_fork('defined') + node.generate(1) + self.sync_all() + + + self.log.info("Re-sending MnEHF for new fork") + tx_sent_2 = self.send_tx(tx) + node.generate(1) + self.sync_all() + + ehf_block_2 = node.getbestblockhash() + self.log.info(f"Check MnEhfTx again {tx_sent_2} was mined in {ehf_block_2}") + assert tx_sent_2 in node.getblock(ehf_block_2)['tx'] + + self.log.info(f"Generate some more block to jump to `started` status") + for i in range(12): + node.generate(1) + self.check_fork('started') + self.restart_all_nodes() + self.check_fork('started') + + + self.log.info(f"Re-consider block {ehf_block} to the old MnEHF and forget new fork") + for inode in self.nodes: + inode.reconsiderblock(ehf_block) + assert_equal(node.getbestblockhash(), block_fork_active) self.check_fork('active') self.restart_all_nodes() From 4e03666ec907715691b3e74b0277458f86c6f31d Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 17 Aug 2023 09:25:32 +0700 Subject: [PATCH 06/21] Update src/chainparams.cpp Co-authored-by: UdjinM6 --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index d0c32fbfc8b7e..b8bee3e7e0c07 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -111,7 +111,7 @@ bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePas if (consensus.vDeployments[index].bit == nBit) { auto& deployment = consensus.vDeployments[index]; if (timePast > deployment.nTimeout) { - LogPrintf("%s: activation by bit=%d time-outed at height=%d\n", __func__, nBit, height); + 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) { From bb8d06ae843eff5b5b9605bbec5d3436ee432d63 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Sep 2023 15:57:37 +0700 Subject: [PATCH 07/21] fix: conflict resolve due to new fuzz test (versionbits) and -Wno-reorder compile flag --- src/test/fuzz/versionbits.cpp | 1 + src/validation.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/fuzz/versionbits.cpp b/src/test/fuzz/versionbits.cpp index 9563908679aa5..1841a7d301860 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/validation.cpp b/src/validation.cpp index 0e41cdbb80412..73721905b4028 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1268,11 +1268,11 @@ CChainState::CChainState(CTxMemPool* mempool, const std::unique_ptr& quorum_block_processor, std::optional from_snapshot_blockhash) : m_mempool(mempool), - m_mnhfManager(mnhfManager), m_params(::Params()), 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) {} From 628ce18139f11f62208b405ac40e67eea2683a40 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 4 Sep 2023 18:01:28 +0700 Subject: [PATCH 08/21] feat: let unknown deployments to be mined in blocks --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index b8bee3e7e0c07..e16dc7da0b430 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -126,7 +126,7 @@ bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePas } } LogPrintf("%s: not found MnEHF fork bit=%d\n", __func__, nBit); - return false; + return true; } void CChainParams::AddLLMQ(Consensus::LLMQType llmqType) From 0878e184da661731b22010dcc59d984ed4383fc8 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 6 Sep 2023 18:53:29 +0700 Subject: [PATCH 09/21] fix: check MnEHF earlier --- src/evo/mnhftx.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index ef2ebff68c0ee..e6679c18953ad 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -80,6 +80,10 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida return false; } + if (!Params().UpdateMNActivationParam(mnhfTx.signal.versionBit, pindexPrev->nHeight, pindexPrev->GetMedianTimePast(), true /* fJustCheck */)) { + return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-non-ehf"); + } + return true; } From ef14b53b3d0c84ee7e41e66d83a2ea7e1133961b Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 6 Sep 2023 18:53:51 +0700 Subject: [PATCH 10/21] feat: add functional test for unknown and invalid version bits of EHF release --- test/functional/feature_mnehf.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 14fbd8033e5c8..5746b218f5116 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -124,31 +124,37 @@ def run_test(self): key = ECKey() key.generate() pubkey = key.get_pubkey().get_bytes() - tx = self.create_mnehf(28, pubkey) + ehf_tx = self.create_mnehf(28, pubkey) + ehf_unknown_tx = self.create_mnehf(27, pubkey) + ehf_invalid_tx = self.create_mnehf(9, pubkey) # deployment that is known as non-EHF self.log.info("Checking deserialization of CMnEhf by python's code") mnehf_payload = CMnEhf() - mnehf_payload.deserialize(BytesIO(tx.vExtraPayload)) + mnehf_payload.deserialize(BytesIO(ehf_tx.vExtraPayload)) assert_equal(mnehf_payload.version, 1) assert_equal(mnehf_payload.versionBit, 28) self.log.info("Checking correctness of requestId and quorumHash") assert_equal(mnehf_payload.quorumHash, int(self.mninfo[0].node.quorum("selectquorum", 100, 'a0eee872d7d3170dd20d5c5e8380c92b3aa887da5f63d8033289fafa35a90691')["quorumHash"], 16)) - self.send_tx(tx, expected_error='mnhf-before-v20') + self.send_tx(ehf_tx, expected_error='mnhf-before-v20') assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') self.activate_v20() assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') - tx_sent = self.send_tx(tx) + ehf_tx_sent = self.send_tx(ehf_tx) + ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx) + self.send_tx(ehf_invalid_tx, expected_error='bad-mnhf-non-ehf') node.generate(1) self.sync_all() ehf_block = node.getbestblockhash() - self.log.info(f"Check MnEhfTx {tx_sent} was mined in {ehf_block}") - assert tx_sent in node.getblock(ehf_block)['tx'] + self.log.info(f"Check MnEhfTx {ehf_tx_sent} was mined in {ehf_block}") + assert ehf_tx_sent in node.getblock(ehf_block)['tx'] + assert ehf_unknown_tx_sent in node.getblock(ehf_block)['tx'] - self.log.info(f"MnEhf tx: '{tx}' is sent: {tx_sent}") + self.log.info(f"MnEhf tx: '{ehf_tx}' is sent: {ehf_tx_sent}") + self.log.info(f"MnEhf 'unknown' tx: '{ehf_unknown_tx}' is sent: {ehf_unknown_tx_sent}") self.log.info(f"mempool: {node.getmempoolinfo()}") assert_equal(node.getmempoolinfo()['size'], 0) @@ -188,7 +194,7 @@ def run_test(self): self.log.info("Re-sending MnEHF for new fork") - tx_sent_2 = self.send_tx(tx) + tx_sent_2 = self.send_tx(ehf_tx) node.generate(1) self.sync_all() From 5bcbcc8dc2b906824655701c9c58a406dca68fbf Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 6 Sep 2023 20:10:26 +0700 Subject: [PATCH 11/21] docs: documented UpdateMNActivationParam --- src/chainparams.h | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/chainparams.h b/src/chainparams.h index 1aa84dfaf7845..f5f116a3267c8 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -134,6 +134,15 @@ class CChainParams void UpdateDIP8Parameters(int nActivationHeight); void UpdateBudgetParameters(int nMasternodePaymentsStartBlock, int nBudgetPaymentsStartBlock, int nSuperblockStartBlock); void UpdateLLMQInstantSend(Consensus::LLMQType llmqType); + /** + * Update params for Masternodes EHF + * + * @param[in] nBit The version bit to update + * @param[in] height The height of block where that signal is mined + * @param[in] timePast The block time to validate if release is already time-outed + * @param[in] fJustCheck If true do not update any internal data, only validate params + * @return Whether params are legit and params are updated (if release is known) + */ bool UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const; int PoolMinParticipants() const { return nPoolMinParticipants; } int PoolMaxParticipants() const { return nPoolMaxParticipants; } From df4c366e6baccc7a4618689942f45cb995c96026 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 11 Sep 2023 15:32:54 +0700 Subject: [PATCH 12/21] fix: logs in chainparams moved out from if(fJustCheck) --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e16dc7da0b430..333e512389e39 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -118,8 +118,8 @@ bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePas LogPrintf("%s: trying to set MnEHF height=%d for non-masternode activation fork bit=%d\n", __func__, height, nBit); return false; } + LogPrintf("%s: set MnEHF height=%d for bit=%d fJustCheck=%d is valid\n", __func__, height, nBit, fJustCheck); 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; From 5d9085f8cb11ea3a9bbd0b35efa5a68201936717 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Mon, 11 Sep 2023 16:00:59 +0700 Subject: [PATCH 13/21] Update src/chainparams.cpp Co-authored-by: UdjinM6 --- src/chainparams.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 333e512389e39..3bc1e3425da22 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -125,7 +125,7 @@ bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePas return true; } } - LogPrintf("%s: not found MnEHF fork bit=%d\n", __func__, nBit); + LogPrintf("%s: WARNING: unknown MnEHF fork bit=%d\n", __func__, nBit); return true; } From 7b18bc83686a0abe3cfcb92bc3a231888b9e26d0 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 20 Sep 2023 14:12:57 +0700 Subject: [PATCH 14/21] fix: EHF takes care not only about nTimeOut but about nStartTime also --- src/chainparams.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 3bc1e3425da22..738cc63cc7cd6 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -110,8 +110,8 @@ bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePas 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); + if (timePast > deployment.nTimeout || timePast < deployment.nStartTime) { + LogPrintf("%s: activation by bit=%d height=%d deployment='%s' is out of time range start=%lld timeout=%lld\n", __func__, nBit, height, VersionBitsDeploymentInfo[Consensus::DeploymentPos(index)].name, deployment.nStartTime, deployment.nTimeout); continue; } if (deployment.nMNActivationHeight < 0) { From 3973f2b92564ac682fb89c55eafd6a24b6cc8162 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Wed, 20 Sep 2023 15:36:59 +0700 Subject: [PATCH 15/21] feat: update functional tests for Mn EHF - to use same bit more than once --- test/functional/feature_mnehf.py | 37 ++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 5746b218f5116..3b4f1f484c2aa 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -31,12 +31,21 @@ def set_test_params(self): def skip_test_if_missing_module(self): self.skip_if_no_wallet() - def restart_all_nodes(self): + def restart_all_nodes(self, params=None): 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) + if params is not None: + self.extra_args[inode][0] = f"-vbparams=testdummy:{params[0]}:{params[1]}:12:12:12:5:0" + self.log.info(f"Actual restart options: {self.extra_args[inode]}") + + self.restart_node(0) + for mn in self.mninfo: + index = mn.node.index + self.stop_node(index) + self.start_masternode(mn) + for i in range(len(self.nodes)): + self.connect_nodes(i, 0) + def create_mnehf(self, versionBit, pubkey=None): # request ID = sha256("mnhf", versionBit) @@ -219,6 +228,26 @@ def run_test(self): self.restart_all_nodes() self.check_fork('active') + self.bump_mocktime(int(60 * 60 * 24 * 14)) + node.generate(1) + self.sync_all() + self.restart_all_nodes(params=[self.mocktime, self.mocktime + 1000000]) + self.mine_quorum() + + ehf_tx_second = self.create_mnehf(28, pubkey) + assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') + ehf_tx_sent = self.send_tx(ehf_tx_second) + node.generate(1) + self.sync_all() + + self.check_fork('defined') + for i in range(10): + self.log.info(f"Generating {i} ...") + self.bump_mocktime(next) + node.generate(next) + self.sync_all() + self.check_fork('started') + if __name__ == '__main__': From 92be5e0be777ec78b80de3fb66e1e8297e238ec9 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Thu, 21 Sep 2023 14:20:42 +0700 Subject: [PATCH 16/21] fix: now EHF transactions expires after nExpiryEHF blocks --- src/chainparams.cpp | 4 +++ src/consensus/params.h | 2 ++ src/evo/mnhftx.cpp | 62 ++++++++++++++++++++++++-------- src/evo/mnhftx.h | 19 ++++++++-- src/miner.cpp | 16 +++++++-- src/miner.h | 3 +- src/validation.h | 2 ++ test/functional/feature_mnehf.py | 39 ++++++++++++++------ 8 files changed, 115 insertions(+), 32 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 738cc63cc7cd6..e1245ecdc4229 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -192,6 +192,7 @@ class CMainParams : public CChainParams { consensus.DIP0024Height = 1737792; // 0000000000000001342be9c0b75ad40c276beaad91616423c4d9cb101b3db438 consensus.V19Height = 1899072; // 0000000000000015e32e73052d663626327004c81c5c22cb8b42c361015c0eae consensus.MinBIP9WarningHeight = 1899072 + 2016; // V19 activation height + miner confirmation window + consensus.nExpireEHF = 576 * 365; // one year consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 20 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -389,6 +390,7 @@ class CTestNetParams : public CChainParams { consensus.DIP0024Height = 769700; // 0000008d84e4efd890ae95c70a7a6126a70a80e5c19e4cb264a5b3469aeef172 consensus.V19Height = 850100; // 000004728b8ff2a16b9d4eebb0fd61eeffadc9c7fe4b0ec0b5a739869401ab5b consensus.MinBIP9WarningHeight = 850100 + 2016; // v19 activation height + miner confirmation window + consensus.nExpireEHF = 576 * 365; // one year consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 20 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -560,6 +562,7 @@ class CDevNetParams : public CChainParams { consensus.DIP0024Height = 300; consensus.V19Height = 300; consensus.MinBIP9WarningHeight = 300 + 2016; // v19 activation height + miner confirmation window + consensus.nExpireEHF = 576 * 365; // one year consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 1 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -798,6 +801,7 @@ class CRegTestParams : public CChainParams { consensus.DIP0024Height = 900; consensus.V19Height = 900; consensus.MinBIP9WarningHeight = 0; + consensus.nExpireEHF = 576; // one day for RegTest consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 1 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes diff --git a/src/consensus/params.h b/src/consensus/params.h index f06f4352defcf..6db88e242bbfb 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -112,6 +112,8 @@ struct Params { /** Don't warn about unknown BIP 9 activations below this height. * This prevents us from warning about the CSV and DIP activations. */ int MinBIP9WarningHeight; + /** this value is used to expire signals of EHF */ + int nExpireEHF; /** * Minimum blocks including miner confirmation of the total of nMinerConfirmationWindow blocks in a retargeting period, * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index e6679c18953ad..33765c64a342c 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -22,7 +22,22 @@ 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 +CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev) +{ + Signals signals = GetFromCache(pindexPrev); + const int height = pindexPrev->nHeight + 1; + for (auto it = signals.begin(); it != signals.end(); ) { + if (height > it->second + Params().GetConsensus().nExpireEHF) { + LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is expired at height=%d\n", it->first, it->second, height); + it = signals.erase(it); + } else { + ++it; + } + } + return signals; +} + +bool MNHFTx::Verify(const CBlockIndex* const pQuorumIndex, const uint256& msgHash, TxValidationState& state) const { if (versionBit >= VERSIONBITS_NUM_BITS) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds"); @@ -75,18 +90,33 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida SetTxPayload(tx_copy, payload_copy); uint256 msgHash = tx_copy.GetHash(); + if (!mnhfTx.signal.Verify(pindexQuorum, msgHash, state)) { // set up inside Verify return false; } - if (!Params().UpdateMNActivationParam(mnhfTx.signal.versionBit, pindexPrev->nHeight, pindexPrev->GetMedianTimePast(), true /* fJustCheck */)) { + if (!Params().UpdateMNActivationParam(mnhfTx.signal.versionBit, pindexPrev->nHeight, pindexPrev->GetMedianTimePast(), /* fJustCheck= */ true )) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-non-ehf"); } return true; } +std::optional extractEHFSignal(const CTransaction& tx) +{ + if (tx.nVersion != 3 || tx.nType != TRANSACTION_MNHF_SIGNAL) { + // only interested in special TXs 'TRANSACTION_MNHF_SIGNAL' + return std::nullopt; + } + + MNHFTxPayload mnhfTx; + if (!GetTxPayload(tx, mnhfTx)) { + return std::nullopt; + } + return mnhfTx.signal.versionBit; +} + static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, std::vector& signals_to_process, BlockValidationState& state) { AssertLockHeld(cs_main); @@ -116,7 +146,7 @@ static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, 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 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates-in-block"); } return true; @@ -130,19 +160,19 @@ bool CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pi // state is set inside extractSignals return false; } + Signals signals = GetSignalsStage(pindex->pprev); if (new_signals.empty()) { if (!fJustCheck) { - AddToCache(GetFromCache(pindex->pprev), pindex); + AddToCache(signals, 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()); + LogPrintf("CMNHFManager::ProcessBlock: add mnhf bit=%d block:%s number of known signals:%lld\n", versionBit, pindex->GetBlockHash().ToString(), signals.size()); if (signals.find(versionBit) != signals.end()) { return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicate"); } @@ -168,7 +198,7 @@ bool CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pi AddToCache(signals, pindex); return true; } catch (const std::exception& e) { - LogPrintf("%s -- failed: %s\n", __func__, e.what()); + LogPrintf("CMNHFManager::ProcessBlock -- failed: %s\n", e.what()); return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-proc-mnhf-inblock"); } } @@ -204,24 +234,26 @@ void CMNHFManager::UpdateChainParams(const CBlockIndex* const pindex, const CBlo 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; + const 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); + bool update_ret = Params().UpdateMNActivationParam(versionBit, 0, pindex->GetMedianTimePast(), /* fJustCheck= */ false); assert(update_ret); } Signals signals{GetFromCache(pindex)}; for (const auto& signal: signals) { - uint8_t versionBit = signal.first; - int value = signal.second; + const uint8_t versionBit = signal.first; + const 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); + bool update_ret = Params().UpdateMNActivationParam(versionBit, value, pindex->GetMedianTimePast(), /* fJustCheck= */ false); assert(update_ret); } } @@ -240,9 +272,9 @@ CMNHFManager::Signals CMNHFManager::GetFromCache(const CBlockIndex* const pindex } if (VersionBitsState(pindex->pprev, Params().GetConsensus(), Consensus::DEPLOYMENT_V20, versionbitscache) != ThresholdState::ACTIVE) { LOCK(cs_cache); - mnhfCache.insert(blockHash, signals); + mnhfCache.insert(blockHash, {}); LogPrintf("CMNHFManager::GetFromCache: mnhf feature is disabled: return empty for block %s\n", pindex->GetBlockHash().ToString()); - return signals; + return {}; } 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()); @@ -258,7 +290,7 @@ void CMNHFManager::AddToCache(const Signals& signals, const CBlockIndex* const p 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()); + LogPrintf("CMNHFManager::AddToCache: mnhf for block %s add to cache: %lld\n", pindex->GetBlockHash().ToString(), signals.size()); mnhfCache.insert(blockHash, signals); } m_evoDb.Write(std::make_pair(DB_SIGNALS, blockHash), signals); diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index ef70bb40620d0..67320bf9ecf11 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -31,7 +32,7 @@ class MNHFTx CBLSSignature sig; MNHFTx() = default; - bool Verify(const CBlockIndex* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const; + bool Verify(const CBlockIndex* const pQuorumIndex, const uint256& msgHash, TxValidationState& state) const; SERIALIZE_METHODS(MNHFTx, obj) { @@ -93,7 +94,7 @@ class CMNHFManager unordered_lru_cache mnhfCache GUARDED_BY(cs_cache) {MNHFCacheSize}; public: - explicit CMNHFManager(CEvoDB& evoDb) : + explicit CMNHFManager(CEvoDB& evoDb) : m_evoDb(evoDb) {} ~CMNHFManager() = default; @@ -113,11 +114,25 @@ class CMNHFManager */ void UpdateChainParams(const CBlockIndex* const pindex, const CBlockIndex* const pindexOld) EXCLUSIVE_LOCKS_REQUIRED(cs_main); + /** + * This function prepares signals for new block. + * This data is filterd expired signals from previous blocks. + * 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); + + /** + * This function returns list of signals available on previous block. + * NOTE: that some signals could expired between blocks. + * validate them by + */ Signals GetFromCache(const CBlockIndex* const pindex); + }; +std::optional extractEHFSignal(const CTransaction& tx); bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main); #endif // BITCOIN_EVO_MNHFTX_H diff --git a/src/miner.cpp b/src/miner.cpp index ecdbd51af9418..75cc986fda644 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -174,7 +175,8 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LogPrintf("%s: CCreditPool is %s\n", __func__, creditPool.ToString()); creditPoolDiff.emplace(std::move(creditPool), pindexPrev, chainparams.GetConsensus()); } - addPackageTxs(nPackagesSelected, nDescendantsUpdated, creditPoolDiff); + std::unordered_map signals = m_chainstate.m_mnhfManager.GetSignalsStage(pindexPrev); + addPackageTxs(nPackagesSelected, nDescendantsUpdated, creditPoolDiff, signals); int64_t nTime1 = GetTimeMicros(); @@ -400,7 +402,7 @@ void BlockAssembler::SortForBlock(const CTxMemPool::setEntries& package, std::ve // Each time through the loop, we compare the best transaction in // mapModifiedTxs with the next transaction in the mempool to decide what // transaction package to work on next. -void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, std::optional& creditPoolDiff) +void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpdated, std::optional& creditPoolDiff, std::unordered_map& signals) { AssertLockHeld(m_mempool.cs); @@ -460,7 +462,7 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda if (creditPoolDiff != std::nullopt) { // If one transaction is skipped due to limits, it is not a reason to interrupt // whole process of adding transactions. - // `state` is local here because used to log info about this specific tx + // `state` is local here because used only to log info about this specific tx TxValidationState state; if (!creditPoolDiff->ProcessLockUnlockTransaction(iter->GetTx(), state)) { @@ -473,6 +475,14 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } } + if (std::optional signal = extractEHFSignal(iter->GetTx()); signal != std::nullopt) { + if (signals.find(*signal) != signals.end()) { + LogPrintf("%s: ehf signal tx %s skipped due to duplicate %d\n", + __func__, iter->GetTx().GetHash().ToString(), *signal); + continue; + } + signals.insert({*signal, 0}); + } // We skip mapTx entries that are inBlock, and mapModifiedTx shouldn't // contain anything that is inBlock. diff --git a/src/miner.h b/src/miner.h index 465cd891dc629..048c338928449 100644 --- a/src/miner.h +++ b/src/miner.h @@ -194,7 +194,8 @@ class BlockAssembler /** Add transactions based on feerate including unconfirmed ancestors * Increments nPackagesSelected / nDescendantsUpdated with corresponding * statistics from the package selection (for logging statistics). */ - void addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated, std::optional& creditPoolDiff) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); + void addPackageTxs(int& nPackagesSelected, int& nDescendantsUpdated, + std::optional& creditPoolDiff, std::unordered_map& signals) EXCLUSIVE_LOCKS_REQUIRED(m_mempool.cs); // helper functions for addPackageTxs() /** Remove confirmed (inBlock) entries from given set */ diff --git a/src/validation.h b/src/validation.h index 3f58c5ed7648f..2cbf5259e4880 100644 --- a/src/validation.h +++ b/src/validation.h @@ -585,7 +585,9 @@ class CChainState const std::unique_ptr& m_clhandler; const std::unique_ptr& m_isman; const std::unique_ptr& m_quorum_block_processor; +public: CMNHFManager& m_mnhfManager; +private: CEvoDB& m_evoDb; public: diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 3b4f1f484c2aa..3dab0efccac31 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -43,9 +43,18 @@ def restart_all_nodes(self, params=None): index = mn.node.index self.stop_node(index) self.start_masternode(mn) - for i in range(len(self.nodes)): + for i in range(1, self.num_nodes): self.connect_nodes(i, 0) + def slowly_generate_batch(self, amount): + self.log.info(f"Slowly generate {amount} blocks") + while amount > 0: + self.log.info(f"Generating batch of blocks {amount} left") + next = min(10, amount) + amount -= next + self.bump_mocktime(next) + self.nodes[1].generate(next) + self.sync_all() def create_mnehf(self, versionBit, pubkey=None): # request ID = sha256("mnhf", versionBit) @@ -69,7 +78,7 @@ def create_mnehf(self, versionBit, pubkey=None): mnehf_tx.calc_sha256() msgHash = format(mnehf_tx.sha256, '064x') - self.log.info(f"Signing request_id: {request_id} msgHash: {msgHash}") + self.log.info(f"Signing request_id: {request_id} msgHash: {msgHash} quorum: {quorumHash}") recsig = self.get_recovered_sig(request_id, msgHash) mnehf_payload.quorumSig = bytearray.fromhex(recsig["sig"]) @@ -94,10 +103,11 @@ def check_fork(self, expected): def ensure_tx_is_not_mined(self, tx_id): try: - self.nodes[0].gettransaction(tx_id) + assert_equal(self.nodes[0].getrawtransaction(tx_id, 1)['height'], -1); raise AssertionError("Transaction should not be mined") - except JSONRPCException as e: - assert "Invalid or non-wallet transaction id" in e.error['message'] + except KeyError as e: + # KeyError is expected + pass def send_tx(self, tx, expected_error = None, reason = None): try: @@ -155,6 +165,7 @@ def run_test(self): ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx) self.send_tx(ehf_invalid_tx, expected_error='bad-mnhf-non-ehf') node.generate(1) + ehf_height = node.getblockcount() self.sync_all() ehf_block = node.getbestblockhash() @@ -236,17 +247,23 @@ def run_test(self): ehf_tx_second = self.create_mnehf(28, pubkey) assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') + + self.log.info("Ehf with same bit signal should fail after 575 blocks but be accepted after 576 on regnet.") + self.log.info(f"Current progress is from {ehf_height} to {node.getblockcount()}") + self.slowly_generate_batch(576 - (node.getblockcount() - ehf_height)) ehf_tx_sent = self.send_tx(ehf_tx_second) + self.log.info(f"ehf tx sent: {ehf_tx_sent}") + self.log.info(f"block: {node.getblock(node.getbestblockhash())}") + self.ensure_tx_is_not_mined(ehf_tx_sent) node.generate(1) self.sync_all() + self.log.info(f"block: {node.getblock(node.getbestblockhash())}") + block = node.getblock(node.getbestblockhash()) + assert ehf_tx_sent in block['tx'] self.check_fork('defined') - for i in range(10): - self.log.info(f"Generating {i} ...") - self.bump_mocktime(next) - node.generate(next) - self.sync_all() - self.check_fork('started') + self.slowly_generate_batch(12 * 4) + self.check_fork('active') From 13f28a01940806302547d1d29e9c0dd298c418a8 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 24 Sep 2023 01:33:30 +0700 Subject: [PATCH 17/21] fix: mark invalid EHF tx in mempool --- src/miner.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/miner.cpp b/src/miner.cpp index 75cc986fda644..e8fbc2a541c26 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -477,6 +477,10 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda } if (std::optional signal = extractEHFSignal(iter->GetTx()); signal != std::nullopt) { if (signals.find(*signal) != signals.end()) { + if (fUsingModified) { + mapModifiedTx.get().erase(modit); + failedTx.insert(iter); + } LogPrintf("%s: ehf signal tx %s skipped due to duplicate %d\n", __func__, iter->GetTx().GetHash().ToString(), *signal); continue; From 5e31bd55457d55d6da1633997ca20b32800947c2 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 24 Sep 2023 01:45:42 +0700 Subject: [PATCH 18/21] refactor: multiple fixes, cleanups, improvements and refactorings --- src/chainparams.cpp | 3 +++ src/evo/mnhftx.cpp | 23 +++++++---------------- src/miner.cpp | 2 +- src/validation.cpp | 7 +++++++ src/validation.h | 6 +++--- test/functional/feature_mnehf.py | 12 ++++++------ test/functional/test_runner.py | 2 +- 7 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index e1245ecdc4229..a0ffa23236122 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -107,6 +108,8 @@ static CBlock FindDevNetGenesisBlock(const CBlock &prevBlock, const CAmount& rew bool CChainParams::UpdateMNActivationParam(int nBit, int height, int64_t timePast, bool fJustCheck) const { + assert(nBit < VERSIONBITS_NUM_BITS); + for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) { if (consensus.vDeployments[index].bit == nBit) { auto& deployment = consensus.vDeployments[index]; diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 33765c64a342c..fe95f782dadb5 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -117,7 +117,7 @@ std::optional extractEHFSignal(const CTransaction& tx) return mnhfTx.signal.versionBit; } -static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, std::vector& signals_to_process, BlockValidationState& state) +static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, std::vector& new_signals, BlockValidationState& state) { AssertLockHeld(cs_main); @@ -139,14 +139,11 @@ static bool extractSignals(const CBlock& block, const CBlockIndex* const pindex, 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-in-block"); + const uint8_t bit = mnhfTx.signal.versionBit; + if (std::find(new_signals.begin(), new_signals.end(), bit) != new_signals.end()) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates-in-block"); + } + new_signals.push_back(bit); } return true; @@ -162,7 +159,7 @@ bool CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pi } Signals signals = GetSignalsStage(pindex->pprev); if (new_signals.empty()) { - if (!fJustCheck) { + if (fJustCheck) { AddToCache(signals, pindex); } return true; @@ -217,8 +214,6 @@ bool CMNHFManager::UndoBlock(const CBlock& block, const CBlockIndex* const pinde 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()); @@ -236,8 +231,6 @@ void CMNHFManager::UpdateChainParams(const CBlockIndex* const pindex, const CBlo for (const auto& signal: signals_old) { const 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(), /* fJustCheck= */ false); @@ -249,8 +242,6 @@ void CMNHFManager::UpdateChainParams(const CBlockIndex* const pindex, const CBlo const uint8_t versionBit = signal.first; const 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(), /* fJustCheck= */ false); diff --git a/src/miner.cpp b/src/miner.cpp index e8fbc2a541c26..ec74030474206 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -175,7 +175,7 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc LogPrintf("%s: CCreditPool is %s\n", __func__, creditPool.ToString()); creditPoolDiff.emplace(std::move(creditPool), pindexPrev, chainparams.GetConsensus()); } - std::unordered_map signals = m_chainstate.m_mnhfManager.GetSignalsStage(pindexPrev); + std::unordered_map signals = m_chainstate.GetMNHFSignalsStage(); addPackageTxs(nPackagesSelected, nDescendantsUpdated, creditPoolDiff, signals); int64_t nTime1 = GetTimeMicros(); diff --git a/src/validation.cpp b/src/validation.cpp index 73721905b4028..4ced61248165b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -2493,6 +2493,13 @@ CoinsCacheSizeState CChainState::GetCoinsCacheSizeState( return CoinsCacheSizeState::OK; } +std::unordered_map CChainState::GetMNHFSignalsStage() +{ + const CBlockIndex* const tip = m_chain.Tip(); + if (tip == nullptr) return {}; + return this->m_mnhfManager.GetSignalsStage(tip); +} + bool CChainState::FlushStateToDisk( BlockValidationState &state, FlushStateMode mode, diff --git a/src/validation.h b/src/validation.h index 2cbf5259e4880..02089a4699a36 100644 --- a/src/validation.h +++ b/src/validation.h @@ -585,9 +585,7 @@ class CChainState const std::unique_ptr& m_clhandler; const std::unique_ptr& m_isman; const std::unique_ptr& m_quorum_block_processor; -public: CMNHFManager& m_mnhfManager; -private: CEvoDB& m_evoDb; public: @@ -780,8 +778,10 @@ class CChainState size_t max_coins_cache_size_bytes, size_t max_mempool_size_bytes) EXCLUSIVE_LOCKS_REQUIRED(::cs_main); - std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + /** Return list of MN EHF signals for current Tip() */ + std::unordered_map GetMNHFSignalsStage() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); + std::string ToString() EXCLUSIVE_LOCKS_REQUIRED(::cs_main); private: bool ActivateBestChainStep(BlockValidationState& state, CBlockIndex* pindexMostWork, const std::shared_ptr& pblock, bool& fInvalidFound, ConnectTrace& connectTrace) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); bool ConnectTip(BlockValidationState& state, CBlockIndex* pindexNew, const std::shared_ptr& pblock, ConnectTrace& connectTrace, DisconnectedBlockTransactions& disconnectpool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_mempool->cs); diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 3dab0efccac31..3ee7d672d01b0 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -248,16 +248,16 @@ def run_test(self): ehf_tx_second = self.create_mnehf(28, pubkey) assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') - self.log.info("Ehf with same bit signal should fail after 575 blocks but be accepted after 576 on regnet.") - self.log.info(f"Current progress is from {ehf_height} to {node.getblockcount()}") - self.slowly_generate_batch(576 - (node.getblockcount() - ehf_height)) + self.log.info("Testing EHF signal with same bit") + self.log.info(f"Previous signal at height={ehf_height}, total blocks={node.getblockcount()}, should success at {ehf_hight + 576}") + self.slowly_generate_batch(576 - (node.getblockcount() - ehf_height) - 1) ehf_tx_sent = self.send_tx(ehf_tx_second) - self.log.info(f"ehf tx sent: {ehf_tx_sent}") - self.log.info(f"block: {node.getblock(node.getbestblockhash())}") + self.log.info("Mine block and ensure not mined yet...") + node.generate(1) self.ensure_tx_is_not_mined(ehf_tx_sent) node.generate(1) self.sync_all() - self.log.info(f"block: {node.getblock(node.getbestblockhash())}") + self.log.info("Mine one more block and ensure it is mined") block = node.getblock(node.getbestblockhash()) assert ehf_tx_sent in block['tx'] diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 444ed6d8af2b3..f7cfb49287fe8 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -121,10 +121,10 @@ 'feature_llmq_dkgerrors.py', # NOTE: needs dash_hash to pass 'feature_dip4_coinbasemerkleroots.py', # NOTE: needs dash_hash to pass 'feature_asset_locks.py', # NOTE: needs dash_hash to pass + 'feature_mnehf.py', # NOTE: needs dash_hash to pass # vv Tests less than 60s vv 'p2p_sendheaders.py', # NOTE: needs dash_hash to pass 'p2p_sendheaders_compressed.py', # NOTE: needs dash_hash to pass - 'feature_mnehf.py', # NOTE: needs dash_hash to pass 'wallet_importmulti.py', 'mempool_limit.py', 'rpc_txoutproof.py', From d83dbd287a9a9527f5de210b0022d88bc0bb0087 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 24 Sep 2023 19:30:28 +0700 Subject: [PATCH 19/21] fix: fix previous commit with fixes --- src/evo/mnhftx.cpp | 2 +- test/functional/feature_mnehf.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index fe95f782dadb5..05d7db3a5b0d6 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -159,7 +159,7 @@ bool CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pi } Signals signals = GetSignalsStage(pindex->pprev); if (new_signals.empty()) { - if (fJustCheck) { + if (!fJustCheck) { AddToCache(signals, pindex); } return true; diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 3ee7d672d01b0..1a082395f809f 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -103,9 +103,9 @@ def check_fork(self, expected): def ensure_tx_is_not_mined(self, tx_id): try: - assert_equal(self.nodes[0].getrawtransaction(tx_id, 1)['height'], -1); + assert_equal(self.nodes[0].getrawtransaction(tx_id, 1)['height'], -1) raise AssertionError("Transaction should not be mined") - except KeyError as e: + except KeyError: # KeyError is expected pass @@ -249,7 +249,7 @@ def run_test(self): assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') self.log.info("Testing EHF signal with same bit") - self.log.info(f"Previous signal at height={ehf_height}, total blocks={node.getblockcount()}, should success at {ehf_hight + 576}") + self.log.info(f"Previous signal at height={ehf_height}, total blocks={node.getblockcount()}, should success at {ehf_height + 576}") self.slowly_generate_batch(576 - (node.getblockcount() - ehf_height) - 1) ehf_tx_sent = self.send_tx(ehf_tx_second) self.log.info("Mine block and ensure not mined yet...") From 4b046bb60808927349cf5556fdb73f04f818057e Mon Sep 17 00:00:00 2001 From: UdjinM6 Date: Tue, 26 Sep 2023 13:25:39 +0300 Subject: [PATCH 20/21] use deployment nStartTime as a signal expiration mark, adjust tests if a signal is mined prior to nStartTime then it means it was mined for one of the previous deployments with the same bit and we can ignore it --- src/chainparams.cpp | 4 --- src/consensus/params.h | 2 -- src/evo/mnhftx.cpp | 26 +++++++++++--- test/functional/feature_mnehf.py | 60 +++++++++++++++----------------- 4 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a0ffa23236122..8716ba49d3bae 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -195,7 +195,6 @@ class CMainParams : public CChainParams { consensus.DIP0024Height = 1737792; // 0000000000000001342be9c0b75ad40c276beaad91616423c4d9cb101b3db438 consensus.V19Height = 1899072; // 0000000000000015e32e73052d663626327004c81c5c22cb8b42c361015c0eae consensus.MinBIP9WarningHeight = 1899072 + 2016; // V19 activation height + miner confirmation window - consensus.nExpireEHF = 576 * 365; // one year consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 20 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -393,7 +392,6 @@ class CTestNetParams : public CChainParams { consensus.DIP0024Height = 769700; // 0000008d84e4efd890ae95c70a7a6126a70a80e5c19e4cb264a5b3469aeef172 consensus.V19Height = 850100; // 000004728b8ff2a16b9d4eebb0fd61eeffadc9c7fe4b0ec0b5a739869401ab5b consensus.MinBIP9WarningHeight = 850100 + 2016; // v19 activation height + miner confirmation window - consensus.nExpireEHF = 576 * 365; // one year consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 20 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -565,7 +563,6 @@ class CDevNetParams : public CChainParams { consensus.DIP0024Height = 300; consensus.V19Height = 300; consensus.MinBIP9WarningHeight = 300 + 2016; // v19 activation height + miner confirmation window - consensus.nExpireEHF = 576 * 365; // one year consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 1 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes @@ -804,7 +801,6 @@ class CRegTestParams : public CChainParams { consensus.DIP0024Height = 900; consensus.V19Height = 900; consensus.MinBIP9WarningHeight = 0; - consensus.nExpireEHF = 576; // one day for RegTest consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // ~uint256(0) >> 1 consensus.nPowTargetTimespan = 24 * 60 * 60; // Dash: 1 day consensus.nPowTargetSpacing = 2.5 * 60; // Dash: 2.5 minutes diff --git a/src/consensus/params.h b/src/consensus/params.h index 6db88e242bbfb..f06f4352defcf 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -112,8 +112,6 @@ struct Params { /** Don't warn about unknown BIP 9 activations below this height. * This prevents us from warning about the CSV and DIP activations. */ int MinBIP9WarningHeight; - /** this value is used to expire signals of EHF */ - int nExpireEHF; /** * Minimum blocks including miner confirmation of the total of nMinerConfirmationWindow blocks in a retargeting period, * (nPowTargetTimespan / nPowTargetSpacing) which is also used for BIP9 deployments. diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 05d7db3a5b0d6..8e8ecd7683b19 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -27,12 +27,28 @@ CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pin Signals signals = GetFromCache(pindexPrev); const int height = pindexPrev->nHeight + 1; for (auto it = signals.begin(); it != signals.end(); ) { - if (height > it->second + Params().GetConsensus().nExpireEHF) { - LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is expired at height=%d\n", it->first, it->second, height); + bool found{false}; + const auto signal_pindex = pindexPrev->GetAncestor(it->second); + assert(signal_pindex != nullptr); + const int64_t signal_time = signal_pindex->GetMedianTimePast(); + for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) { + const auto& deployment = Params().GetConsensus().vDeployments[index]; + if (deployment.bit != it->first) continue; + if (signal_time < deployment.nStartTime) { + // new deployment is using the same bit as the old one + LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is expired at height=%d\n", it->first, it->second, height); + it = signals.erase(it); + } else { + ++it; + } + found = true; + break; + } + if (!found) { + // no deployment means we buried it and aren't using the same bit (yet) + LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is not known at height=%d\n", it->first, it->second, height); it = signals.erase(it); - } else { - ++it; - } + } } return signals; } diff --git a/test/functional/feature_mnehf.py b/test/functional/feature_mnehf.py index 1a082395f809f..9498013e86665 100755 --- a/test/functional/feature_mnehf.py +++ b/test/functional/feature_mnehf.py @@ -25,7 +25,7 @@ class MnehfTest(DashTestFramework): def set_test_params(self): - extra_args = [["-vbparams=testdummy:0:999999999999:12:12:12:5:0"] for _ in range(4)] + extra_args = [["-vbparams=testdummy:0:999999999999:12:12:12:5:0", "-persistmempool=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): @@ -164,14 +164,12 @@ def run_test(self): ehf_tx_sent = self.send_tx(ehf_tx) ehf_unknown_tx_sent = self.send_tx(ehf_unknown_tx) self.send_tx(ehf_invalid_tx, expected_error='bad-mnhf-non-ehf') - node.generate(1) - ehf_height = node.getblockcount() + ehf_blockhash = node.generate(1)[0] self.sync_all() - ehf_block = node.getbestblockhash() - self.log.info(f"Check MnEhfTx {ehf_tx_sent} was mined in {ehf_block}") - assert ehf_tx_sent in node.getblock(ehf_block)['tx'] - assert ehf_unknown_tx_sent in node.getblock(ehf_block)['tx'] + self.log.info(f"Check MnEhfTx {ehf_tx_sent} was mined in {ehf_blockhash}") + assert ehf_tx_sent in node.getblock(ehf_blockhash)['tx'] + assert ehf_unknown_tx_sent in node.getblock(ehf_blockhash)['tx'] self.log.info(f"MnEhf tx: '{ehf_tx}' is sent: {ehf_tx_sent}") self.log.info(f"MnEhf 'unknown' tx: '{ehf_unknown_tx}' is sent: {ehf_unknown_tx_sent}") @@ -201,10 +199,10 @@ def run_test(self): self.check_fork('active') - block_fork_active = node.getbestblockhash() - self.log.info(f"Invalidate block: {ehf_block} with tip {block_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: - inode.invalidateblock(ehf_block) + inode.invalidateblock(ehf_blockhash) self.log.info("Expecting for fork to be defined in next blocks because no MnEHF tx here") for i in range(12): @@ -215,12 +213,11 @@ def run_test(self): self.log.info("Re-sending MnEHF for new fork") tx_sent_2 = self.send_tx(ehf_tx) - node.generate(1) + ehf_blockhash_2 = node.generate(1)[0] self.sync_all() - ehf_block_2 = node.getbestblockhash() - self.log.info(f"Check MnEhfTx again {tx_sent_2} was mined in {ehf_block_2}") - assert tx_sent_2 in node.getblock(ehf_block_2)['tx'] + self.log.info(f"Check MnEhfTx again {tx_sent_2} was mined in {ehf_blockhash_2}") + assert tx_sent_2 in node.getblock(ehf_blockhash_2)['tx'] self.log.info(f"Generate some more block to jump to `started` status") for i in range(12): @@ -230,35 +227,37 @@ def run_test(self): self.check_fork('started') - self.log.info(f"Re-consider block {ehf_block} to the old MnEHF and forget new fork") + self.log.info(f"Re-consider block {ehf_blockhash} to the old MnEHF and forget new fork") for inode in self.nodes: - inode.reconsiderblock(ehf_block) - assert_equal(node.getbestblockhash(), block_fork_active) + inode.reconsiderblock(ehf_blockhash) + assert_equal(node.getbestblockhash(), fork_active_blockhash) self.check_fork('active') self.restart_all_nodes() self.check_fork('active') + self.log.info("Testing duplicate EHF signal with same bit") + ehf_tx_duplicate = self.send_tx(self.create_mnehf(28, pubkey)) + tip_blockhash = node.generate(1)[0] + self.sync_blocks() + block = node.getblock(tip_blockhash) + assert ehf_tx_duplicate in node.getrawmempool() and ehf_tx_duplicate not in block['tx'] + + self.log.info("Testing EHF signal with same bit but with newer start time") self.bump_mocktime(int(60 * 60 * 24 * 14)) node.generate(1) - self.sync_all() + self.sync_blocks() self.restart_all_nodes(params=[self.mocktime, self.mocktime + 1000000]) self.mine_quorum() - ehf_tx_second = self.create_mnehf(28, pubkey) - assert_equal(get_bip9_details(node, 'testdummy')['status'], 'defined') + ehf_tx_new_start = self.create_mnehf(28, pubkey) + self.check_fork('defined') - self.log.info("Testing EHF signal with same bit") - self.log.info(f"Previous signal at height={ehf_height}, total blocks={node.getblockcount()}, should success at {ehf_height + 576}") - self.slowly_generate_batch(576 - (node.getblockcount() - ehf_height) - 1) - ehf_tx_sent = self.send_tx(ehf_tx_second) - self.log.info("Mine block and ensure not mined yet...") - node.generate(1) - self.ensure_tx_is_not_mined(ehf_tx_sent) - node.generate(1) + self.log.info("Mine one block and ensure EHF tx for the new deployment is mined") + ehf_tx_sent = self.send_tx(ehf_tx_new_start) + tip_blockhash = node.generate(1)[0] self.sync_all() - self.log.info("Mine one more block and ensure it is mined") - block = node.getblock(node.getbestblockhash()) + block = node.getblock(tip_blockhash) assert ehf_tx_sent in block['tx'] self.check_fork('defined') @@ -266,6 +265,5 @@ def run_test(self): self.check_fork('active') - if __name__ == '__main__': MnehfTest().main() From f7705cdf72c50dbf122a8990a41762ddb2f11176 Mon Sep 17 00:00:00 2001 From: Konstantin Akimov Date: Sun, 24 Sep 2023 22:57:04 +0700 Subject: [PATCH 21/21] fix: scan quorums instead just using verified sigs --- src/evo/mnhftx.cpp | 17 +++++++---------- src/evo/mnhftx.h | 6 +++--- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 8e8ecd7683b19..02f6936ca6251 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -53,24 +53,21 @@ CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pin return signals; } -bool MNHFTx::Verify(const CBlockIndex* const pQuorumIndex, const uint256& msgHash, TxValidationState& state) const +bool MNHFTx::Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const { if (versionBit >= VERSIONBITS_NUM_BITS) { return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds"); } - Consensus::LLMQType llmqType = Params().GetConsensus().llmqTypeMnhf; - const auto& llmq_params_opt = llmq::GetLLMQParams(llmqType); - if (!llmq_params_opt.has_value()) { - return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-type"); - } - int signOffset{llmq_params_opt->dkgInterval}; + 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})); - - if (!llmq::CSigningManager::VerifyRecoveredSig(llmqType, *llmq::quorumManager, pQuorumIndex->nHeight + signOffset, requestId, msgHash, sig)) { + 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"); } + return true; } @@ -107,7 +104,7 @@ bool CheckMNHFTx(const CTransaction& tx, const CBlockIndex* pindexPrev, TxValida uint256 msgHash = tx_copy.GetHash(); - if (!mnhfTx.signal.Verify(pindexQuorum, msgHash, state)) { + if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) { // set up inside Verify return false; } diff --git a/src/evo/mnhftx.h b/src/evo/mnhftx.h index 67320bf9ecf11..a6a67d9c72007 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -28,11 +28,11 @@ class MNHFTx { public: uint8_t versionBit{0}; - uint256 quorumHash; - CBLSSignature sig; + uint256 quorumHash{0}; + CBLSSignature sig{}; MNHFTx() = default; - bool Verify(const CBlockIndex* const pQuorumIndex, const uint256& msgHash, TxValidationState& state) const; + bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const; SERIALIZE_METHODS(MNHFTx, obj) {