Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cbf1a47
CheckEphemeralSpends: only compute txid of tx when needed
instagibbs Nov 12, 2024
04a614b
Rename CheckValidEphemeralTx to PreCheckEphemeralTx
instagibbs Nov 12, 2024
3ed930a
Have HasDust and PreCheckValidEphemeralTx take CTransaction
instagibbs Nov 12, 2024
62016b3
Use std::ranges for ephemeral policy checks
instagibbs Nov 12, 2024
c6859ce
Move+rename GetDustIndexes -> GetDust
instagibbs Nov 12, 2024
dd9044b
ephemeral policy: IWYU
instagibbs Nov 12, 2024
c5c10fd
ephemeral policy doxygen cleanup
instagibbs Nov 12, 2024
ef94d84
bench: remove unnecessary CMTxn constructors
instagibbs Nov 12, 2024
5fbcfd1
unit test: assert txid returned on CheckEphemeralSpends failures
instagibbs Nov 12, 2024
15b6cbf
unit test: make dust index less magical
instagibbs Nov 12, 2024
bc0d98e
fuzz: remove dangling reference to GetEntry
instagibbs Nov 12, 2024
c041ad6
fuzz: explain package eval coin tracking better
instagibbs Nov 12, 2024
bedca1c
fuzz: Directly place transactions in vector
instagibbs Nov 12, 2024
768a0c1
func: cleanup test_dustrelay comments
instagibbs Nov 12, 2024
09ce926
func: cleanup reorg test comment
instagibbs Nov 12, 2024
4dfdf61
fuzz: remove unused TransactionsDelta validation interface
instagibbs Nov 12, 2024
445eaed
fuzz: use optional status instead of should_rbf_eph_spend
instagibbs Nov 12, 2024
7c34901
fuzz: package_eval: move last_tx inside txn ctor
instagibbs Nov 12, 2024
ca050d1
unit test: adapt to changing MAX_DUST_OUTPUTS_PER_TX
instagibbs Nov 12, 2024
08e969b
RPC: only enforce dust rules on priority when standardness active
instagibbs Nov 12, 2024
e5709a4
func: slight elaboration on submitpackage restriction
instagibbs Nov 12, 2024
87b26e3
func: rename test_free_relay to test_no_minrelay_fee
instagibbs Nov 12, 2024
d9cfa5f
CheckEphemeralSpends: no need to iterate inputs if no parent dust
instagibbs Nov 12, 2024
8424290
unit test: ephemeral_tests is using a dust relay rate, not minrelay
instagibbs Nov 12, 2024
cf0cee1
func: add note about lack of 1P1C propagation in tree submitpackage
instagibbs Nov 12, 2024
ba35a57
CheckEphemeralSpends: return boolean, and set child state and txid ou…
instagibbs Nov 13, 2024
d033acb
fuzz: package_eval: let fuzzer run out input in main tx creation loop
instagibbs Nov 13, 2024
ea5db2f
functional: only generate required blocks for test
instagibbs Nov 19, 2024
466e4df
assert_mempool_contents: assert not duplicates expected
instagibbs Nov 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/bench/mempool_ephemeral_spends.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench)
}

// Tx with many outputs
CMutableTransaction tx1 = CMutableTransaction();
CMutableTransaction tx1;
tx1.vin.resize(1);
tx1.vout.resize(number_outputs);
for (size_t i = 0; i < tx1.vout.size(); i++) {
Expand All @@ -55,7 +55,7 @@ static void MempoolCheckEphemeralSpends(benchmark::Bench& bench)
const auto& parent_txid = tx1.GetHash();

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

uint32_t iteration{0};

TxValidationState dummy_state;
Txid dummy_txid;

bench.run([&]() NO_THREAD_SAFETY_ANALYSIS {

CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool);
CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool, dummy_state, dummy_txid);
iteration++;
});
}
Expand Down
40 changes: 28 additions & 12 deletions src/policy/ephemeral_policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,39 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <consensus/validation.h>
#include <policy/ephemeral_policy.h>
#include <policy/feerate.h>
#include <policy/packages.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <txmempool.h>
#include <util/check.h>
#include <util/hasher.h>

