Skip to content

Commit f7144b2

Browse files
committed
Merge bitcoin/bitcoin#31279: policy: ephemeral dust followups
466e4df assert_mempool_contents: assert not duplicates expected (Greg Sanders) ea5db2f functional: only generate required blocks for test (Greg Sanders) d033acb fuzz: package_eval: let fuzzer run out input in main tx creation loop (Greg Sanders) ba35a57 CheckEphemeralSpends: return boolean, and set child state and txid outparams (Greg Sanders) cf0cee1 func: add note about lack of 1P1C propagation in tree submitpackage (Greg Sanders) 8424290 unit test: ephemeral_tests is using a dust relay rate, not minrelay (Greg Sanders) d9cfa5f CheckEphemeralSpends: no need to iterate inputs if no parent dust (Greg Sanders) 87b26e3 func: rename test_free_relay to test_no_minrelay_fee (Greg Sanders) e5709a4 func: slight elaboration on submitpackage restriction (Greg Sanders) 08e969b RPC: only enforce dust rules on priority when standardness active (Greg Sanders) ca050d1 unit test: adapt to changing MAX_DUST_OUTPUTS_PER_TX (Greg Sanders) 7c34901 fuzz: package_eval: move last_tx inside txn ctor (Greg Sanders) 445eaed fuzz: use optional status instead of should_rbf_eph_spend (Greg Sanders) 4dfdf61 fuzz: remove unused TransactionsDelta validation interface (Greg Sanders) 09ce926 func: cleanup reorg test comment (Greg Sanders) 768a0c1 func: cleanup test_dustrelay comments (Greg Sanders) bedca1c fuzz: Directly place transactions in vector (Greg Sanders) c041ad6 fuzz: explain package eval coin tracking better (Greg Sanders) bc0d98e fuzz: remove dangling reference to GetEntry (Greg Sanders) 15b6cbf unit test: make dust index less magical (Greg Sanders) 5fbcfd1 unit test: assert txid returned on CheckEphemeralSpends failures (Greg Sanders) ef94d84 bench: remove unnecessary CMTxn constructors (Greg Sanders) c5c10fd ephemeral policy doxygen cleanup (Greg Sanders) dd9044b ephemeral policy: IWYU (Greg Sanders) c6859ce Move+rename GetDustIndexes -> GetDust (Greg Sanders) 62016b3 Use std::ranges for ephemeral policy checks (Greg Sanders) 3ed930a Have HasDust and PreCheckValidEphemeralTx take CTransaction (Greg Sanders) 04a614b Rename CheckValidEphemeralTx to PreCheckEphemeralTx (Greg Sanders) cbf1a47 CheckEphemeralSpends: only compute txid of tx when needed (Greg Sanders) Pull request description: Follow-up to bitcoin/bitcoin#30239 Here are the parent PR's comments that should be addressed by this PR: https://github.com/bitcoin/bitcoin/pull/30239/files#r1834529646 https://github.com/bitcoin/bitcoin/pull/30239/files#r1831247308 https://github.com/bitcoin/bitcoin/pull/30239/files#r1832622481 https://github.com/bitcoin/bitcoin/pull/30239/files#r1831195216 bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) bitcoin/bitcoin#30239 (comment) ACKs for top commit: naumenkogs: ACK 466e4df hodlinator: ACK 466e4df theStack: lgtm ACK 466e4df glozow: utACK 466e4df Tree-SHA512: 89106f695755c238b84e0996b89446c0733e10a94c867f656d516d26697d2efe38dfc332188b8589a0a26a3d2bd2c88c6ab70c108e187ce5bfcb91bbf3fb0391
2 parents 2638fdb + 466e4df commit f7144b2

15 files changed

+210
-146
lines changed

src/bench/mempool_ephemeral_spends.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench)
4444
}
4545

4646
// Tx with many outputs
47-
CMutableTransaction tx1 = CMutableTransaction();
47+
CMutableTransaction tx1;
4848
tx1.vin.resize(1);
4949
tx1.vout.resize(number_outputs);
5050
for (size_t i = 0; i < tx1.vout.size(); i++) {
@@ -56,7 +56,7 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench)
5656
const auto& parent_txid = tx1.GetHash();
5757

