ephemeral_policy.cpp raw
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 }
96