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