5858
// Spends all outputs of tx1, other details don't matter
59-
CMutableTransaction tx2 = CMutableTransaction();
59+
CMutableTransaction tx2;
6060
tx2.vin.resize(tx1.vout.size());
6161
for (size_t i = 0; i < tx2.vin.size(); i++) {
6262
tx2.vin[0].prevout.hash = parent_txid;
@@ -74,9 +74,12 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench)
7474

7575
uint32_t iteration{0};
7676

77+
TxValidationState dummy_state;
78+
Txid dummy_txid;
79+
7780
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {
7881

79-
CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool);
82+
CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool, dummy_state, dummy_txid);
8083
iteration++;
8184
});
8285
}

src/policy/ephemeral_policy.cpp

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,39 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <consensus/validation.h>
56
#include <policy/ephemeral_policy.h>
7+
#include <policy/feerate.h>
8+
#include <policy/packages.h>
69
#include <policy/policy.h>
10+
#include <primitives/transaction.h>
11+
#include <txmempool.h>
12+
#include <util/check.h>
13+
#include <util/hasher.h>
714

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-
}
15+
#include <algorithm>
16+
#include <cstdint>
17+
#include <map>
18+
#include <memory>
19+
#include <unordered_set>
20+
#include <utility>
21+
#include <vector>
1222

13-
bool CheckValidEphemeralTx(const CTransactionRef& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state)
23+
bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state)
1424
{
1525
// We never want to give incentives to mine this transaction alone
16-
if ((base_fee != 0 || mod_fee != 0) && HasDust(tx, dust_relay_rate)) {
26+
if ((base_fee != 0 || mod_fee != 0) && !GetDust(tx, dust_relay_rate).empty()) {
1727
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "dust", "tx with dust output must be 0-fee");
1828
}
1929

2030
return true;
2131
}
2232

23-
std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool)
33+
bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Txid& out_child_txid)
2434
{
25-
if (!Assume(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}))) {
35+
if (!Assume(std::ranges::all_of(package, [](const auto& tx){return tx != nullptr;}))) {
2636
// Bail out of spend checks if caller gave us an invalid package
27-
return std::nullopt;
37+
return true;
2838
}
2939

3040
std::map<Txid, CTransactionRef> map_txid_ref;
@@ -33,7 +43,6 @@ std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_r
3343
}
3444

3545
for (const auto& tx : package) {
36-
Txid txid = tx->GetHash();
3746
std::unordered_set<Txid, SaltedTxidHasher> processed_parent_set;
3847
std::unordered_set<COutPoint, SaltedOutpointHasher> unspent_parent_dust;
3948

@@ -63,16 +72,23 @@ std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_r
6372
processed_parent_set.insert(parent_txid);
6473
}
6574

75+
if (unspent_parent_dust.empty()) {
76+
continue;
77+
}
78+
6679
// Now that we have gathered parents' dust, make sure it's spent
6780
// by the child
6881
for (const auto& tx_input : tx->vin) {
6982
unspent_parent_dust.erase(tx_input.prevout);
7083
}
7184

7285
if (!unspent_parent_dust.empty()) {
73-
return txid;
86+
out_child_txid = tx->GetHash();
87+
out_child_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "missing-ephemeral-spends",
88+
strprintf("tx %s did not spend parent's ephemeral dust", out_child_txid.ToString()));
89+
return false;
7490
}
7591
}
7692

77-
return std::nullopt;
93+
return true;
7894
}

src/policy/ephemeral_policy.h

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@
55
#ifndef BITCOIN_POLICY_EPHEMERAL_POLICY_H
66
#define BITCOIN_POLICY_EPHEMERAL_POLICY_H
77

8+
#include <consensus/amount.h>
89
#include <policy/packages.h>
9-
#include <policy/policy.h>
1010
#include <primitives/transaction.h>
11-
#include <txmempool.h>
11+
12+
#include <optional>
13+
14+
class CFeeRate;
15+
class CTxMemPool;
16+
class TxValidationState;
1217

