LCOV - code coverage report
Current view: top level - src/evo - mnhftx.cpp (source / functions) Hit Total Coverage
Test: test_dash_coverage.info Lines: 100 242 41.3 %
Date: 2026-06-25 07:23:51 Functions: 16 22 72.7 %

          Line data    Source code
       1             : // Copyright (c) 2021-2025 The Dash Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include <consensus/validation.h>
       6             : #include <deploymentstatus.h>
       7             : #include <evo/evodb.h>
       8             : #include <evo/mnhftx.h>
       9             : #include <evo/specialtx.h>
      10             : #include <llmq/commitment.h>
      11             : #include <llmq/quorumsman.h>
      12             : #include <llmq/signhash.h>
      13             : #include <node/blockstorage.h>
      14             : #include <util/std23.h>
      15             : 
      16             : #include <chain.h>
      17             : #include <chainparams.h>
      18             : #include <validation.h>
      19             : #include <versionbits.h>
      20             : 
      21             : #include <algorithm>
      22             : #include <stack>
      23             : #include <string>
      24             : #include <vector>
      25             : 
      26             : using node::ReadBlockFromDisk;
      27             : 
      28             : static const std::string MNEHF_REQUESTID_PREFIX = "mnhf";
      29             : static const std::string DB_SIGNALS = "mnhf_s";
      30             : static const std::string DB_SIGNALS_v2 = "mnhf_s2";
      31             : 
      32           0 : uint256 MNHFTxPayload::GetRequestId() const
      33             : {
      34           0 :     return ::SerializeHash(std::make_pair(MNEHF_REQUESTID_PREFIX, int64_t{signal.versionBit}));
      35           0 : }
      36             : 
      37           0 : CMutableTransaction MNHFTxPayload::PrepareTx() const
      38             : {
      39           0 :     CMutableTransaction tx;
      40           0 :     tx.nVersion = 3;
      41           0 :     tx.nType = SPECIALTX_TYPE;
      42           0 :     SetTxPayload(tx, *this);
      43             : 
      44           0 :     return tx;
      45           0 : }
      46             : 
      47         540 : CMNHFManager::CMNHFManager(CEvoDB& evoDb, const ChainstateManager& chainman, const llmq::CQuorumManager& qman) :
      48         180 :     m_evoDb(evoDb),
      49         180 :     m_chainman{chainman},
      50         180 :     m_qman{qman}
      51         360 : {
      52             :     assert(globalInstance == nullptr);
      53             :     globalInstance = this;
      54         180 : }
      55             : 
      56         540 : CMNHFManager::~CMNHFManager()
      57         540 : {
      58         180 :     assert(globalInstance != nullptr);
      59         180 :     globalInstance = nullptr;
      60         540 : }
      61             : 
      62      147419 : CMNHFManager::Signals CMNHFManager::GetSignalsStage(const CBlockIndex* const pindexPrev)
      63             : {
      64      147419 :     if (!DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) return {};
      65             : 
      66       69697 :     Signals signals_tmp = GetForBlock(pindexPrev);
      67             : 
      68       69697 :     if (pindexPrev == nullptr) return {};
      69       69697 :     const int height = pindexPrev->nHeight + 1;
      70             : 
      71       69697 :     Signals signals_ret;
      72             : 
      73       69697 :     for (auto signal : signals_tmp) {
      74           0 :         bool expired{false};
      75           0 :         const auto signal_pindex = pindexPrev->GetAncestor(signal.second);
      76           0 :         assert(signal_pindex != nullptr);
      77           0 :         const int64_t signal_time = signal_pindex->GetMedianTimePast();
      78           0 :         for (int index = 0; index < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; ++index) {
      79           0 :             const auto& deployment = Params().GetConsensus().vDeployments[index];
      80           0 :             if (deployment.bit != signal.first) continue;
      81           0 :             if (signal_time < deployment.nStartTime) {
      82             :                 // new deployment is using the same bit as the old one
      83           0 :                 LogPrintf("CMNHFManager::GetSignalsStage: mnhf signal bit=%d height:%d is expired at height=%d\n",
      84             :                           signal.first, signal.second, height);
      85           0 :                 expired = true;
      86           0 :             }
      87           0 :         }
      88           0 :         if (!expired) {
      89           0 :             signals_ret.insert(signal);
      90           0 :         }
      91             :     }
      92       69697 :     return signals_ret;
      93      217116 : }
      94             : 
      95           0 : bool MNHFTx::Verify(const llmq::CQuorumManager& qman, const uint256& quorumHash, const uint256& requestId, const uint256& msgHash, TxValidationState& state) const
      96             : {
      97           0 :     if (versionBit >= VERSIONBITS_NUM_BITS) {
      98           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-nbit-out-of-bounds");
      99             :     }
     100             : 
     101           0 :     const Consensus::LLMQType& llmqType = Params().GetConsensus().llmqTypeMnhf;
     102           0 :     const auto quorum = qman.GetQuorum(llmqType, quorumHash);
     103             : 
     104           0 :     if (!quorum) {
     105           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-missing-quorum");
     106             :     }
     107             : 
     108           0 :     const llmq::SignHash signHash{llmqType, quorum->qc->quorumHash, requestId, msgHash};
     109           0 :     if (!sig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
     110           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-invalid");
     111             :     }
     112             : 
     113           0 :     return true;
     114           0 : }
     115             : 
     116           2 : bool CheckMNHFTx(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CTransaction& tx, const CBlockIndex* pindexPrev, TxValidationState& state)
     117             : {
     118           2 :     if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) {
     119           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-type");
     120             :     }
     121             : 
     122           2 :     const auto opt_mnhfTx = GetTxPayload<MNHFTxPayload>(tx);
     123           2 :     if (!opt_mnhfTx) {
     124           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-payload");
     125             :     }
     126           2 :     auto& mnhfTx = *opt_mnhfTx;
     127           2 :     if (mnhfTx.nVersion == 0 || mnhfTx.nVersion > MNHFTxPayload::CURRENT_VERSION) {
     128           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-version");
     129             :     }
     130             : 
     131           2 :     if (!Params().IsValidMNActivation(mnhfTx.signal.versionBit, pindexPrev->GetMedianTimePast())) {
     132           1 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-non-ehf");
     133             :     }
     134             : 
     135           2 :     const CBlockIndex* pindexQuorum = WITH_LOCK(::cs_main, return chainman.m_blockman.LookupBlockIndex(mnhfTx.signal.quorumHash));
     136           1 :     if (!pindexQuorum) {
     137           1 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-hash");
     138             :     }
     139             : 
     140           0 :     if (pindexQuorum != pindexPrev->GetAncestor(pindexQuorum->nHeight)) {
     141             :         // not part of active chain
     142           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-mnhf-quorum-hash");
     143             :     }
     144             : 
     145             :     // Copy transaction except `quorumSig` field to calculate hash
     146           0 :     CMutableTransaction tx_copy(tx);
     147           0 :     auto payload_copy = mnhfTx;
     148           0 :     payload_copy.signal.sig = CBLSSignature();
     149           0 :     SetTxPayload(tx_copy, payload_copy);
     150           0 :     uint256 msgHash = tx_copy.GetHash();
     151             : 
     152             : 
     153           0 :     if (!mnhfTx.signal.Verify(qman, mnhfTx.signal.quorumHash, mnhfTx.GetRequestId(), msgHash, state)) {
     154             :         // set up inside Verify
     155           0 :         return false;
     156             :     }
     157             : 
     158           0 :     return true;
     159           2 : }
     160             : 
     161         187 : std::optional<uint8_t> extractEHFSignal(const CTransaction& tx)
     162             : {
     163         187 :     if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) {
     164             :         // only interested in special TXs 'TRANSACTION_MNHF_SIGNAL'
     165         187 :         return std::nullopt;
     166             :     }
     167             : 
     168           0 :     const auto opt_mnhfTx = GetTxPayload<MNHFTxPayload>(tx);
     169           0 :     if (!opt_mnhfTx) {
     170           0 :         return std::nullopt;
     171             :     }
     172           0 :     return opt_mnhfTx->signal.versionBit;
     173         187 : }
     174             : 
     175       49403 : static bool extractSignals(const ChainstateManager& chainman, const llmq::CQuorumManager& qman, const CBlock& block, const CBlockIndex* const pindex, std::vector<uint8_t>& new_signals, BlockValidationState& state)
     176             : {
     177             :     // we skip the coinbase
     178       79563 :     for (size_t i = 1; i < block.vtx.size(); ++i) {
     179       30160 :         const CTransaction& tx = *block.vtx[i];
     180             : 
     181       30160 :         if (!tx.IsSpecialTxVersion() || tx.nType != TRANSACTION_MNHF_SIGNAL) {
     182             :             // only interested in special TXs 'TRANSACTION_MNHF_SIGNAL'
     183       30160 :             continue;
     184             :         }
     185             : 
     186           0 :         TxValidationState tx_state;
     187           0 :         if (!CheckMNHFTx(chainman, qman, tx, pindex, tx_state)) {
     188           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(), tx_state.GetDebugMessage());
     189             :         }
     190             : 
     191           0 :         const auto opt_mnhfTx = GetTxPayload<MNHFTxPayload>(tx);
     192           0 :         if (!opt_mnhfTx) {
     193           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-tx-payload");
     194             :         }
     195           0 :         const uint8_t bit = opt_mnhfTx->signal.versionBit;
     196           0 :         if (std23::ranges::contains(new_signals, bit)) {
     197           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicates-in-block");
     198             :         }
     199           0 :         new_signals.push_back(bit);
     200           0 :     }
     201             : 
     202       49403 :     return true;
     203       49403 : }
     204             : 
     205       49014 : std::optional<CMNHFManager::Signals> CMNHFManager::ProcessBlock(const CBlock& block, const CBlockIndex* const pindex, bool fJustCheck, BlockValidationState& state)
     206             : {
     207             :     try {
     208       49014 :         std::vector<uint8_t> new_signals;
     209       49014 :         if (!extractSignals(m_chainman, m_qman, block, pindex, new_signals, state)) {
     210             :             // state is set inside extractSignals
     211           0 :             return std::nullopt;
     212             :         }
     213       49014 :         Signals signals = GetSignalsStage(pindex->pprev);
     214       49014 :         if (new_signals.empty()) {
     215       49014 :             if (!fJustCheck) {
     216       24499 :                 AddToCache(signals, pindex);
     217       24499 :             }
     218       49014 :             LogPrint(BCLog::EHF, "CMNHFManager::ProcessBlock: no new signals; number of known signals: %d\n", signals.size());
     219       49014 :             return signals;
     220             :         }
     221             : 
     222           0 :         const int mined_height = pindex->nHeight;
     223             : 
     224             :         // Extra validation of signals to be sure that it can succeed
     225           0 :         for (const auto& versionBit : new_signals) {
     226           0 :             LogPrintf("CMNHFManager::ProcessBlock: add mnhf bit=%d block:%s number of known signals:%lld\n", versionBit, pindex->GetBlockHash().ToString(), signals.size());
     227           0 :             if (signals.find(versionBit) != signals.end()) {
     228           0 :                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-duplicate");
     229           0 :                 return std::nullopt;
     230             :             }
     231             : 
     232           0 :             if (!Params().IsValidMNActivation(versionBit, pindex->GetMedianTimePast())) {
     233           0 :                 state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-mnhf-non-mn-fork");
     234           0 :                 return std::nullopt;
     235             :             }
     236             :         }
     237           0 :         if (fJustCheck) {
     238             :             // We are done, no need actually update any params
     239           0 :             return signals;
     240             :         }
     241           0 :         for (const auto& versionBit : new_signals) {
     242           0 :             signals.insert({versionBit, mined_height});
     243             :         }
     244             : 
     245           0 :         AddToCache(signals, pindex);
     246           0 :         return signals;
     247       49014 :     } catch (const std::exception& e) {
     248           0 :         LogPrintf("CMNHFManager::ProcessBlock -- failed: %s\n", e.what());
     249           0 :         state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-proc-mnhf-inblock");
     250           0 :         return std::nullopt;
     251           0 :     }
     252       49014 : }
     253             : 
     254         389 : bool CMNHFManager::UndoBlock(const CBlock& block, const CBlockIndex* const pindex)
     255             : {
     256         389 :     std::vector<uint8_t> excluded_signals;
     257         389 :     BlockValidationState state;
     258         389 :     if (!extractSignals(m_chainman, m_qman, block, pindex, excluded_signals, state)) {
     259           0 :         LogPrintf("CMNHFManager::%s: failed to extract signals\n", __func__);
     260           0 :         return false;
     261             :     }
     262         389 :     if (excluded_signals.empty()) {
     263         389 :         return true;
     264             :     }
     265             : 
     266           0 :     const Signals signals = GetForBlock(pindex);
     267           0 :     for (const auto& versionBit : excluded_signals) {
     268           0 :         LogPrintf("%s: exclude mnhf bit=%d block:%s number of known signals:%lld\n", __func__, versionBit, pindex->GetBlockHash().ToString(), signals.size());
     269           0 :         assert(signals.find(versionBit) != signals.end());
     270           0 :         assert(Params().IsValidMNActivation(versionBit, pindex->GetMedianTimePast()));
     271             :     }
     272             : 
     273           0 :     return true;
     274         389 : }
     275             : 
     276       69697 : CMNHFManager::Signals CMNHFManager::GetForBlock(const CBlockIndex* pindex)
     277             : {
     278       69697 :     if (pindex == nullptr) return {};
     279             : 
     280       69697 :     std::stack<const CBlockIndex *> to_calculate;
     281             : 
     282       69697 :     std::optional<CMNHFManager::Signals> signalsTmp;
     283       69697 :     while (!(signalsTmp = GetFromCache(pindex)).has_value()) {
     284           0 :         to_calculate.push(pindex);
     285           0 :         pindex = pindex->pprev;
     286             :     }
     287             : 
     288       69697 :     const Consensus::Params& consensusParams{Params().GetConsensus()};
     289       69697 :     while (!to_calculate.empty()) {
     290           0 :         const CBlockIndex* pindex_top{to_calculate.top()};
     291           0 :         if (pindex_top->nHeight % 1000 == 0) {
     292           0 :             LogPrintf("re-index EHF signals at block %d\n", pindex_top->nHeight);
     293           0 :         }
     294           0 :         CBlock block;
     295           0 :         if (!ReadBlockFromDisk(block, pindex_top, consensusParams)) {
     296           0 :             throw std::runtime_error("failed-getehfforblock-read");
     297             :         }
     298           0 :         BlockValidationState state;
     299           0 :         signalsTmp = ProcessBlock(block, pindex_top, false, state);
     300           0 :         if (!signalsTmp.has_value()) {
     301           0 :             LogPrintf("%s: process block failed due to %s\n", __func__, state.ToString());
     302           0 :             throw std::runtime_error("failed-getehfforblock-construct");
     303             :         }
     304             : 
     305           0 :         to_calculate.pop();
     306           0 :     }
     307       69697 :     return *signalsTmp;
     308       69697 : }
     309             : 
     310       69697 : std::optional<CMNHFManager::Signals> CMNHFManager::GetFromCache(const CBlockIndex* const pindex)
     311             : {
     312       69697 :     Signals signals{};
     313       69697 :     if (pindex == nullptr) return signals;
     314             : 
     315             :     // TODO: remove this check of phashBlock to nullptr
     316             :     // This check is needed only because unit test 'versionbits_tests.cpp'
     317             :     // lets `phashBlock` to be nullptr
     318       69697 :     if (pindex->phashBlock == nullptr) return signals;
     319             : 
     320             : 
     321       69697 :     const uint256& blockHash = pindex->GetBlockHash();
     322             :     {
     323       69697 :         LOCK(cs_cache);
     324       69697 :         if (mnhfCache.get(blockHash, signals)) {
     325       69697 :             return signals;
     326             :         }
     327       69697 :     }
     328             :     {
     329           0 :         LOCK(cs_cache);
     330           0 :         if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) {
     331           0 :             mnhfCache.insert(blockHash, signals);
     332           0 :             return signals;
     333             :         }
     334           0 :     }
     335           0 :     if (m_evoDb.Read(std::make_pair(DB_SIGNALS_v2, blockHash), signals)) {
     336           0 :         LOCK(cs_cache);
     337           0 :         mnhfCache.insert(blockHash, signals);
     338           0 :         return signals;
     339           0 :     }
     340           0 :     if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_MN_RR)) {
     341             :         // before mn_rr activation we are safe
     342           0 :         if (m_evoDb.Read(std::make_pair(DB_SIGNALS, blockHash), signals)) {
     343           0 :             LOCK(cs_cache);
     344           0 :             mnhfCache.insert(blockHash, signals);
     345           0 :             return signals;
     346           0 :         }
     347           0 :     }
     348           0 :     return std::nullopt;
     349       69697 : }
     350             : 
     351       24499 : void CMNHFManager::AddToCache(const Signals& signals, const CBlockIndex* const pindex)
     352             : {
     353       24499 :     assert(pindex != nullptr);
     354       24499 :     const uint256& blockHash = pindex->GetBlockHash();
     355             :     {
     356       24499 :         LOCK(cs_cache);
     357       24499 :         mnhfCache.insert(blockHash, signals);
     358       24499 :     }
     359       24499 :     if (!DeploymentActiveAt(*pindex, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_V20)) return;
     360             : 
     361        7573 :     m_evoDb.Write(std::make_pair(DB_SIGNALS_v2, blockHash), signals);
     362       24499 : }
     363             : 
     364           0 : void CMNHFManager::AddSignal(const CBlockIndex* const pindex, int bit)
     365             : {
     366           0 :     auto signals = GetForBlock(pindex->pprev);
     367           0 :     signals.emplace(bit, pindex->nHeight);
     368           0 :     AddToCache(signals, pindex);
     369           0 : }
     370             : 
     371         178 : bool CMNHFManager::ForceSignalDBUpdate()
     372             : {
     373             :     // force ehf signals db update
     374         178 :     auto dbTx = m_evoDb.BeginTransaction();
     375             : 
     376         178 :     const bool last_legacy = bls::bls_legacy_scheme.load();
     377         178 :     bls::bls_legacy_scheme.store(false);
     378         178 :     GetSignalsStage(m_chainman.ActiveTip());
     379         178 :     bls::bls_legacy_scheme.store(last_legacy);
     380             : 
     381         178 :     dbTx->Commit();
     382             :     // flush it to disk
     383         178 :     if (!m_evoDb.CommitRootTransaction()) {
     384           0 :         LogPrintf("CMNHFManager::%s -- failed to commit to evoDB\n", __func__);
     385           0 :         return false;
     386             :     }
     387         178 :     return true;
     388         178 : }
     389             : 
     390           0 : std::string MNHFTx::ToString() const
     391             : {
     392           0 :     return strprintf("MNHFTx(versionBit=%d, quorumHash=%s, sig=%s)",
     393           0 :                      versionBit, quorumHash.ToString(), sig.ToString());
     394           0 : }
     395           0 : std::string MNHFTxPayload::ToString() const
     396             : {
     397           0 :     return strprintf("MNHFTxPayload(nVersion=%d, signal=%s)",
     398           0 :                      nVersion, signal.ToString());
     399           0 : }

Generated by: LCOV version 1.16