LCOV - code coverage report
Current view: top level - src/evo - assetlocktx.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 77 96 80.2 %
Date: 2026-06-25 07:23:43 Functions: 5 7 71.4 %

          Line data    Source code
       1             : // Copyright (c) 2023-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/assetlocktx.h>
       6             : #include <evo/specialtx.h>
       7             : 
       8             : #include <llmq/commitment.h>
       9             : #include <llmq/quorumsman.h>
      10             : #include <llmq/signhash.h>
      11             : 
      12             : #include <chainparams.h>
      13             : #include <consensus/params.h>
      14             : #include <consensus/validation.h>
      15             : #include <deploymentstatus.h>
      16             : #include <logging.h>
      17             : #include <node/blockstorage.h>
      18             : #include <tinyformat.h>
      19             : #include <util/ranges_set.h>
      20             : 
      21             : #include <algorithm>
      22             : 
      23             : using node::BlockManager;
      24             : 
      25             : 
      26             : /**
      27             :  * Asset Lock Transaction
      28             :  */
      29        3007 : bool CheckAssetLockTx(const CTransaction& tx, TxValidationState& state)
      30             : {
      31        3007 :     if (tx.nType != TRANSACTION_ASSET_LOCK) {
      32           1 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-type");
      33             :     }
      34             : 
      35        3006 :     CAmount returnAmount{0};
      36        6365 :     for (const CTxOut& txout : tx.vout) {
      37        3363 :         const CScript& script = txout.scriptPubKey;
      38        3363 :         if (script.empty() || script[0] != OP_RETURN) continue;
      39             : 
      40        3006 :         if (script.size() != 2 || script[1] != 0) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-non-empty-return");
      41             : 
      42        3005 :         if (txout.nValue == 0 || !MoneyRange(txout.nValue)) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-opreturn-outofrange");
      43             : 
      44             :         // Should be only one OP_RETURN
      45        3003 :         if (returnAmount > 0) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-multiple-return");
      46        3002 :         returnAmount = txout.nValue;
      47             :     }
      48             : 
      49        3002 :     if (returnAmount == 0) return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-no-return");
      50             : 
      51        3001 :     const auto opt_assetLockTx = GetTxPayload<CAssetLockPayload>(tx);
      52        3001 :     if (!opt_assetLockTx.has_value()) {
      53           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-payload");
      54             :     }
      55             : 
      56        3001 :     if (opt_assetLockTx->getVersion() == 0 || opt_assetLockTx->getVersion() > CAssetLockPayload::CURRENT_VERSION) {
      57           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-version");
      58             :     }
      59             : 
      60        3001 :     if (opt_assetLockTx->getCreditOutputs().empty()) {
      61           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-emptycreditoutputs");
      62             :     }
      63             : 
      64        3001 :     CAmount creditOutputsAmount = 0;
      65        8871 :     for (const CTxOut& out : opt_assetLockTx->getCreditOutputs()) {
      66        5874 :         if (out.nValue == 0 || !MoneyRange(out.nValue) || !MoneyRange(creditOutputsAmount + out.nValue)) {
      67           3 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-credit-outofrange");
      68             :         }
      69             : 
      70        5871 :         creditOutputsAmount += out.nValue;
      71        5871 :         if (!out.scriptPubKey.IsPayToPublicKeyHash()) {
      72           1 :             return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-pubKeyHash");
      73             :         }
      74             :     }
      75        2997 :     if (creditOutputsAmount != returnAmount) {
      76           3 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetlocktx-creditamount");
      77             :     }
      78             : 
      79        2994 :     return true;
      80        3007 : }
      81             : 
      82           0 : std::string CAssetLockPayload::ToString() const
      83             : {
      84           0 :     std::string outputs{"["};
      85           0 :     for (const CTxOut& tx: creditOutputs) {
      86           0 :         outputs.append(tx.ToString());
      87           0 :         outputs.append(",");
      88             :     }
      89           0 :     outputs.back() = ']';
      90           0 :     return strprintf("CAssetLockPayload(nVersion=%d,creditOutputs=%s)", nVersion, outputs.c_str());
      91           0 : }
      92             : 
      93             : /**
      94             :  * Asset Unlock Transaction (withdrawals)
      95             :  */
      96             : 
      97             : const std::string ASSETUNLOCK_REQUESTID_PREFIX = "plwdtx";
      98             : 
      99        1150 : bool CAssetUnlockPayload::VerifySig(const llmq::CQuorumManager& qman, const uint256& msgHash, gsl::not_null<const CBlockIndex*> pindexTip, TxValidationState& state) const
     100             : {
     101             :     // That quourm hash must be active at `requestHeight`,
     102             :     // and at the quorumHash must be active in either the current or previous quorum cycle
     103             :     // and the sig must validate against that specific quorumHash.
     104             : 
     105             : 
     106        1150 :     Consensus::LLMQType llmqType = Params().GetConsensus().llmqTypePlatform;
     107             : 
     108        1150 :     const auto& llmq_params_opt = Params().GetLLMQ(llmqType);
     109        1150 :     assert(llmq_params_opt.has_value());
     110             : 
     111             :     // We check all active quorums + 1 the latest inactive
     112        1150 :     const int quorums_to_scan = llmq_params_opt->signingActiveQuorumCount + 1;
     113        1150 :     const auto quorums = qman.ScanQuorums(llmqType, pindexTip, quorums_to_scan);
     114             : 
     115        2678 :     if (bool isActive = std::any_of(quorums.begin(), quorums.end(), [&](const auto &q) { return q->qc->quorumHash == quorumHash; }); !isActive) {
     116           6 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-too-old-quorum");
     117             :     }
     118             : 
     119        1144 :     if (static_cast<uint32_t>(pindexTip->nHeight) < requestedHeight || pindexTip->nHeight >= getHeightToExpiry()) {
     120          18 :         LogPrint(BCLog::CREDITPOOL, "Asset unlock tx %d with requested height %d could not be accepted on height: %d\n",
     121             :                 index, requestedHeight, pindexTip->nHeight);
     122          18 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-too-late");
     123             :     }
     124             : 
     125        1126 :     const auto quorum = qman.GetQuorum(llmqType, quorumHash);
     126             :     // quorum must be valid at this point. Let's check and throw error just in case
     127        1126 :     if (!quorum) {
     128           0 :         LogPrintf("%s: ERROR! No quorum for credit pool found for hash=%s\n", __func__, quorumHash.ToString());
     129           0 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-quorum-internal-error");
     130             :     }
     131             : 
     132        1126 :     const uint256 requestId = ::SerializeHash(std::make_pair(ASSETUNLOCK_REQUESTID_PREFIX, index));
     133             : 
     134        2252 :     if (const llmq::SignHash signHash(llmqType, quorum->qc->quorumHash, requestId, msgHash);
     135        1126 :         quorumSig.VerifyInsecure(quorum->qc->quorumPublicKey, signHash.Get())) {
     136        1123 :         return true;
     137             :     }
     138             : 
     139           3 :     return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-not-verified");
     140        1150 : }
     141             : 
     142        1173 : bool CheckAssetUnlockTx(const BlockManager& blockman, const llmq::CQuorumManager& qman, const CTransaction& tx, gsl::not_null<const CBlockIndex*> pindexPrev, const std::optional<CRangesSet>& indexes, TxValidationState& state)
     143             : {
     144             :     // Some checks depends from blockchain status also, such as `known indexes` and `withdrawal limits`
     145             :     // They are omitted here and done by CCreditPool
     146        1173 :     if (tx.nType != TRANSACTION_ASSET_UNLOCK) {
     147           1 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-type");
     148             :     }
     149             : 
     150        1172 :     if (!tx.vin.empty()) {
     151           1 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-have-input");
     152             :     }
     153             : 
     154        1171 :     if (tx.vout.size() > CAssetUnlockPayload::MAXIMUM_WITHDRAWALS) {
     155           1 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-too-many-outs");
     156             :     }
     157             : 
     158        1170 :     const auto opt_assetUnlockTx = GetTxPayload<CAssetUnlockPayload>(tx);
     159        1170 :     if (!opt_assetUnlockTx) {
     160           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-payload");
     161             :     }
     162        1170 :     auto& assetUnlockTx = *opt_assetUnlockTx;
     163             : 
     164        1170 :     if (assetUnlockTx.getVersion() == 0 || assetUnlockTx.getVersion() > CAssetUnlockPayload::CURRENT_VERSION) {
     165           3 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-version");
     166             :     }
     167             : 
     168        1167 :     if (indexes != std::nullopt && indexes->Contains(assetUnlockTx.getIndex())) {
     169          13 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-duplicated-index");
     170             :     }
     171             : 
     172        1158 :     if (LOCK(::cs_main); blockman.LookupBlockIndex(assetUnlockTx.getQuorumHash()) == nullptr) {
     173           4 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-assetunlock-quorum-hash");
     174             :     }
     175             : 
     176             :     // Copy transaction except `quorumSig` field to calculate hash
     177        1150 :     CMutableTransaction tx_copy(tx);
     178        1150 :     const CAssetUnlockPayload payload_copy{assetUnlockTx.getVersion(), assetUnlockTx.getIndex(), assetUnlockTx.getFee(), assetUnlockTx.getRequestedHeight(), assetUnlockTx.getQuorumHash(), CBLSSignature{}};
     179        1150 :     SetTxPayload(tx_copy, payload_copy);
     180             : 
     181        1150 :     uint256 msgHash = tx_copy.GetHash();
     182             : 
     183        1150 :     return assetUnlockTx.VerifySig(qman, msgHash, pindexPrev, state);
     184        1173 : }
     185             : 
     186        3407 : bool GetAssetUnlockFee(const CTransaction& tx, CAmount& txfee, TxValidationState& state)
     187             : {
     188        3407 :     const auto opt_assetUnlockTx = GetTxPayload<CAssetUnlockPayload>(tx);
     189        3407 :     if (!opt_assetUnlockTx) {
     190           0 :         return state.Invalid(TxValidationResult::TX_BAD_SPECIAL, "bad-assetunlocktx-payload");
     191             :     }
     192        3407 :     const CAmount txfee_aux = opt_assetUnlockTx->getFee();
     193        3407 :     if (txfee_aux == 0 || !MoneyRange(txfee_aux)) {
     194           3 :         return state.Invalid(TxValidationResult::TX_CONSENSUS, "bad-txns-assetunlock-fee-outofrange");
     195             :     }
     196        3404 :     txfee = txfee_aux;
     197        3404 :     return true;
     198        3407 : }
     199             : 
     200           0 : std::string CAssetUnlockPayload::ToString() const
     201             : {
     202           0 :     return strprintf("CAssetUnlockPayload(nVersion=%d,index=%d,fee=%d.%08d,requestedHeight=%d,quorumHash=%d,quorumSig=%s",
     203           0 :             nVersion, index, fee / COIN, fee % COIN, requestedHeight, quorumHash.GetHex(), quorumSig.ToString().c_str());
     204           0 : }

Generated by: LCOV version 1.16