|
| 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 <policy/ephemeral_policy.h> |
| 6 | +#include <policy/policy.h> |
| 7 | + |
| 8 | +bool HasDust(const CTransactionRef& tx, CFeeRate dust_relay_rate) |
| 9 | +{ |
| 10 | + return std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& output) { return IsDust(output, dust_relay_rate); }); |
| 11 | +} |
| 12 | + |
| 13 | +bool CheckValidEphemeralTx(const CTransactionRef& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state) |
| 14 | +{ |
| 15 | + // We never want to give incentives to mine this transaction alone |
| 16 | + if ((base_fee != 0 || mod_fee != 0) && HasDust(tx, dust_relay_rate)) { |
| 17 | + return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "dust", "tx with dust output must be 0-fee"); |
| 18 | + } |
| 19 | + |
| 20 | + return true; |
| 21 | +} |
| 22 | + |
| 23 | +std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool) |
| 24 | +{ |
| 25 | + if (!Assume(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}))) { |
| 26 | + // Bail out of spend checks if caller gave us an invalid package |
| 27 | + return std::nullopt; |
| 28 | + } |
| 29 | + |
| 30 | + std::map<Txid, CTransactionRef> map_txid_ref; |
| 31 | + for (const auto& tx : package) { |
| 32 | + map_txid_ref[tx->GetHash()] = tx; |
| 33 | + } |
| 34 | + |
| 35 | + for (const auto& tx : package) { |
| 36 | + Txid txid = tx->GetHash(); |
| 37 | + std::unordered_set<Txid, SaltedTxidHasher> processed_parent_set; |
| 38 | + std::unordered_set<COutPoint, SaltedOutpointHasher> unspent_parent_dust; |
| 39 | + |
| 40 | + for (const auto& tx_input : tx->vin) { |
| 41 | + const Txid& parent_txid{tx_input.prevout.hash}; |
| 42 | + // Skip parents we've already checked dust for |
| 43 | + if (processed_parent_set.contains(parent_txid)) continue; |
| 44 | + |
| 45 | + // We look for an in-package or in-mempool dependency |
| 46 | + CTransactionRef parent_ref = nullptr; |
| 47 | + if (auto it = map_txid_ref.find(parent_txid); it != map_txid_ref.end()) { |
| 48 | + parent_ref = it->second; |
| 49 | + } else { |
| 50 | + parent_ref = tx_pool.get(parent_txid); |
| 51 | + } |
| 52 | + |
| 53 | + // Check for dust on parents |
| 54 | + if (parent_ref) { |
| 55 | + for (uint32_t out_index = 0; out_index < parent_ref->vout.size(); out_index++) { |
| 56 | + const auto& tx_output = parent_ref->vout[out_index]; |
| 57 | + if (IsDust(tx_output, dust_relay_rate)) { |
| 58 | + unspent_parent_dust.insert(COutPoint(parent_txid, out_index)); |
| 59 | + } |
| 60 | + } |
| 61 | + } |
| 62 | + |
| 63 | + processed_parent_set.insert(parent_txid); |
| 64 | + } |
| 65 | + |
| 66 | + // Now that we have gathered parents' dust, make sure it's spent |
| 67 | + // by the child |
| 68 | + for (const auto& tx_input : tx->vin) { |
| 69 | + unspent_parent_dust.erase(tx_input.prevout); |
| 70 | + } |
| 71 | + |
| 72 | + if (!unspent_parent_dust.empty()) { |
| 73 | + return txid; |
| 74 | + } |
| 75 | + } |
| 76 | + |
| 77 | + return std::nullopt; |
| 78 | +} |
0 commit comments