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