diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 151ba48622060..8716ba49d3bae 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -105,6 +106,32 @@ static CBlock FindDevNetGenesisBlock(const CBlock &prevBlock, const CAmount& rew assert(false); } +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]; + 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) { + 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) { + deployment.nMNActivationHeight = height; + } + return true; + } + } + LogPrintf("%s: WARNING: unknown MnEHF fork bit=%d\n", __func__, nBit); + return true; +} + void CChainParams::AddLLMQ(Consensus::LLMQType llmqType) { assert(!GetLLMQ(llmqType).has_value()); @@ -909,7 +936,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 +952,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 +1028,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 +1049,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..f5f116a3267c8 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -134,6 +134,16 @@ 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; } 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/evo/mnhftx.cpp b/src/evo/mnhftx.cpp index 665e600f734cb..02f6936ca6251 100644 --- a/src/evo/mnhftx.cpp +++ b/src/evo/mnhftx.cpp @@ -15,28 +15,59 @@ #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 +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(); ) { + 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); + } + } + return signals; +} + +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; } @@ -72,14 +103,203 @@ 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)) { + + if (!mnhfTx.signal.Verify(mnhfTx.signal.quorumHash, msgHash, state)) { // set up inside Verify return false; } + 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& new_signals, 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"); + } + 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; +} + +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; + } + Signals signals = GetSignalsStage(pindex->pprev); + if (new_signals.empty()) { + if (!fJustCheck) { + AddToCache(signals, pindex); + } + return true; + } + + int mined_height = pindex->nHeight; + + // Extra validation of signals to be sure that it can succeed + for (const auto& versionBit : new_signals) { + 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"); + } + + 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("CMNHFManager::ProcessBlock -- failed: %s\n", 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) { + 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) { + const uint8_t versionBit = signal.first; + + 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); + assert(update_ret); + } + + Signals signals{GetFromCache(pindex)}; + for (const auto& signal: signals) { + const uint8_t versionBit = signal.first; + const int value = signal.second; + + 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); + 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, {}); + LogPrintf("CMNHFManager::GetFromCache: mnhf feature is disabled: return empty for block %s\n", pindex->GetBlockHash().ToString()); + 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()); + } + 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("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); +} + 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..a6a67d9c72007 100644 --- a/src/evo/mnhftx.h +++ b/src/evo/mnhftx.h @@ -11,7 +11,15 @@ #include #include +#include +#include +#include +#include + +class BlockValidationState; +class CBlock; class CBlockIndex; +class CEvoDB; class TxValidationState; extern RecursiveMutex cs_main; @@ -20,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* pQuorumIndex, const uint256& msgHash, TxValidationState& state) const; + bool Verify(const uint256& quorumHash, const uint256& msgHash, TxValidationState& state) const; SERIALIZE_METHODS(MNHFTx, obj) { @@ -72,6 +80,59 @@ 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); + + /** + * 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/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/miner.cpp b/src/miner.cpp index ecdbd51af9418..ec74030474206 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.GetMNHFSignalsStage(); + 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,18 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda continue; } } + 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; + } + 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/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/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/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/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/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..4ced61248165b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -53,6 +53,7 @@ #include #include +#include #include #include #include @@ -796,7 +797,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, @@ -1256,6 +1261,7 @@ void CoinsViews::InitCache() CChainState::CChainState(CTxMemPool* mempool, BlockManager& blockman, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -1266,6 +1272,7 @@ CChainState::CChainState(CTxMemPool* mempool, m_clhandler(clhandler), m_isman(isman), m_quorum_block_processor(quorum_block_processor), + m_mnhfManager(mnhfManager), m_evoDb(evoDb), m_blockman(blockman), m_from_snapshot_blockhash(from_snapshot_blockhash) {} @@ -1702,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; } @@ -1927,6 +1934,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; } @@ -2181,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()); } @@ -2485,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, @@ -4804,7 +4819,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()); } @@ -5646,6 +5661,7 @@ std::vector ChainstateManager::GetAll() } CChainState& ChainstateManager::InitializeChainstate(CTxMemPool* mempool, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, @@ -5660,7 +5676,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)) { @@ -5730,9 +5746,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..02089a4699a36 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, @@ -775,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); @@ -940,6 +945,7 @@ class ChainstateManager //! @param[in] snapshot_blockhash If given, signify that this chainstate //! is based on a snapshot. CChainState& InitializeChainstate(CTxMemPool* mempool, + CMNHFManager& mnhfManager, CEvoDB& evoDb, const std::unique_ptr& clhandler, const std::unique_ptr& isman, diff --git a/src/versionbits.cpp b/src/versionbits.cpp index 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_mnehf.py b/test/functional/feature_mnehf.py new file mode 100755 index 0000000000000..9498013e86665 --- /dev/null +++ b/test/functional/feature_mnehf.py @@ -0,0 +1,269 @@ +#!/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", "-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): + self.skip_if_no_wallet() + + 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]}") + 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(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) + request_id_buf = ser_string(b"mnhf") + struct.pack(" 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