bool HasDust(const CTransactionRef& tx, CFeeRate dust_relay_rate)
{
return std::any_of(tx->vout.cbegin(), tx->vout.cend(), [&](const auto& output) { return IsDust(output, dust_relay_rate); });
}
#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <unordered_set>
#include <utility>
#include <vector>

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

return true;
}

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

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

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

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

if (unspent_parent_dust.empty()) {
continue;
}

// Now that we have gathered parents' dust, make sure it's spent
// by the child
for (const auto& tx_input : tx->vin) {
unspent_parent_dust.erase(tx_input.prevout);
}

if (!unspent_parent_dust.empty()) {
return txid;
out_child_txid = tx->GetHash();
out_child_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "missing-ephemeral-spends",
strprintf("tx %s did not spend parent's ephemeral dust", out_child_txid.ToString()));
return false;
}
}

return std::nullopt;
return true;
}
23 changes: 13 additions & 10 deletions src/policy/ephemeral_policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
#ifndef BITCOIN_POLICY_EPHEMERAL_POLICY_H
#define BITCOIN_POLICY_EPHEMERAL_POLICY_H

#include <consensus/amount.h>
#include <policy/packages.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <txmempool.h>

#include <optional>

class CFeeRate;
class CTxMemPool;
class TxValidationState;

/** These utility functions ensure that ephemeral dust is safely
* created and spent without unduly risking them entering the utxo
* set.

* This is ensured by requiring:
* - CheckValidEphemeralTx checks are respected
* - PreCheckEphemeralTx checks are respected
* - The parent has no child (and 0-fee as implied above to disincentivize mining)
* - OR the parent transaction has exactly one child, and the dust is spent by that child
*
Expand All @@ -34,22 +39,20 @@
* are the only way to bring fees.
*/

/** Returns true if transaction contains dust */
bool HasDust(const CTransactionRef& tx, CFeeRate dust_relay_rate);

/* All the following checks are only called if standardness rules are being applied. */

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

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

#endif // BITCOIN_POLICY_EPHEMERAL_POLICY_H
14 changes: 10 additions & 4 deletions src/policy/policy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
}

std::vector<uint32_t> GetDust(const CTransaction& tx, CFeeRate dust_relay_rate)
{
std::vector<uint32_t> dust_outputs;
for (uint32_t i{0}; i < tx.vout.size(); ++i) {
if (IsDust(tx.vout[i], dust_relay_rate)) dust_outputs.push_back(i);
}
return dust_outputs;
}

bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
{
std::vector<std::vector<unsigned char> > vSolutions;
Expand Down Expand Up @@ -129,7 +138,6 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
}

unsigned int nDataOut = 0;
unsigned int num_dust_outputs{0};
TxoutType whichType;
for (const CTxOut& txout : tx.vout) {
if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
Expand All @@ -142,13 +150,11 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
reason = "bare-multisig";
return false;
} else if (IsDust(txout, dust_relay_fee)) {
num_dust_outputs++;
}
}

// Only MAX_DUST_OUTPUTS_PER_TX dust is permitted(on otherwise valid ephemeral dust)
if (num_dust_outputs > MAX_DUST_OUTPUTS_PER_TX) {
if (GetDust(tx, dust_relay_fee).size() > MAX_DUST_OUTPUTS_PER_TX) {
reason = "dust";
return false;
}
Expand Down
2 changes: 2 additions & 0 deletions src/policy/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);

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

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not CTransactionRef?


// Changing the default transaction version requires a two step process: first
// adapting relay policy by bumping TX_MAX_STANDARD_VERSION, and then later
Expand Down
2 changes: 1 addition & 1 deletion src/rpc/mining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ static RPCHelpMan prioritisetransaction()

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

Expand Down
Loading
Loading