1318
/** These utility functions ensure that ephemeral dust is safely
1419
* created and spent without unduly risking them entering the utxo
1520
* set.
1621
1722
* This is ensured by requiring:
18-
* - CheckValidEphemeralTx checks are respected
23+
* - PreCheckEphemeralTx checks are respected
1924
* - The parent has no child (and 0-fee as implied above to disincentivize mining)
2025
* - OR the parent transaction has exactly one child, and the dust is spent by that child
2126
*
@@ -34,22 +39,20 @@
3439
* are the only way to bring fees.
3540
*/
3641

37-
/** Returns true if transaction contains dust */
38-
bool HasDust(const CTransactionRef& tx, CFeeRate dust_relay_rate);
39-
4042
/* All the following checks are only called if standardness rules are being applied. */
4143

4244
/** Must be called for each transaction once transaction fees are known.
4345
* Does context-less checks about a single transaction.
44-
* Returns false if the fee is non-zero and dust exists, populating state. True otherwise.
46+
* @returns false if the fee is non-zero and dust exists, populating state. True otherwise.
4547
*/
46-
bool CheckValidEphemeralTx(const CTransactionRef& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state);
48+
bool PreCheckEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_rate, CAmount base_fee, CAmount mod_fee, TxValidationState& state);
4749

4850
/** Must be called for each transaction(package) if any dust is in the package.
4951
* Checks that each transaction's parents have their dust spent by the child,
5052
* where parents are either in the mempool or in the package itself.
51-
* The function returns std::nullopt if all dust is properly spent, or the txid of the violating child spend.
53+
* Sets out_child_state and out_child_txid on failure.
54+
* @returns true if all dust is properly spent.
5255
*/
53-
std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool);
56+
bool CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool, TxValidationState& out_child_state, Txid& out_child_txid);
5457

5558
#endif // BITCOIN_POLICY_EPHEMERAL_POLICY_H

src/policy/policy.cpp

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,15 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
6767
return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
6868
}
6969

70+
std::vector<uint32_t> GetDust(const CTransaction& tx, CFeeRate dust_relay_rate)
71+
{
72+
std::vector<uint32_t> dust_outputs;
73+
for (uint32_t i{0}; i < tx.vout.size(); ++i) {
74+
if (IsDust(tx.vout[i], dust_relay_rate)) dust_outputs.push_back(i);
75+
}
76+
return dust_outputs;
77+
}
78+
7079
bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
7180
{
7281
std::vector<std::vector<unsigned char> > vSolutions;
@@ -129,7 +138,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
129138
}
130139

131140
unsigned int nDataOut = 0;
132-
unsigned int num_dust_outputs{0};
133141
TxoutType whichType;
134142
for (const CTxOut& txout : tx.vout) {
135143
if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
@@ -142,13 +150,11 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
142150
else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
143151
reason = "bare-multisig";
144152
return false;
145-
} else if (IsDust(txout, dust_relay_fee)) {
146-
num_dust_outputs++;
147153
}
148154
}
149155

150156
// Only MAX_DUST_OUTPUTS_PER_TX dust is permitted(on otherwise valid ephemeral dust)
151-
if (num_dust_outputs > MAX_DUST_OUTPUTS_PER_TX) {
157+
if (GetDust(tx, dust_relay_fee).size() > MAX_DUST_OUTPUTS_PER_TX) {
152158
reason = "dust";
153159
return false;
154160
}

src/policy/policy.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);
131131

132132
bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType);
133133

134+
/** Get the vout index numbers of all dust outputs */
135+
std::vector<uint32_t> GetDust(const CTransaction& tx, CFeeRate dust_relay_rate);
134136

135137
// Changing the default transaction version requires a two step process: first
136138
// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later

src/rpc/mining.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ static RPCHelpMan prioritisetransaction()
496496

497497
// Non-0 fee dust transactions are not allowed for entry, and modification not allowed afterwards
498498
const auto& tx = mempool.get(hash);
499-
if (tx && HasDust(tx, mempool.m_opts.dust_relay_feerate)) {
499+
if (mempool.m_opts.require_standard && tx && !GetDust(*tx, mempool.m_opts.dust_relay_feerate).empty()) {
500500
throw JSONRPCError(RPC_INVALID_PARAMETER, "Priority is not supported for transactions with dust outputs.");
501501
}
502502

0 commit comments

Comments
 (0)