ephemeral_policy.cpp raw

   1  // Copyright (c) 2024-present The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #include <consensus/validation.h>
   6  #include <policy/ephemeral_policy.h>
   7  #include <policy/feerate.h>
   8  #include <policy/packages.h>
   9  #include <policy/policy.h>
  10  #include <primitives/transaction.h>
  11  #include <script/interpreter.h>
  12  #include <txmempool.h>
  13  #include <util/check.h>
  14  #include <util/hasher.h>
  15  
  16  #include <algorithm>
  17  #include <cstdint>
  18  #include <map>
  19  #include <memory>
  20  #include <unordered_set>
  21  #include <utility>
  22  #include <vector>
  23  
  24  bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state)
  25  {
  26      // We never want to give incentives to mine this transaction alone
  27      if ((base_fee != 0 || mod_fee != 0) && !GetDust(tx, dust_relay_rate).empty()) {
  28          return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "dust", "tx with dust output must be 0-fee");
  29      }
  30  
  31      return true;
  32  }
  33  
  34  bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Wtxid& out_child_wtxid)
  35  {
  36      if (!Assume(std::ranges::all_of(package, [](const auto& tx){return tx != nullptr;}))) {
  37          // Bail out of spend checks if caller gave us an invalid package
  38          return true;
  39      }
  40  
  41      std::map<Txid, CTransactionRef> map_txid_ref;
  42      for (const auto& tx : package) {
  43          map_txid_ref[tx->GetHash()] = tx;
  44      }
  45  
  46      for (const auto& tx : package) {
  47          std::unordered_set<Txid, SaltedTxidHasher> processed_parent_set;
  48          std::unordered_set<COutPoint, SaltedOutpointHasher> unspent_parent_dust;
  49  
  50          for (const auto& tx_input : tx->vin) {
  51              const Txid& parent_txid{tx_input.prevout.hash};
  52              // Skip parents we've already checked dust for
  53              if (processed_parent_set.contains(parent_txid)) continue;
  54  
  55              // We look for an in-package or in-mempool dependency
  56              CTransactionRef parent_ref = nullptr;
  57              if (auto it = map_txid_ref.find(parent_txid); it != map_txid_ref.end()) {
  58                  parent_ref = it->second;
  59              } else {
  60                  parent_ref = tx_pool.get(parent_txid);
  61              }
  62  
  63              // Check for dust on parents.  Unspendable (OP_RETURN) outputs and
  64              // confidential outputs (witness v4/33, value hidden in a
  65              // commitment) are exempt: they are not "dust to be spent".
  66              if (parent_ref) {
  67                  for (uint32_t out_index = 0; out_index < parent_ref->vout.size(); out_index++) {
  68                      const auto& tx_output = parent_ref->vout[out_index];
  69                      int witver;
  70                      std::vector<uint8_t> witprog;
  71                      const bool is_ct = tx_output.scriptPubKey.IsWitnessProgram(witver, witprog) &&
  72                                         witver == 4 && witprog.size() == WITNESS_V4_BPCT_SIZE;
  73                      if (IsDust(tx_output, dust_relay_rate) &&
  74                          !tx_output.scriptPubKey.IsUnspendable() && !is_ct) {
  75                          unspent_parent_dust.insert(COutPoint(parent_txid, out_index));
  76                      }
  77                  }
  78              }
  79  
  80              processed_parent_set.insert(parent_txid);
  81          }
  82  
  83          if (unspent_parent_dust.empty()) {
  84              continue;
  85          }
  86  
  87          // Now that we have gathered parents' dust, make sure it's spent
  88          // by the child
  89          for (const auto& tx_input : tx->vin) {
  90              unspent_parent_dust.erase(tx_input.prevout);
  91          }
  92  
  93          if (!unspent_parent_dust.empty()) {
  94              const Txid& out_child_txid = tx->GetHash();
  95              out_child_wtxid = tx->GetWitnessHash();
  96              out_child_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "missing-ephemeral-spends",
  97                                  strprintf("tx %s (wtxid=%s) did not spend parent's ephemeral dust", out_child_txid.ToString(), out_child_wtxid.ToString()));
  98              return false;
  99          }
 100      }
 101  
 102      return true;
 103  }
 104