LCOV - code coverage report
Current view: top level - src/evo - specialtxman.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 578 720 80.3 %
Date: 2026-06-25 07:23:43 Functions: 29 29 100.0 %

          Line data    Source code
       1             : // Copyright (c) 2018-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 <evo/specialtxman.h>
       6             : 
       7             : #include <chainlock/chainlock.h>
       8             : #include <chainlock/clsig.h>
       9             : #include <chainlock/handler.h>
      10             : #include <evo/assetlocktx.h>
      11             : #include <evo/cbtx.h>
      12             : #include <evo/creditpool.h>
      13             : #include <evo/deterministicmns.h>
      14             : #include <evo/mnhftx.h>
      15             : #include <evo/netinfo.h>
      16             : #include <evo/simplifiedmns.h>
      17             : #include <llmq/blockprocessor.h>
      18             : #include <llmq/commitment.h>
      19             : #include <llmq/quorumsman.h>
      20             : #include <llmq/utils.h>
      21             : #include <messagesigner.h>
      22             : #include <util/helpers.h>
      23             : 
      24             : #include <chainparams.h>
      25             : #include <consensus/amount.h>
      26             : #include <consensus/validation.h>
      27             : #include <deploymentstatus.h>
      28             : #include <hash.h>
      29             : #include <primitives/block.h>
      30             : #include <util/system.h>
      31             : #include <validation.h>
      32             : 
      33      167509 : static bool CheckCbTxBestChainlock(const CCbTx& cbTx, const CBlockIndex* pindex, const Consensus::Params& consensus_params,
      34             :                                    const CChain& chain, const llmq::CQuorumManager& qman,
      35             :                                    const chainlock::Chainlocks& chainlocks, BlockValidationState& state)
      36             : {
      37      167509 :     if (cbTx.nVersion < CCbTx::Version::CLSIG_AND_BALANCE) {
      38       56027 :         return true;
      39             :     }
      40             : 
      41      111482 :     static Mutex cached_mutex;
      42             :     static const CBlockIndex* cached_pindex GUARDED_BY(cached_mutex){nullptr};
      43      111482 :     static std::optional<std::pair<CBLSSignature, uint32_t>> cached_chainlock GUARDED_BY(cached_mutex){std::nullopt};
      44             : 
      45      111482 :     auto best_clsig = chainlocks.GetBestChainLock();
      46      122124 :     if (best_clsig.getHeight() == pindex->nHeight - 1 && cbTx.bestCLHeightDiff == 0 &&
      47       10642 :         cbTx.bestCLSignature == best_clsig.getSig()) {
      48             :         // matches our best clsig which still hold values for the previous block
      49       10562 :         LOCK(cached_mutex);
      50       10562 :         cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
      51       10562 :         cached_pindex = pindex;
      52       10562 :         return true;
      53       10562 :     }
      54             : 
      55      100920 :     std::optional<std::pair<CBLSSignature, uint32_t>> prevBlockCoinbaseChainlock{std::nullopt};
      56      125129 :     if (LOCK(cached_mutex); cached_pindex == pindex->pprev) {
      57       24209 :         prevBlockCoinbaseChainlock = cached_chainlock;
      58       24209 :     }
      59      100920 :     if (!prevBlockCoinbaseChainlock.has_value()) {
      60       76711 :         prevBlockCoinbaseChainlock = GetNonNullCoinbaseChainlock(pindex->pprev);
      61       76711 :     }
      62             :     // If std::optional prevBlockCoinbaseChainlock is empty, then up to the previous block, coinbase Chainlock is null.
      63      100920 :     if (prevBlockCoinbaseChainlock.has_value()) {
      64             :         // Previous block Coinbase has a non-null Chainlock: current block's Chainlock must be non-null and at least as new as the previous one
      65       27401 :         if (!cbTx.bestCLSignature.IsValid()) {
      66             :             // IsNull() doesn't exist for CBLSSignature: we assume that a non valid BLS sig is null
      67           0 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-null-clsig");
      68             :         }
      69       27401 :         if (cbTx.bestCLHeightDiff > prevBlockCoinbaseChainlock.value().second + 1) {
      70           6 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-older-clsig");
      71             :         }
      72       27395 :     }
      73             : 
      74             :     // IsNull() doesn't exist for CBLSSignature: we assume that a valid BLS sig is non-null
      75      100914 :     if (cbTx.bestCLSignature.IsValid()) {
      76       27446 :         int curBlockCoinbaseCLHeight = pindex->nHeight - static_cast<int>(cbTx.bestCLHeightDiff) - 1;
      77       27446 :         if (best_clsig.getHeight() == curBlockCoinbaseCLHeight && best_clsig.getSig() == cbTx.bestCLSignature) {
      78             :             // matches our best (but outdated) clsig, no need to verify it again
      79       22906 :             LOCK(cached_mutex);
      80       22906 :             cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
      81       22906 :             cached_pindex = pindex;
      82       22906 :             return true;
      83       22906 :         }
      84        4540 :         uint256 curBlockCoinbaseCLBlockHash = pindex->GetAncestor(curBlockCoinbaseCLHeight)->GetBlockHash();
      85        4540 :         chainlock::ChainLockSig clsig{curBlockCoinbaseCLHeight, curBlockCoinbaseCLBlockHash, cbTx.bestCLSignature};
      86        4540 :         llmq::VerifyRecSigStatus ret = chainlock::VerifyChainLock(consensus_params, chain, qman, clsig);
      87        4540 :         if (ret != llmq::VerifyRecSigStatus::Valid) {
      88           3 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid-clsig");
      89             :         }
      90        4537 :         LOCK(cached_mutex);
      91        4537 :         cached_chainlock = std::make_pair(cbTx.bestCLSignature, cbTx.bestCLHeightDiff);
      92        4537 :         cached_pindex = pindex;
      93       78008 :     } else if (cbTx.bestCLHeightDiff != 0) {
      94             :         // Null bestCLSignature is allowed only with bestCLHeightDiff = 0
      95           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-cldiff");
      96             :     }
      97             : 
      98       78005 :     return true;
      99      167509 : }
     100             : 
     101      584795 : static bool CheckSpecialTxInner(CDeterministicMNManager& dmnman, llmq::CQuorumSnapshotManager& qsnapman,
     102             :                                 const ChainstateManager& chainman, const llmq::CQuorumManager& qman,
     103             :                                 const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view,
     104             :                                 const std::optional<CRangesSet>& indexes, bool check_sigs, TxValidationState& state)
     105             :     EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
     106             : {
     107      584795 :     AssertLockHeld(::cs_main);
     108             : 
     109      584795 :     if (!tx.HasExtraPayloadField())
     110      339309 :         return true;
     111             : 
     112      245486 :     if (!DeploymentActiveAfter(pindexPrev, chainman.GetConsensus(), Consensus::DEPLOYMENT_DIP0003)) {
     113           1 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-dip3-inactive");
     114             :     }
     115             : 
     116             :     try {
     117      245485 :         switch (tx.nType) {
     118             :         case TRANSACTION_PROVIDER_REGISTER:
     119        3831 :             return CheckProRegTx(tx, pindexPrev, dmnman, view, chainman, state, check_sigs);
     120             :         case TRANSACTION_PROVIDER_UPDATE_SERVICE:
     121        3786 :             return CheckProUpServTx(tx, pindexPrev, dmnman, chainman, state, check_sigs);
     122             :         case TRANSACTION_PROVIDER_UPDATE_REGISTRAR:
     123         139 :             return CheckProUpRegTx(tx, pindexPrev, dmnman, view, chainman, state, check_sigs);
     124             :         case TRANSACTION_PROVIDER_UPDATE_REVOKE:
     125          97 :             return CheckProUpRevTx(tx, pindexPrev, dmnman, chainman, state, check_sigs);
     126             :         case TRANSACTION_COINBASE: {
     127           0 :             if (!tx.IsCoinBase()) {
     128           0 :                 return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-invalid");
     129             :             }
     130           0 :             if (const auto opt_cbTx = GetTxPayload<CCbTx>(tx)) {
     131           0 :                 return CheckCbTx(*opt_cbTx, pindexPrev, state);
     132             :             } else {
     133           0 :                 return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-cbtx-payload");
     134             :             }
     135             :         }
     136             :         case TRANSACTION_QUORUM_COMMITMENT:
     137      233399 :             return llmq::CheckLLMQCommitment({dmnman, qsnapman, chainman, pindexPrev}, tx, state);
     138             :         case TRANSACTION_MNHF_SIGNAL:
     139         733 :             return CheckMNHFTx(chainman, qman, tx, pindexPrev, state);
     140             :         case TRANSACTION_ASSET_LOCK:
     141        2992 :             return CheckAssetLockTx(tx, state);
     142             :         case TRANSACTION_ASSET_UNLOCK:
     143         508 :             return CheckAssetUnlockTx(chainman.m_blockman, qman, tx, pindexPrev, indexes, state);
     144             :         }
     145           0 :     } catch (const std::exception& e) {
     146           0 :         LogPrintf("%s -- failed: %s\n", __func__, e.what());
     147           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "failed-check-special-tx");
     148           0 :     }
     149             : 
     150           0 :     return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-tx-type-check");
     151      584795 : }
     152             : 
     153       46947 : bool CSpecialTxProcessor::CheckSpecialTx(const CTransaction& tx, const CBlockIndex* pindexPrev, const CCoinsViewCache& view, bool check_sigs, TxValidationState& state)
     154             : {
     155       46947 :     AssertLockHeld(::cs_main);
     156       93894 :     return CheckSpecialTxInner(m_dmnman, m_qsnapman, m_chainman, m_qman, tx, pindexPrev, view, std::nullopt, check_sigs,
     157       46947 :                                state);
     158           0 : }
     159             : 
     160        5851 : static void HandleQuorumCommitment(const llmq::CFinalCommitment& qc, const std::vector<CDeterministicMNCPtr>& members,
     161             :                                    bool debugLogs, CDeterministicMNList& mnList)
     162             : {
     163       26879 :     for (size_t i = 0; i < members.size(); i++) {
     164       21028 :         if (!mnList.HasMN(members[i]->proTxHash)) {
     165           0 :             continue;
     166             :         }
     167       21028 :         if (!qc.validMembers[i]) {
     168             :             // punish MN for failed DKG participation
     169             :             // The idea is to immediately ban a MN when it fails 2 DKG sessions with only a few blocks in-between
     170             :             // If there were enough blocks between failures, the MN has a chance to recover as he reduces his penalty by 1 for every block
     171             :             // If it however fails 3 times in the timespan of a single payment cycle, it should definitely get banned
     172         354 :             mnList.PoSePunish(members[i]->proTxHash, mnList.CalcPenalty(66), debugLogs);
     173         354 :         }
     174       21028 :     }
     175        5851 : }
     176             : 
     177      214461 : bool CSpecialTxProcessor::BuildNewListFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindexPrev,
     178             :                                                 const CCoinsViewCache& view, bool debugLogs,
     179             :                                                 BlockValidationState& state, CDeterministicMNList& mnListRet)
     180             : {
     181      214461 :     AssertLockHeld(cs_main);
     182      214461 :     CDeterministicMNList oldList = m_dmnman.GetListForBlock(pindexPrev);
     183      214461 :     return RebuildListFromBlock(block, pindexPrev, oldList, view, debugLogs, state, mnListRet);
     184      214461 : }
     185             : 
     186      214461 : bool CSpecialTxProcessor::RebuildListFromBlock(const CBlock& block, gsl::not_null<const CBlockIndex*> pindexPrev,
     187             :                                                 const CDeterministicMNList& prevList, const CCoinsViewCache& view,
     188             :                                                 bool debugLogs, BlockValidationState& state,
     189             :                                                 CDeterministicMNList& mnListRet)
     190             : {
     191             :     // Verify that prevList either represents an empty/initial state (default-constructed),
     192             :     // or it matches the previous block's hash.
     193      214461 :     assert(prevList == CDeterministicMNList() || prevList.GetBlockHash() == pindexPrev->GetBlockHash());
     194             : 
     195      214461 :     int nHeight = pindexPrev->nHeight + 1;
     196             : 
     197      214461 :     CDeterministicMNList newList = prevList;
     198      214461 :     newList.SetBlockHash(uint256()); // we can't know the final block hash, so better not return a (invalid) block hash
     199      214461 :     newList.SetHeight(nHeight);
     200             : 
     201      214461 :     auto payee = prevList.GetMNPayee(pindexPrev);
     202             : 
     203             :     // we iterate the prevList here and update the newList
     204             :     // this is only valid as long these have not diverged at this point, which is the case as long as we don't add
     205             :     // code above this loop that modifies newList
     206      998504 :     prevList.ForEachMN(/*onlyValid=*/false, [&pindexPrev, &newList, this](const auto& dmn) {
     207      784043 :         if (!dmn.pdmnState->confirmedHash.IsNull()) {
     208             :             // already confirmed
     209      777337 :             return;
     210             :         }
     211             :         // this works on the previous block, so confirmation will happen one block after nMasternodeMinimumConfirmations
     212             :         // has been reached, but the block hash will then point to the block at nMasternodeMinimumConfirmations
     213        6706 :         int nConfirmations = pindexPrev->nHeight - dmn.pdmnState->nRegisteredHeight;
     214        6706 :         if (nConfirmations >= this->m_consensus_params.nMasternodeMinimumConfirmations) {
     215        3427 :             auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
     216        3427 :             newState->UpdateConfirmedHash(dmn.proTxHash, pindexPrev->GetBlockHash());
     217        3427 :             newList.UpdateMN(dmn.proTxHash, newState);
     218        3427 :         }
     219      784043 :     });
     220             : 
     221      214461 :     newList.DecreaseScores();
     222             : 
     223      214461 :     const bool isMNRewardReallocation{
     224      214461 :         DeploymentActiveAfter(pindexPrev, m_chainman.GetConsensus(), Consensus::DEPLOYMENT_MN_RR)};
     225      214461 :     const bool is_v24_deployed{DeploymentActiveAfter(pindexPrev, m_chainman, Consensus::DEPLOYMENT_V24)};
     226             : 
     227             :     // we skip the coinbase
     228      533772 :     for (int i = 1; i < (int)block.vtx.size(); i++) {
     229      319311 :         const CTransaction& tx = *block.vtx[i];
     230             : 
     231      319311 :         if (!tx.IsSpecialTxVersion()) {
     232             :             // only interested in special TXs
     233        6895 :             continue;
     234             :         }
     235             : 
     236      312416 :         if (tx.nType == TRANSACTION_PROVIDER_REGISTER) {
     237        2789 :             const auto opt_proTx = GetTxPayload<CProRegTx>(tx);
     238        2789 :             if (!opt_proTx) {
     239           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
     240             :             }
     241        2789 :             auto& proTx = *opt_proTx;
     242             : 
     243        2789 :             auto dmn = std::make_shared<CDeterministicMN>(newList.GetTotalRegisteredCount(), proTx.nType);
     244        2789 :             dmn->proTxHash = tx.GetHash();
     245             : 
     246             :             // collateralOutpoint is either pointing to an external collateral or to the ProRegTx itself
     247        2789 :             if (proTx.collateralOutpoint.hash.IsNull()) {
     248        1076 :                 dmn->collateralOutpoint = COutPoint(tx.GetHash(), proTx.collateralOutpoint.n);
     249        1076 :             } else {
     250        1713 :                 dmn->collateralOutpoint = proTx.collateralOutpoint;
     251             :             }
     252             : 
     253             :             // Complain about spent collaterals only when we process the tip.
     254             :             // This is safe because blocks below the tip were verified
     255             :             // when they were connected initially.
     256        2789 :             if (!view.GetBestBlock().IsNull()) {
     257        2789 :                 Coin coin;
     258        2789 :                 CAmount expectedCollateral = GetMnType(proTx.nType).collat_amount;
     259        4502 :                 if (!proTx.collateralOutpoint.hash.IsNull() && (!view.GetCoin(dmn->collateralOutpoint, coin) ||
     260        1713 :                                                                 coin.IsSpent() || coin.out.nValue != expectedCollateral)) {
     261             :                     // should actually never get to this point as CheckProRegTx should have handled this case.
     262             :                     // We do this additional check nevertheless to be 100% sure
     263           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-collateral");
     264             :                 }
     265        2789 :             }
     266             : 
     267        2789 :             auto replacedDmn = newList.GetMNByCollateral(dmn->collateralOutpoint);
     268        2789 :             if (replacedDmn != nullptr) {
     269             :                 // This might only happen with a ProRegTx that refers an external collateral
     270             :                 // In that case the new ProRegTx will replace the old one. This means the old one is removed
     271             :                 // and the new one is added like a completely fresh one, which is also at the bottom of the payment list
     272         300 :                 newList.RemoveMN(replacedDmn->proTxHash);
     273         300 :                 if (debugLogs) {
     274         300 :                     LogPrintf("%s -- MN %s removed from list because collateral was used for " /* Continued */
     275             :                               "a new ProRegTx. collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n",
     276             :                               __func__, replacedDmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(),
     277             :                               nHeight, newList.GetCounts().total());
     278         300 :                 }
     279         300 :             }
     280             : 
     281        5588 :             for (const auto& entry : proTx.netInfo->GetEntries()) {
     282        5598 :                 if (const auto service_opt{entry.GetAddrPort()}) {
     283        2799 :                     if (newList.HasUniqueProperty(*service_opt)) {
     284           0 :                         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
     285             :                     }
     286        2799 :                 } else if (const auto domain_opt{entry.GetDomainPort()}) {
     287           0 :                     if (newList.HasUniqueProperty(*domain_opt)) {
     288           0 :                         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
     289             :                     }
     290           0 :                 } else {
     291           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-netinfo-entry");
     292             :                 }
     293             :             }
     294        2789 :             if (newList.HasUniqueProperty(proTx.keyIDOwner) || newList.HasUniqueProperty(proTx.pubKeyOperator)) {
     295           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-key");
     296             :             }
     297             : 
     298        2789 :             dmn->nOperatorReward = proTx.nOperatorReward;
     299             : 
     300        2789 :             auto dmnState = std::make_shared<CDeterministicMNState>(proTx);
     301        2789 :             dmnState->nRegisteredHeight = nHeight;
     302        2789 :             if (proTx.netInfo->IsEmpty()) {
     303             :                 // start in banned pdmnState as we need to wait for a ProUpServTx
     304          40 :                 dmnState->BanIfNotBanned(nHeight);
     305          40 :             }
     306        2789 :             dmn->pdmnState = dmnState;
     307             : 
     308        2789 :             newList.AddMN(dmn);
     309             : 
     310        2789 :             if (debugLogs) {
     311        2789 :                 LogPrintf("%s -- MN %s added at height %d: %s\n", __func__, tx.GetHash().ToString(), nHeight,
     312             :                           proTx.ToString());
     313        2789 :             }
     314      312416 :         } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_SERVICE) {
     315        2897 :             const auto opt_proTx = GetTxPayload<CProUpServTx>(tx);
     316        2897 :             if (!opt_proTx) {
     317           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
     318             :             }
     319             : 
     320        5934 :             for (const auto& entry : opt_proTx->netInfo->GetEntries()) {
     321        6074 :                 if (const auto service_opt{entry.GetAddrPort()}) {
     322        4423 :                     if (newList.HasUniqueProperty(*service_opt) &&
     323        1406 :                         newList.GetUniquePropertyMN(*service_opt)->proTxHash != opt_proTx->proTxHash) {
     324           0 :                         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
     325             :                     }
     326        3057 :                 } else if (const auto domain_opt{entry.GetDomainPort()}) {
     327          20 :                     if (newList.HasUniqueProperty(*domain_opt) &&
     328           0 :                         newList.GetUniquePropertyMN(*domain_opt)->proTxHash != opt_proTx->proTxHash) {
     329           0 :                         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-dup-netinfo-entry");
     330             :                     }
     331          20 :                 } else {
     332           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-netinfo-entry");
     333             :                 }
     334             :             }
     335             : 
     336        2897 :             auto dmn = newList.GetMN(opt_proTx->proTxHash);
     337        2897 :             if (!dmn) {
     338           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-hash");
     339             :             }
     340        2897 :             if (opt_proTx->nType != dmn->nType) {
     341           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-type-mismatch");
     342             :             }
     343        2897 :             if (!IsValidMnType(opt_proTx->nType)) {
     344           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-type");
     345             :             }
     346             : 
     347        2897 :             auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
     348        2897 :             if (is_v24_deployed) {
     349             :                 // Extended addresses support in v24 means that the version can be updated
     350          30 :                 newState->nVersion = opt_proTx->nVersion;
     351          30 :             }
     352        2897 :             newState->netInfo = opt_proTx->netInfo;
     353        2897 :             newState->scriptOperatorPayout = opt_proTx->scriptOperatorPayout;
     354        2897 :             if (opt_proTx->nType == MnType::Evo) {
     355         162 :                 newState->platformNodeID = opt_proTx->platformNodeID;
     356         162 :                 if (opt_proTx->nVersion < ProTxVersion::ExtAddr) {
     357         132 :                     newState->platformP2PPort = opt_proTx->platformP2PPort;
     358         132 :                     newState->platformHTTPPort = opt_proTx->platformHTTPPort;
     359         132 :                 } else {
     360             :                     // From ExtAddr onwards the Platform ports are stored in netInfo. Clear the
     361             :                     // legacy scalar fields (which a legacy registration may have left set) so the
     362             :                     // in-memory state matches its serialized form, which omits them for ExtAddr
     363             :                     // (see CDeterministicMNState serialization). Otherwise a stale value would
     364             :                     // survive in diff-reconstructed lists but vanish through a snapshot round-trip.
     365          30 :                     newState->platformP2PPort = 0;
     366          30 :                     newState->platformHTTPPort = 0;
     367             :                 }
     368         162 :             }
     369        2897 :             if (newState->IsBanned()) {
     370             :                 // only revive when all keys are set
     371         244 :                 if (newState->pubKeyOperator != CBLSLazyPublicKey() && !newState->keyIDVoting.IsNull() &&
     372         122 :                     !newState->keyIDOwner.IsNull()) {
     373         122 :                     newState->Revive(nHeight);
     374         122 :                     if (debugLogs) {
     375         122 :                         LogPrintf("%s -- MN %s revived at height %d\n", __func__, opt_proTx->proTxHash.ToString(), nHeight);
     376         122 :                     }
     377         122 :                 }
     378         122 :             }
     379             : 
     380        2897 :             newList.UpdateMN(opt_proTx->proTxHash, newState);
     381        2897 :             if (debugLogs) {
     382        2897 :                 LogPrintf("%s -- MN %s updated at height %d: %s\n", __func__, opt_proTx->proTxHash.ToString(), nHeight,
     383             :                           opt_proTx->ToString());
     384        2897 :             }
     385      309627 :         } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REGISTRAR) {
     386         126 :             const auto opt_proTx = GetTxPayload<CProUpRegTx>(tx);
     387         126 :             if (!opt_proTx) {
     388           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
     389             :             }
     390             : 
     391         126 :             auto dmn = newList.GetMN(opt_proTx->proTxHash);
     392         126 :             if (!dmn) {
     393           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-hash");
     394             :             }
     395         126 :             auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
     396         126 :             if (newState->pubKeyOperator != opt_proTx->pubKeyOperator) {
     397             :                 // reset all operator related fields and put MN into PoSe-banned state in case the operator key changes
     398           6 :                 newState->ResetOperatorFields();
     399           6 :                 newState->BanIfNotBanned(nHeight);
     400             :                 // we update pubKeyOperator here, make sure state version matches
     401             :                 // Make sure we don't accidentally downgrade the state version if using version after basic BLS
     402           6 :                 newState->nVersion = newState->nVersion > ProTxVersion::BasicBLS ? newState->nVersion : opt_proTx->nVersion;
     403           6 :                 newState->netInfo = NetInfoInterface::MakeNetInfo(newState->nVersion);
     404           6 :                 newState->pubKeyOperator = opt_proTx->pubKeyOperator;
     405           6 :             }
     406         126 :             newState->keyIDVoting = opt_proTx->keyIDVoting;
     407         126 :             newState->scriptPayout = opt_proTx->scriptPayout;
     408             : 
     409         126 :             newList.UpdateMN(opt_proTx->proTxHash, newState);
     410             : 
     411         126 :             if (debugLogs) {
     412         126 :                 LogPrintf("%s -- MN %s updated at height %d: %s\n", __func__, opt_proTx->proTxHash.ToString(), nHeight,
     413             :                           opt_proTx->ToString());
     414         126 :             }
     415      306730 :         } else if (tx.nType == TRANSACTION_PROVIDER_UPDATE_REVOKE) {
     416          73 :             const auto opt_proTx = GetTxPayload<CProUpRevTx>(tx);
     417          73 :             if (!opt_proTx) {
     418           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-payload");
     419             :             }
     420             : 
     421          73 :             auto dmn = newList.GetMN(opt_proTx->proTxHash);
     422          73 :             if (!dmn) {
     423           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-protx-hash");
     424             :             }
     425          73 :             auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
     426          73 :             newState->ResetOperatorFields();
     427          73 :             newState->BanIfNotBanned(nHeight);
     428          73 :             newState->nRevocationReason = opt_proTx->nReason;
     429             : 
     430          73 :             newList.UpdateMN(opt_proTx->proTxHash, newState);
     431             : 
     432          73 :             if (debugLogs) {
     433          73 :                 LogPrintf("%s -- MN %s revoked operator key at height %d: %s\n", __func__,
     434             :                           opt_proTx->proTxHash.ToString(), nHeight, opt_proTx->ToString());
     435          73 :             }
     436      306604 :         } else if (tx.nType == TRANSACTION_QUORUM_COMMITMENT) {
     437      303858 :             const auto opt_qc = GetTxPayload<llmq::CFinalCommitmentTxPayload>(tx);
     438      303858 :             if (!opt_qc) {
     439           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-payload");
     440             :             }
     441      303858 :             if (!opt_qc->commitment.IsNull()) {
     442        5851 :                 const auto& llmq_params_opt = Params().GetLLMQ(opt_qc->commitment.llmqType);
     443        5851 :                 if (!llmq_params_opt.has_value()) {
     444           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-commitment-type");
     445             :                 }
     446        5851 :                 int qcnHeight = int(opt_qc->nHeight);
     447       11702 :                 int quorumHeight = qcnHeight - (qcnHeight % llmq_params_opt->dkgInterval) +
     448        5851 :                                    int(opt_qc->commitment.quorumIndex);
     449        5851 :                 auto pQuorumBaseBlockIndex = pindexPrev->GetAncestor(quorumHeight);
     450        5851 :                 if (!pQuorumBaseBlockIndex || pQuorumBaseBlockIndex->GetBlockHash() != opt_qc->commitment.quorumHash) {
     451             :                     // we should actually never get into this case as validation should have caught it...but let's be sure
     452           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-qc-quorum-hash");
     453             :                 }
     454             : 
     455             :                 // The commitment has already been validated at this point, so it's safe to use members of it
     456             : 
     457        5851 :                 const auto members = llmq::utils::GetAllQuorumMembers(opt_qc->commitment.llmqType,
     458        5851 :                                                                       {m_dmnman, m_qsnapman, m_chainman,
     459        5851 :                                                                        pQuorumBaseBlockIndex});
     460        5851 :                 HandleQuorumCommitment(opt_qc->commitment, members, debugLogs, newList);
     461        5851 :             }
     462      303858 :         }
     463      312416 :     }
     464             : 
     465             :     // we skip the coinbase
     466      533772 :     for (int i = 1; i < (int)block.vtx.size(); i++) {
     467      319311 :         const CTransaction& tx = *block.vtx[i];
     468             : 
     469             :         // check if any existing MN collateral is spent by this transaction
     470      348499 :         for (const auto& in : tx.vin) {
     471       29188 :             auto dmn = newList.GetMNByCollateral(in.prevout);
     472       29188 :             if (dmn && dmn->collateralOutpoint == in.prevout) {
     473         261 :                 newList.RemoveMN(dmn->proTxHash);
     474             : 
     475         261 :                 if (debugLogs) {
     476         261 :                     LogPrintf("%s -- MN %s removed from list because collateral was spent. " /* Continued */
     477             :                               "collateralOutpoint=%s, nHeight=%d, mapCurMNs.allMNsCount=%d\n",
     478             :                               __func__, dmn->proTxHash.ToString(), dmn->collateralOutpoint.ToStringShort(), nHeight,
     479             :                               newList.GetCounts().total());
     480         261 :                 }
     481         261 :             }
     482       29188 :         }
     483      319311 :     }
     484             : 
     485             :     // The payee for the current block was determined by the previous block's list, but it might have disappeared in the
     486             :     // current block. We still pay that MN one last time, however.
     487      348930 :     if (auto dmn = payee ? newList.GetMN(payee->proTxHash) : nullptr) {
     488      134469 :         auto newState = std::make_shared<CDeterministicMNState>(*dmn->pdmnState);
     489      134469 :         newState->nLastPaidHeight = nHeight;
     490             :         // Starting from v19 and until MNRewardReallocation, EvoNodes will be paid 4 blocks in a row
     491             :         // No need to check if v19 is active, since EvoNode ProRegTxes are allowed only after v19 activation
     492             :         // Note: If the payee wasn't found in the current block that's fine
     493      134469 :         if (dmn->nType == MnType::Evo && !isMNRewardReallocation) {
     494       11689 :             ++newState->nConsecutivePayments;
     495       11689 :             if (debugLogs) {
     496       11689 :                 LogPrint(BCLog::MNPAYMENTS, "%s -- MN %s is an EvoNode, bumping nConsecutivePayments to %d\n", __func__,
     497             :                          dmn->proTxHash.ToString(), newState->nConsecutivePayments);
     498       11689 :             }
     499       11689 :         }
     500      134469 :         newList.UpdateMN(payee->proTxHash, newState);
     501      134469 :     }
     502             : 
     503             :     // reset nConsecutivePayments on non-paid EvoNodes
     504      214461 :     auto newList2 = newList;
     505     1000732 :     newList2.ForEachMN(/*onlyValid=*/false, [&](const auto& dmn) {
     506      786271 :         if (dmn.nType != MnType::Evo) return;
     507       66094 :         if (payee != nullptr && dmn.proTxHash == payee->proTxHash && !isMNRewardReallocation) return;
     508       54405 :         if (dmn.pdmnState->nConsecutivePayments == 0) return;
     509        2937 :         if (debugLogs) {
     510        2937 :             LogPrint(BCLog::MNPAYMENTS, "%s -- MN %s, reset nConsecutivePayments %d->0\n", __func__,
     511             :                      dmn.proTxHash.ToString(), dmn.pdmnState->nConsecutivePayments);
     512        2937 :         }
     513        2937 :         auto newState = std::make_shared<CDeterministicMNState>(*dmn.pdmnState);
     514        2937 :         newState->nConsecutivePayments = 0;
     515        2937 :         newList.UpdateMN(dmn.proTxHash, newState);
     516      786271 :     });
     517             : 
     518      214461 :     mnListRet = newList;
     519             : 
     520      214461 :     return true;
     521      214461 : }
     522             : 
     523      341271 : bool CSpecialTxProcessor::ProcessSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, const CCoinsViewCache& view, bool fJustCheck,
     524             :                                                    bool fCheckCbTxMerkleRoots, BlockValidationState& state, std::optional<MNListUpdates>& updatesRet)
     525             : {
     526      341271 :     AssertLockHeld(::cs_main);
     527             : 
     528             :     try {
     529             :         static int64_t nTimeLoop = 0;
     530             :         static int64_t nTimeQuorum = 0;
     531             :         static int64_t nTimeDMN = 0;
     532             :         static int64_t nTimeMerkleMNL = 0;
     533             :         static int64_t nTimeMerkleQuorums = 0;
     534             :         static int64_t nTimeCbTxCL = 0;
     535             :         static int64_t nTimeMnehf = 0;
     536             :         static int64_t nTimePayload = 0;
     537             :         static int64_t nTimeCreditPool = 0;
     538             : 
     539      341271 :         int64_t nTime1 = GetTimeMicros();
     540             : 
     541      341271 :         std::optional<CCbTx> opt_cbTx{std::nullopt};
     542      341271 :         if (fCheckCbTxMerkleRoots && block.vtx.size() > 0 && block.vtx[0]->nType == TRANSACTION_COINBASE) {
     543      167555 :             const auto& tx = block.vtx[0];
     544      167555 :             if (!tx->IsCoinBase()) {
     545           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-invalid");
     546             :             }
     547      167555 :             if (opt_cbTx = GetTxPayload<CCbTx>(*tx); opt_cbTx) {
     548      167555 :                 TxValidationState tx_state;
     549      167555 :                 if (!CheckCbTx(*opt_cbTx, pindex->pprev, tx_state)) {
     550           0 :                     assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS ||
     551             :                            tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL);
     552           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
     553           0 :                                          strprintf("Special Transaction check failed (tx hash %s) %s",
     554           0 :                                                    tx->GetHash().ToString(), tx_state.GetDebugMessage()));
     555             :                 }
     556      167555 :             } else {
     557           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-payload");
     558             :             }
     559      167555 :         }
     560      341271 :         if (fCheckCbTxMerkleRoots) {
     561             :             // To ensure that opt_cbTx is not missing when it's supposed to be
     562      341067 :             if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003) && !opt_cbTx.has_value()) {
     563           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-version");
     564             :             }
     565      341067 :         }
     566             : 
     567      341271 :         int64_t nTime2 = GetTimeMicros();
     568      341271 :         nTimePayload += nTime2 - nTime1;
     569      341271 :         LogPrint(BCLog::BENCHMARK, "      - GetTxPayload: %.2fms [%.2fs]\n", 0.001 * (nTime2 - nTime1),
     570             :                  nTimePayload * 0.000001);
     571             : 
     572      341271 :         CRangesSet indexes;
     573      341271 :         if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20)) {
     574      111528 :             CCreditPool creditPool{m_cpoolman.GetCreditPool(pindex->pprev)};
     575      111528 :             LogPrint(BCLog::CREDITPOOL, "CSpecialTxProcessor::%s -- CCreditPool is %s\n", __func__, creditPool.ToString());
     576      111528 :             indexes = std::move(creditPool.indexes);
     577      111528 :         }
     578             : 
     579     1046655 :         for (size_t i = 0; i < block.vtx.size(); ++i) {
     580             :             // we validated CCbTx above, starts from the 2nd transaction
     581      705403 :             if (i == 0 && block.vtx[i]->nType == TRANSACTION_COINBASE) continue;
     582             : 
     583      537848 :             const auto ptr_tx = block.vtx[i];
     584      537848 :             TxValidationState tx_state;
     585             :             // At this moment CheckSpecialTx() may fail by 2 possible ways:
     586             :             // consensus failures and "TX_BAD_SPECIAL"
     587      537848 :             if (!CheckSpecialTxInner(m_dmnman, m_qsnapman, m_chainman, m_qman, *ptr_tx, pindex->pprev, view, indexes,
     588      537848 :                                      fCheckCbTxMerkleRoots, tx_state)) {
     589          19 :                 assert(tx_state.GetResult() == TxValidationResult::TX_CONSENSUS || tx_state.GetResult() == TxValidationResult::TX_BAD_SPECIAL);
     590          38 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, tx_state.GetRejectReason(),
     591          19 :                                  strprintf("Special Transaction check failed (tx hash %s) %s", ptr_tx->GetHash().ToString(), tx_state.GetDebugMessage()));
     592             :             }
     593      537848 :         }
     594             : 
     595      341252 :         int64_t nTime3 = GetTimeMicros();
     596      341252 :         nTimeLoop += nTime3 - nTime2;
     597      341252 :         LogPrint(BCLog::BENCHMARK, "      - Loop: %.2fms [%.2fs]\n", 0.001 * (nTime3 - nTime2), nTimeLoop * 0.000001);
     598             : 
     599      341252 :         if (opt_cbTx.has_value()) {
     600      167537 :             if (!CheckCreditPoolDiffForBlock(block, pindex, *opt_cbTx, state)) {
     601          24 :                 return error("CSpecialTxProcessor: CheckCreditPoolDiffForBlock for block %s failed with %s",
     602          24 :                              pindex->GetBlockHash().ToString(), state.ToString());
     603             :             }
     604      167513 :         }
     605             : 
     606      341228 :         int64_t nTime4 = GetTimeMicros();
     607      341228 :         nTimeCreditPool += nTime4 - nTime3;
     608      341228 :         LogPrint(BCLog::BENCHMARK, "      - CheckCreditPoolDiffForBlock: %.2fms [%.2fs]\n", 0.001 * (nTime4 - nTime3),
     609             :                  nTimeCreditPool * 0.000001);
     610             : 
     611      341228 :         if (!m_qblockman.ProcessBlock(block, pindex, state, fJustCheck, fCheckCbTxMerkleRoots)) {
     612             :             // pass the state returned by the function above
     613           4 :             return false;
     614             :         }
     615             : 
     616      341224 :         int64_t nTime5 = GetTimeMicros();
     617      341224 :         nTimeQuorum += nTime5 - nTime4;
     618      341224 :         LogPrint(BCLog::BENCHMARK, "      - m_qblockman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime5 - nTime4),
     619             :                  nTimeQuorum * 0.000001);
     620             : 
     621      341224 :         CDeterministicMNList mn_list;
     622      341224 :         if (DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0003)) {
     623      167509 :             if (!BuildNewListFromBlock(block, pindex->pprev, view, true, state, mn_list)) {
     624             :                 // pass the state returned by the function above
     625           0 :                 return false;
     626             :             }
     627      167509 :             mn_list.SetBlockHash(pindex->GetBlockHash());
     628             : 
     629      167509 :             if (!fJustCheck && !m_dmnman.ProcessBlock(block, pindex, state, mn_list, updatesRet)) {
     630             :                 // pass the state returned by the function above
     631           0 :                 return false;
     632             :             }
     633      167509 :         }
     634             : 
     635      341224 :         int64_t nTime6 = GetTimeMicros();
     636      341224 :         nTimeDMN += nTime6 - nTime5;
     637      341224 :         LogPrint(BCLog::BENCHMARK, "      - m_dmnman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5),
     638             :                  nTimeDMN * 0.000001);
     639             : 
     640      341224 :         if (opt_cbTx.has_value()) {
     641      167509 :             uint256 calculatedMerkleRootMNL;
     642      167509 :             if (!CalcCbTxMerkleRootMNList(calculatedMerkleRootMNL, mn_list.to_sml(), state)) {
     643             :                 // pass the state returned by the function above
     644           0 :                 return false;
     645             :             }
     646      167509 :             if (calculatedMerkleRootMNL != opt_cbTx->merkleRootMNList) {
     647           0 :                 return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-mnmerkleroot");
     648             :             }
     649             : 
     650      167509 :             int64_t nTime6_1 = GetTimeMicros();
     651      167509 :             nTimeMerkleMNL += nTime6_1 - nTime6;
     652      167509 :             LogPrint(BCLog::BENCHMARK, "      - CalcCbTxMerkleRootMNList: %.2fms [%.2fs]\n",
     653             :                      0.001 * (nTime6_1 - nTime6), nTimeMerkleMNL * 0.000001);
     654             : 
     655      167509 :             if (opt_cbTx->nVersion >= CCbTx::Version::MERKLE_ROOT_QUORUMS) {
     656      160829 :                 uint256 calculatedMerkleRootQuorums;
     657      160829 :                 if (!CalcCbTxMerkleRootQuorums(block, pindex->pprev, m_qblockman, calculatedMerkleRootQuorums, state)) {
     658             :                     // pass the state returned by the function above
     659           0 :                     return false;
     660             :                 }
     661      160829 :                 if (calculatedMerkleRootQuorums != opt_cbTx->merkleRootQuorums) {
     662           0 :                     return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-quorummerkleroot");
     663             :                 }
     664      160829 :             }
     665             : 
     666      167509 :             int64_t nTime6_2 = GetTimeMicros();
     667      167509 :             nTimeMerkleQuorums += nTime6_2 - nTime6_1;
     668             : 
     669      167509 :             LogPrint(BCLog::BENCHMARK, "      - CalcCbTxMerkleRootQuorums: %.2fms [%.2fs]\n",
     670             :                      0.001 * (nTime6_2 - nTime6_1), nTimeMerkleQuorums * 0.000001);
     671             : 
     672      167509 :             if (!CheckCbTxBestChainlock(*opt_cbTx, pindex, m_consensus_params, m_chainman.ActiveChain(), m_qman,
     673      167509 :                                         m_chainlocks, state)) {
     674             :                 // pass the state returned by the function above
     675           9 :                 return false;
     676             :             }
     677             : 
     678      167500 :             int64_t nTime6_3 = GetTimeMicros();
     679      167500 :             nTimeCbTxCL += nTime6_3 - nTime6_2;
     680      167500 :             LogPrint(BCLog::BENCHMARK, "      - CheckCbTxBestChainlock: %.2fms [%.2fs]\n",
     681             :                      0.001 * (nTime6_3 - nTime6_2), nTimeCbTxCL * 0.000001);
     682      167500 :         }
     683             : 
     684      341215 :         int64_t nTime7 = GetTimeMicros();
     685             : 
     686      341215 :         if (!m_mnhfman.ProcessBlock(block, pindex, fJustCheck, state)) {
     687             :             // pass the state returned by the function above
     688           0 :             return false;
     689             :         }
     690             : 
     691      341215 :         int64_t nTime8 = GetTimeMicros();
     692      341215 :         nTimeMnehf += nTime8 - nTime7;
     693      341215 :         LogPrint(BCLog::BENCHMARK, "      - m_mnhfman.ProcessBlock: %.2fms [%.2fs]\n", 0.001 * (nTime8 - nTime7),
     694             :                  nTimeMnehf * 0.000001);
     695             : 
     696      341215 :         if (DeploymentActiveAfter(pindex, m_consensus_params, Consensus::DEPLOYMENT_V19) && bls::bls_legacy_scheme.load()) {
     697             :             // NOTE: The block next to the activation is the one that is using new rules.
     698             :             // V19 activated just activated, so we must switch to the new rules here.
     699        1258 :             bls::bls_legacy_scheme.store(false);
     700        1258 :             LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
     701        1258 :         }
     702      341271 :     } catch (const std::exception& e) {
     703           0 :         LogPrintf("CSpecialTxProcessor::%s -- FAILURE! %s\n", __func__, e.what());
     704           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-procspectxsinblock");
     705           0 :     }
     706             : 
     707      341215 :     return true;
     708      341271 : }
     709             : 
     710       26566 : bool CSpecialTxProcessor::UndoSpecialTxsInBlock(const CBlock& block, const CBlockIndex* pindex, std::optional<MNListUpdates>& updatesRet)
     711             : {
     712       26566 :     AssertLockHeld(::cs_main);
     713             : 
     714       26566 :     auto bls_legacy_scheme = bls::bls_legacy_scheme.load();
     715             : 
     716             :     try {
     717       26566 :         if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V19) && !bls_legacy_scheme) {
     718             :             // NOTE: The block next to the activation is the one that is using new rules.
     719             :             // Removing the activation block here, so we must switch back to the old rules.
     720           7 :             bls::bls_legacy_scheme.store(true);
     721           7 :             LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
     722           7 :         }
     723             : 
     724       26566 :         if (!m_mnhfman.UndoBlock(block, pindex)) {
     725           0 :             return false;
     726             :         }
     727             : 
     728       26566 :         if (!m_dmnman.UndoBlock(pindex, updatesRet)) {
     729           0 :             return false;
     730             :         }
     731             : 
     732       26566 :         if (!m_qblockman.UndoBlock(block, pindex)) {
     733           0 :             return false;
     734             :         }
     735       26566 :     } catch (const std::exception& e) {
     736           0 :         bls::bls_legacy_scheme.store(bls_legacy_scheme);
     737           0 :         LogPrintf("CSpecialTxProcessor::%s -- bls_legacy_scheme=%d\n", __func__, bls::bls_legacy_scheme.load());
     738           0 :         return error(strprintf("CSpecialTxProcessor::%s -- FAILURE! %s\n", __func__, e.what()).c_str());
     739           0 :     }
     740             : 
     741       26566 :     return true;
     742       26566 : }
     743             : 
     744      167537 : bool CSpecialTxProcessor::CheckCreditPoolDiffForBlock(const CBlock& block, const CBlockIndex* pindex, const CCbTx& cbTx,
     745             :                                                       BlockValidationState& state)
     746             : {
     747      167537 :     AssertLockHeld(::cs_main);
     748             : 
     749      167537 :     if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_DIP0008)) return true;
     750      160857 :     if (!DeploymentActiveAt(*pindex, m_consensus_params, Consensus::DEPLOYMENT_V20)) return true;
     751             : 
     752             :     try {
     753      111510 :         const CAmount blockSubsidy = GetBlockSubsidy(pindex, m_consensus_params);
     754      223020 :         const auto creditPoolDiff = GetCreditPoolDiffForBlock(m_cpoolman, block,
     755      111510 :                                                               pindex->pprev, m_consensus_params, blockSubsidy, state);
     756      111510 :         if (!creditPoolDiff.has_value()) return false;
     757             : 
     758      111498 :         const CAmount target_balance{cbTx.creditPoolBalance};
     759             :         // But it maybe not included yet in previous block yet; in this case value must be 0
     760      111498 :         const CAmount locked_calculated{creditPoolDiff->GetTotalLocked()};
     761      111498 :         if (target_balance != locked_calculated) {
     762          12 :             LogPrintf("CSpecialTxProcessor::%s -- mismatched locked amount in CbTx: %lld against re-calculated: %lld\n", __func__, target_balance, locked_calculated);
     763          12 :             return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-cbtx-assetlocked-amount");
     764             :         }
     765             : 
     766      111510 :     } catch (const std::exception& e) {
     767           0 :         LogPrintf("CSpecialTxProcessor::%s -- FAILURE! %s\n", __func__, e.what());
     768           0 :         return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "failed-checkcreditpooldiff");
     769           0 :     }
     770             : 
     771      111486 :     return true;
     772      167537 : }
     773             : 
     774             : template <typename ProTx>
     775        7577 : static bool CheckService(const ProTx& proTx, TxValidationState& state)
     776             : {
     777        7577 :     switch (proTx.netInfo->Validate()) {
     778             :     case NetInfoStatus::BadAddress:
     779           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr");
     780             :     case NetInfoStatus::BadPort:
     781           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-port");
     782             :     case NetInfoStatus::BadType:
     783           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-type");
     784             :     case NetInfoStatus::NotRoutable:
     785           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-addr-unroutable");
     786             :     case NetInfoStatus::Malformed:
     787           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-bad");
     788             :     case NetInfoStatus::Success:
     789        7577 :         return true;
     790             :     // Shouldn't be possible during self-checks
     791             :     case NetInfoStatus::BadInput:
     792             :     case NetInfoStatus::Duplicate:
     793             :     case NetInfoStatus::MaxLimit:
     794           0 :         assert(false);
     795             :     } // no default case, so the compiler can warn about missing cases
     796           0 :     assert(false);
     797        7577 : }
     798             : 
     799             : template <typename ProTx>
     800         654 : static bool CheckPlatformFields(const ProTx& proTx, bool is_extended_addr, TxValidationState& state)
     801             : {
     802         654 :     if (proTx.platformNodeID.IsNull()) {
     803           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-nodeid");
     804             :     }
     805             : 
     806         654 :     if (is_extended_addr) {
     807             :         // platformHTTPPort and platformP2PPort have been subsumed by netInfo. They should always be zero.
     808         158 :         if (proTx.platformP2PPort != 0) {
     809           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
     810             :         }
     811         158 :         if (proTx.platformHTTPPort != 0) {
     812           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
     813             :         }
     814         158 :         return true;
     815             :     }
     816             : 
     817         496 :     if (::IsNodeOnMainnet()) {
     818           0 :         if (proTx.platformP2PPort != ::MainParams().GetDefaultPlatformP2PPort()) {
     819           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
     820             :         }
     821           0 :         if (proTx.platformHTTPPort != ::MainParams().GetDefaultPlatformHTTPPort()) {
     822           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
     823             :         }
     824           0 :     }
     825         496 :     if (proTx.platformP2PPort == ::MainParams().GetDefaultPort()) {
     826           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-p2p-port");
     827             :     }
     828         496 :     if (proTx.platformHTTPPort == ::MainParams().GetDefaultPort()) {
     829           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-http-port");
     830             :     }
     831             : 
     832         496 :     const uint16_t core_port{proTx.netInfo->GetPrimary().GetPort()};
     833         496 :     if (proTx.platformP2PPort == proTx.platformHTTPPort || proTx.platformP2PPort == core_port ||
     834         496 :         proTx.platformHTTPPort == core_port) {
     835           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-platform-dup-ports");
     836             :     }
     837             : 
     838         496 :     return true;
     839         654 : }
     840             : 
     841             : template <typename ProTx>
     842         143 : static bool CheckHashSig(const ProTx& proTx, const PKHash& pkhash, TxValidationState& state)
     843             : {
     844         145 :     if (std::string strError; !CHashSigner::VerifyHash(::SerializeHash(proTx), ToKeyID(pkhash), proTx.vchSig, strError)) {
     845           2 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
     846             :     }
     847         141 :     return true;
     848         143 : }
     849             : 
     850             : template <typename ProTx>
     851        2376 : static bool CheckStringSig(const ProTx& proTx, const PKHash& pkhash, TxValidationState& state)
     852             : {
     853        4752 :     if (std::string strError;
     854        2376 :         !CMessageSigner::VerifyMessage(ToKeyID(pkhash), proTx.vchSig, proTx.MakeSignString(), strError)) {
     855           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
     856             :     }
     857        2376 :     return true;
     858        2376 : }
     859             : 
     860             : template <typename ProTx>
     861        3872 : static bool CheckHashSig(const ProTx& proTx, const CBLSPublicKey& pubKey, TxValidationState& state)
     862             : {
     863        3872 :     if (!proTx.sig.VerifyInsecure(pubKey, ::SerializeHash(proTx))) {
     864           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
     865             :     }
     866        3872 :     return true;
     867        3872 : }
     868             : 
     869             : template <typename ProTx>
     870        7881 : static std::optional<ProTx> GetValidatedPayload(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
     871             :                                                 const ChainstateManager& chainman, TxValidationState& state)
     872             : {
     873        7881 :     if (tx.nType != ProTx::SPECIALTX_TYPE) {
     874           0 :         state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-type");
     875           0 :         return std::nullopt;
     876             :     }
     877             : 
     878        7881 :     auto opt_ptx = GetTxPayload<ProTx>(tx);
     879        7881 :     if (!opt_ptx) {
     880           0 :         state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-payload");
     881           0 :         return std::nullopt;
     882             :     }
     883        7881 :     if (!opt_ptx->IsTriviallyValid(pindexPrev, chainman, state)) {
     884             :         // pass the state returned by the function above
     885           0 :         return std::nullopt;
     886             :     }
     887        7881 :     return opt_ptx;
     888        7881 : }
     889             : 
     890             : /**
     891             :  * Validates potential changes to masternode state version by ProTx transaction version
     892             :  * @param[in]  pindexPrev    Previous block index to validate DEPLOYMENT_V24 activation
     893             :  * @param[in]  tx_type       Special transaction type
     894             :  * @param[in]  state_version Current masternode state version
     895             :  * @param[in]  tx_version    Proposed transaction version
     896             :  * @param[out] state         This may be set to an Error state if any error occurred processing them
     897             :  * @returns                  true if version change is valid or DEPLOYMENT_V24 is not active
     898             :  */
     899        4026 : static bool IsVersionChangeValid(gsl::not_null<const CBlockIndex*> pindexPrev, const uint16_t tx_type,
     900             :                                  const uint16_t state_version, const uint16_t tx_version,
     901             :                                  const ChainstateManager& chainman, TxValidationState& state)
     902             : {
     903        4026 :     if (!DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)) {
     904             :         // New restrictions only apply after v24 deployment
     905        3964 :         return true;
     906             :     }
     907             : 
     908          62 :     if (state_version >= ProTxVersion::BasicBLS && tx_version == ProTxVersion::LegacyBLS) {
     909             :         // Don't allow legacy scheme versioned transactions after upgrading to basic scheme
     910           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-downgrade");
     911             :     }
     912             : 
     913          62 :     if (state_version == ProTxVersion::LegacyBLS && tx_version > ProTxVersion::BasicBLS) {
     914             :         // Nodes using the legacy scheme must first upgrade to the basic scheme before upgrading further
     915           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-upgrade");
     916             :     }
     917             : 
     918          62 :     if (tx_type != TRANSACTION_PROVIDER_UPDATE_SERVICE && tx_version == ProTxVersion::ExtAddr) {
     919             :         // Only new entries (ProRegTx) and service updates (ProUpServTx) can use ExtAddr versioning
     920           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-tx-type");
     921             :     }
     922             : 
     923          62 :     return true;
     924        4026 : }
     925             : 
     926        3855 : bool CheckProRegTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
     927             :                    CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman,
     928             :                    TxValidationState& state, bool check_sigs)
     929             : {
     930        3855 :     const auto opt_ptx = GetValidatedPayload<CProRegTx>(tx, pindexPrev, chainman, state);
     931        3855 :     if (!opt_ptx) {
     932             :         // pass the state returned by the function above
     933           0 :         return false;
     934             :     }
     935             : 
     936        3855 :     const bool is_v24_active{DeploymentActiveAfter(pindexPrev, chainman, Consensus::DEPLOYMENT_V24)};
     937             : 
     938             :     // No longer allow legacy scheme masternode registration
     939        3855 :     if (is_v24_active && opt_ptx->nVersion < ProTxVersion::BasicBLS) {
     940           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-version-disallowed");
     941             :     }
     942             : 
     943             :     // It's allowed to set addr to 0, which will put the MN into PoSe-banned state and require a ProUpServTx to be
     944             :     // issues later. If any of both is set, it must be valid however
     945        3855 :     if (!opt_ptx->netInfo->IsEmpty() && !CheckService(*opt_ptx, state)) {
     946             :         // pass the state returned by the function above
     947           0 :         return false;
     948             :     }
     949             : 
     950        3855 :     if (opt_ptx->nType == MnType::Evo) {
     951         396 :         if (!CheckPlatformFields(*opt_ptx, opt_ptx->nVersion >= ProTxVersion::ExtAddr, state)) {
     952           0 :             return false;
     953             :         }
     954         396 :     }
     955             : 
     956        3855 :     CTxDestination collateralTxDest;
     957        3855 :     const PKHash* keyForPayloadSig = nullptr;
     958        3855 :     COutPoint collateralOutpoint;
     959             : 
     960        3855 :     CAmount expectedCollateral = GetMnType(opt_ptx->nType).collat_amount;
     961             : 
     962        3855 :     if (!opt_ptx->collateralOutpoint.hash.IsNull()) {
     963        2382 :         Coin coin;
     964        2382 :         if (!view.GetCoin(opt_ptx->collateralOutpoint, coin) || coin.IsSpent() || coin.out.nValue != expectedCollateral) {
     965           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral");
     966             :         }
     967             : 
     968        2382 :         if (!ExtractDestination(coin.out.scriptPubKey, collateralTxDest)) {
     969           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-dest");
     970             :         }
     971             : 
     972             :         // Extract key from collateral. This only works for P2PK and P2PKH collaterals and will fail for P2SH.
     973             :         // Issuer of this ProRegTx must prove ownership with this key by signing the ProRegTx
     974        2382 :         keyForPayloadSig = std::get_if<PKHash>(&collateralTxDest);
     975        2382 :         if (!keyForPayloadSig) {
     976           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-pkh");
     977             :         }
     978             : 
     979        2382 :         collateralOutpoint = opt_ptx->collateralOutpoint;
     980        2382 :     } else {
     981        1473 :         if (opt_ptx->collateralOutpoint.n >= tx.vout.size()) {
     982           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-index");
     983             :         }
     984        1473 :         if (tx.vout[opt_ptx->collateralOutpoint.n].nValue != expectedCollateral) {
     985           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral");
     986             :         }
     987             : 
     988        1473 :         if (!ExtractDestination(tx.vout[opt_ptx->collateralOutpoint.n].scriptPubKey, collateralTxDest)) {
     989           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-dest");
     990             :         }
     991             : 
     992        1473 :         collateralOutpoint = COutPoint(tx.GetHash(), opt_ptx->collateralOutpoint.n);
     993             :     }
     994             : 
     995             :     // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server)
     996             :     // this check applies to internal and external collateral, but internal collaterals are not necessarily a P2PKH
     997        7710 :     if (collateralTxDest == CTxDestination(PKHash(opt_ptx->keyIDOwner)) ||
     998        3855 :         collateralTxDest == CTxDestination(PKHash(opt_ptx->keyIDVoting))) {
     999           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-reuse");
    1000             :     }
    1001             : 
    1002        3855 :     if (pindexPrev) {
    1003        3855 :         auto mnList = dmnman.GetListForBlock(pindexPrev);
    1004             : 
    1005             :         // only allow reusing of addresses when it's for the same collateral (which replaces the old MN)
    1006        7832 :         for (const auto& entry : opt_ptx->netInfo->GetEntries()) {
    1007        7954 :             if (const auto service_opt{entry.GetAddrPort()}) {
    1008        4313 :                 if (mnList.HasUniqueProperty(*service_opt) &&
    1009         340 :                     mnList.GetUniquePropertyMN(*service_opt)->collateralOutpoint != collateralOutpoint) {
    1010           0 :                     return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
    1011             :                 }
    1012        3981 :             } else if (const auto domain_opt{entry.GetDomainPort()}) {
    1013           4 :                 if (mnList.HasUniqueProperty(*domain_opt) &&
    1014           0 :                     mnList.GetUniquePropertyMN(*domain_opt)->collateralOutpoint != collateralOutpoint) {
    1015           0 :                     return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
    1016             :                 }
    1017           4 :             } else {
    1018           0 :                 return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-entry");
    1019             :             }
    1020             :         }
    1021             : 
    1022             :         // never allow duplicate keys, even if this ProTx would replace an existing MN
    1023        3855 :         if (mnList.HasUniqueProperty(opt_ptx->keyIDOwner) || mnList.HasUniqueProperty(opt_ptx->pubKeyOperator)) {
    1024           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-key");
    1025             :         }
    1026             : 
    1027             :         // never allow duplicate platformNodeIds for EvoNodes
    1028        3855 :         if (opt_ptx->nType == MnType::Evo) {
    1029         396 :             if (mnList.HasUniqueProperty(opt_ptx->platformNodeID)) {
    1030           6 :                 return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-platformnodeid");
    1031             :             }
    1032         390 :         }
    1033             : 
    1034        3849 :         if (!DeploymentDIP0003Enforced(pindexPrev->nHeight, Params().GetConsensus())) {
    1035          25 :             if (opt_ptx->keyIDOwner != opt_ptx->keyIDVoting) {
    1036           0 :                 return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-key-not-same");
    1037             :             }
    1038          25 :         }
    1039        3855 :     }
    1040             : 
    1041        3849 :     if (!CheckInputsHash(tx, *opt_ptx, state)) {
    1042             :         // pass the state returned by the function above
    1043           0 :         return false;
    1044             :     }
    1045             : 
    1046        3849 :     if (keyForPayloadSig) {
    1047             :         // collateral is not part of this ProRegTx, so we must verify ownership of the collateral
    1048        2376 :         if (check_sigs && !CheckStringSig(*opt_ptx, *keyForPayloadSig, state)) {
    1049             :             // pass the state returned by the function above
    1050           0 :             return false;
    1051             :         }
    1052        2376 :     } else {
    1053             :         // collateral is part of this ProRegTx, so we know the collateral is owned by the issuer
    1054        1473 :         if (!opt_ptx->vchSig.empty()) {
    1055           0 :             return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-sig");
    1056             :         }
    1057             :     }
    1058             : 
    1059        3849 :     return true;
    1060        3855 : }
    1061             : 
    1062        3786 : bool CheckProUpServTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, CDeterministicMNManager& dmnman,
    1063             :                       const ChainstateManager& chainman, TxValidationState& state, bool check_sigs)
    1064             : {
    1065        3786 :     const auto opt_ptx = GetValidatedPayload<CProUpServTx>(tx, pindexPrev, chainman, state);
    1066        3786 :     if (!opt_ptx) {
    1067             :         // pass the state returned by the function above
    1068           0 :         return false;
    1069             :     }
    1070             : 
    1071        3786 :     if (!CheckService(*opt_ptx, state)) {
    1072             :         // pass the state returned by the function above
    1073           0 :         return false;
    1074             :     }
    1075             : 
    1076        3786 :     if (opt_ptx->nType == MnType::Evo) {
    1077         258 :         if (!CheckPlatformFields(*opt_ptx, opt_ptx->nVersion >= ProTxVersion::ExtAddr, state)) {
    1078           0 :             return false;
    1079             :         }
    1080         258 :     }
    1081             : 
    1082        3786 :     auto mnList = dmnman.GetListForBlock(pindexPrev);
    1083        3786 :     auto dmn = mnList.GetMN(opt_ptx->proTxHash);
    1084        3786 :     if (!dmn) {
    1085           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash");
    1086             :     }
    1087             : 
    1088        3786 :     if (!IsVersionChangeValid(pindexPrev, tx.nType, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) {
    1089             :         // pass the state returned by the function above
    1090           0 :         return false;
    1091             :     }
    1092             : 
    1093             :     // don't allow updating to addresses already used by other MNs
    1094        7826 :     for (const auto& entry : opt_ptx->netInfo->GetEntries()) {
    1095        8080 :         if (const auto service_opt{entry.GetAddrPort()}) {
    1096        6073 :             if (mnList.HasUniqueProperty(*service_opt) &&
    1097        2063 :                 mnList.GetUniquePropertyMN(*service_opt)->proTxHash != opt_ptx->proTxHash) {
    1098           6 :                 return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
    1099             :             }
    1100        4064 :         } else if (const auto domain_opt{entry.GetDomainPort()}) {
    1101          32 :             if (mnList.HasUniqueProperty(*domain_opt) &&
    1102           2 :                 mnList.GetUniquePropertyMN(*domain_opt)->proTxHash != opt_ptx->proTxHash) {
    1103           2 :                 return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-netinfo-entry");
    1104             :             }
    1105          28 :         } else {
    1106           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-netinfo-entry");
    1107             :         }
    1108             :     }
    1109             : 
    1110             :     // don't allow updating to platformNodeIds already used by other EvoNodes
    1111        3778 :     if (opt_ptx->nType == MnType::Evo) {
    1112         353 :         if (mnList.HasUniqueProperty(opt_ptx->platformNodeID) &&
    1113         103 :             mnList.GetUniquePropertyMN(opt_ptx->platformNodeID)->proTxHash != opt_ptx->proTxHash) {
    1114           3 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-platformnodeid");
    1115             :         }
    1116         247 :     }
    1117             : 
    1118        3775 :     if (opt_ptx->scriptOperatorPayout != CScript()) {
    1119        1984 :         if (dmn->nOperatorReward == 0) {
    1120             :             // don't allow setting operator reward payee in case no operatorReward was set
    1121           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-payee");
    1122             :         }
    1123        1984 :         if (!opt_ptx->scriptOperatorPayout.IsPayToPublicKeyHash() && !opt_ptx->scriptOperatorPayout.IsPayToScriptHash()) {
    1124           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-operator-payee");
    1125             :         }
    1126        1984 :     }
    1127             : 
    1128             :     // we can only check the signature if pindexPrev != nullptr and the MN is known
    1129        3775 :     if (!CheckInputsHash(tx, *opt_ptx, state)) {
    1130             :         // pass the state returned by the function above
    1131           0 :         return false;
    1132             :     }
    1133        3775 :     if (check_sigs && !CheckHashSig(*opt_ptx, dmn->pdmnState->pubKeyOperator.Get(), state)) {
    1134             :         // pass the state returned by the function above
    1135           0 :         return false;
    1136             :     }
    1137             : 
    1138        3775 :     return true;
    1139        3786 : }
    1140             : 
    1141         143 : bool CheckProUpRegTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev,
    1142             :                      CDeterministicMNManager& dmnman, const CCoinsViewCache& view, const ChainstateManager& chainman,
    1143             :                      TxValidationState& state, bool check_sigs)
    1144             : {
    1145         143 :     const auto opt_ptx = GetValidatedPayload<CProUpRegTx>(tx, pindexPrev, chainman, state);
    1146         143 :     if (!opt_ptx) {
    1147             :         // pass the state returned by the function above
    1148           0 :         return false;
    1149             :     }
    1150             : 
    1151         143 :     CTxDestination payoutDest;
    1152         143 :     if (!ExtractDestination(opt_ptx->scriptPayout, payoutDest)) {
    1153             :         // should not happen as we checked script types before
    1154           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-dest");
    1155             :     }
    1156             : 
    1157         143 :     auto mnList = dmnman.GetListForBlock(pindexPrev);
    1158         143 :     auto dmn = mnList.GetMN(opt_ptx->proTxHash);
    1159         143 :     if (!dmn) {
    1160           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash");
    1161             :     }
    1162             : 
    1163         143 :     if (!IsVersionChangeValid(pindexPrev, tx.nType, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) {
    1164             :         // pass the state returned by the function above
    1165           0 :         return false;
    1166             :     }
    1167             : 
    1168             :     // don't allow reuse of payee key for other keys (don't allow people to put the payee key onto an online server)
    1169         286 :     if (payoutDest == CTxDestination(PKHash(dmn->pdmnState->keyIDOwner)) ||
    1170         143 :         payoutDest == CTxDestination(PKHash(opt_ptx->keyIDVoting))) {
    1171           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-payee-reuse");
    1172             :     }
    1173             : 
    1174         143 :     Coin coin;
    1175         143 :     if (!view.GetCoin(dmn->collateralOutpoint, coin) || coin.IsSpent()) {
    1176             :         // this should never happen (there would be no dmn otherwise)
    1177           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-collateral");
    1178             :     }
    1179             : 
    1180             :     // don't allow reuse of collateral key for other keys (don't allow people to put the collateral key onto an online server)
    1181         143 :     CTxDestination collateralTxDest;
    1182         143 :     if (!ExtractDestination(coin.out.scriptPubKey, collateralTxDest)) {
    1183           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-collateral-dest");
    1184             :     }
    1185         286 :     if (collateralTxDest == CTxDestination(PKHash(dmn->pdmnState->keyIDOwner)) ||
    1186         143 :         collateralTxDest == CTxDestination(PKHash(opt_ptx->keyIDVoting))) {
    1187           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-collateral-reuse");
    1188             :     }
    1189             : 
    1190         143 :     if (mnList.HasUniqueProperty(opt_ptx->pubKeyOperator)) {
    1191         136 :         auto otherDmn = mnList.GetUniquePropertyMN(opt_ptx->pubKeyOperator);
    1192         136 :         if (opt_ptx->proTxHash != otherDmn->proTxHash) {
    1193           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-dup-key");
    1194             :         }
    1195         136 :     }
    1196             : 
    1197         143 :     if (!DeploymentDIP0003Enforced(pindexPrev->nHeight, Params().GetConsensus())) {
    1198           1 :         if (dmn->pdmnState->keyIDOwner != opt_ptx->keyIDVoting) {
    1199           0 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-protx-key-not-same");
    1200             :         }
    1201           1 :     }
    1202             : 
    1203         143 :     if (!CheckInputsHash(tx, *opt_ptx, state)) {
    1204             :         // pass the state returned by the function above
    1205           0 :         return false;
    1206             :     }
    1207         143 :     if (check_sigs && !CheckHashSig(*opt_ptx, PKHash(dmn->pdmnState->keyIDOwner), state)) {
    1208             :         // pass the state returned by the function above
    1209           2 :         return false;
    1210             :     }
    1211             : 
    1212         141 :     return true;
    1213         143 : }
    1214             : 
    1215          97 : bool CheckProUpRevTx(const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, CDeterministicMNManager& dmnman,
    1216             :                      const ChainstateManager& chainman, TxValidationState& state, bool check_sigs)
    1217             : {
    1218          97 :     const auto opt_ptx = GetValidatedPayload<CProUpRevTx>(tx, pindexPrev, chainman, state);
    1219          97 :     if (!opt_ptx) {
    1220             :         // pass the state returned by the function above
    1221           0 :         return false;
    1222             :     }
    1223             : 
    1224          97 :     auto mnList = dmnman.GetListForBlock(pindexPrev);
    1225          97 :     auto dmn = mnList.GetMN(opt_ptx->proTxHash);
    1226          97 :     if (!dmn) {
    1227           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-protx-hash");
    1228             :     }
    1229             : 
    1230          97 :     if (!IsVersionChangeValid(pindexPrev, tx.nType, dmn->pdmnState->nVersion, opt_ptx->nVersion, chainman, state)) {
    1231             :         // pass the state returned by the function above
    1232           0 :         return false;
    1233             :     }
    1234             : 
    1235          97 :     if (!CheckInputsHash(tx, *opt_ptx, state)) {
    1236             :         // pass the state returned by the function above
    1237           0 :         return false;
    1238             :     }
    1239          97 :     if (check_sigs && !CheckHashSig(*opt_ptx, dmn->pdmnState->pubKeyOperator.Get(), state)) {
    1240             :         // pass the state returned by the function above
    1241           0 :         return false;
    1242             :     }
    1243             : 
    1244          97 :     return true;
    1245          97 : }
    1246             : 
    1247       47752 : bool IsStandardSpecialTx(const CTransaction& tx, std::string& reason)
    1248             : {
    1249       47752 :     if (!tx.IsSpecialTxVersion()) return true;
    1250             : 
    1251        3951 :     if (tx.nType != TRANSACTION_ASSET_LOCK) return true;
    1252             : 
    1253             :     // Each input is referenced by Platform's funding state transition; beyond this
    1254             :     // many inputs that state transition exceeds Platform's ~20 kB size limit.
    1255             :     static constexpr size_t MAX_STANDARD_ASSET_LOCK_INPUTS{100};
    1256        1326 :     if (tx.vin.size() > MAX_STANDARD_ASSET_LOCK_INPUTS) {
    1257           3 :         reason = "assetlocktx-too-many-inputs";
    1258           3 :         return false;
    1259             :     }
    1260             : 
    1261        1323 :     constexpr int max_tx_size_for_platform = 20480;
    1262        1323 :     if (tx.GetTotalSize() > max_tx_size_for_platform) {
    1263           0 :         reason = "assetlocktx-too-big";
    1264           0 :         return false;
    1265             :     }
    1266             : 
    1267        2646 :     if (const auto opt_assetLockTx = GetTxPayload<CAssetLockPayload>(tx);
    1268        1323 :         opt_assetLockTx.has_value() && opt_assetLockTx->getVersion() >= 2) {
    1269           0 :         reason = "assetlocktx-version-2";
    1270           0 :         return false;
    1271             :     }
    1272             : 
    1273        1323 :     return true;
    1274       47752 : }

Generated by: LCOV version 1.16