forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
policy: Allow dust in transactions, spent in-mempool
Also known as Ephemeral Dust. We try to ensure that dust is spent in blocks by requiring: - ephemeral dust tx is 0-fee child bringing fees) - ephemeral dust tx only has one dust output - the output is spent by a single child transaction 0-fee requirement means there is no incentive to mine a transaction which doesn't have a child bringing its own fees for the transaction package.
- Loading branch information
1 parent
8754d05
commit 092c1f4
Showing
5 changed files
with
246 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// Copyright (c) 2024-present The Bitcoin Core developers | ||
// Distributed under the MIT software license, see the accompanying | ||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
#include<policy/ephemeral_policy.h> | ||
#include<policy/policy.h> | ||
|
||
bool CheckValidEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_fee, CAmount txfee, TxValidationState& state) | ||
{ | ||
bool has_dust = false; | ||
for (const CTxOut& txout : tx.vout) { | ||
if (IsDust(txout, dust_relay_fee)) { | ||
// We only allow a single dusty output | ||
if (has_dust) { | ||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "dust"); | ||
} | ||
has_dust = true; | ||
} | ||
} | ||
|
||
// No dust; it's complete standard already | ||
if (!has_dust) return true; | ||
|
||
// We never want to give incentives to mine this alone | ||
if (txfee != 0) { | ||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "dust"); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate) | ||
{ | ||
// Package is topologically sorted, and PreChecks ensures that | ||
// there is up to one dust output per tx. | ||
|
||
assert(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;})); | ||
|
||
// Running tally of unspent dust | ||
std::unordered_set<COutPoint, SaltedOutpointHasher> unspent_dust; | ||
|
||
// If a parent tx has dust, we have to check for the spend | ||
// Single dust per tx possible | ||
std::map<Txid, uint32_t> map_tx_dust; | ||
|
||
for (const auto& tx : package) { | ||
std::unordered_set<Txid, SaltedTxidHasher> child_unspent_dust; | ||
for (const auto& tx_input : tx->vin) { | ||
// Parent tx had dust, child MUST be sweeping it | ||
// if it's spending any output from parent | ||
if (map_tx_dust.contains(tx_input.prevout.hash)) { | ||
child_unspent_dust.insert(tx_input.prevout.hash); | ||
} | ||
} | ||
|
||
// Now that we've built a list of parent txids | ||
// that have dust, make sure that all parent's | ||
// dust are swept by this same tx | ||
for (const auto& tx_input : tx->vin) { | ||
const auto& prevout = tx_input.prevout; | ||
// Parent tx had dust, child MUST be sweeping it | ||
// if it's spending any output from parent | ||
if (map_tx_dust.contains(prevout.hash) && | ||
map_tx_dust[prevout.hash] == prevout.n) { | ||
child_unspent_dust.erase(prevout.hash); | ||
} | ||
|
||
// We want to detect dangling dust too | ||
unspent_dust.erase(tx_input.prevout); | ||
} | ||
|
||
if (!child_unspent_dust.empty()) { | ||
return tx->GetHash(); | ||
} | ||
|
||
// Process new dust | ||
for (uint32_t i=0; i<tx->vout.size(); i++) { | ||
if (IsDust(tx->vout[i], dust_relay_rate)) { | ||
// CheckValidEphemeralTx should disallow multiples | ||
Assume(!map_tx_dust.contains(tx->GetHash())); | ||
map_tx_dust[tx->GetHash()] = i; | ||
unspent_dust.insert(COutPoint(tx->GetHash(), i)); | ||
} | ||
} | ||
|
||
} | ||
|
||
if (!unspent_dust.empty()) { | ||
return unspent_dust.begin()->hash; | ||
} | ||
|
||
return std::nullopt; | ||
} | ||
|
||
std::optional<std::string> CheckEphemeralSpends(const CTransactionRef& ptx, | ||
const CTxMemPool::setEntries& ancestors, | ||
CFeeRate dust_relay_feerate) | ||
{ | ||
std::unordered_set<COutPoint, SaltedOutpointHasher> unspent_dust; | ||
|
||
std::unordered_set<Txid, SaltedTxidHasher> parents; | ||
for (const auto& tx_input : ptx->vin) { | ||
parents.insert(tx_input.prevout.hash); | ||
} | ||
|
||
for (const auto& entry : ancestors) { | ||
const auto& tx = entry->GetTx(); | ||
// Only deal with direct parents | ||
if (parents.count(tx.GetHash()) == 0) continue; | ||
for (uint32_t i=0; i<tx.vout.size(); i++) { | ||
if (IsDust(tx.vout[i], dust_relay_feerate)) { | ||
unspent_dust.insert(COutPoint(tx.GetHash(), i)); | ||
} | ||
} | ||
} | ||
|
||
for (const auto& input : ptx->vin) { | ||
unspent_dust.erase(input.prevout); | ||
} | ||
|
||
if (!unspent_dust.empty()) { | ||
return strprintf("tx does not spend parent ephemeral dust"); | ||
} | ||
|
||
return std::nullopt; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) 2024-present The Bitcoin Core developers | ||
// Distributed under the MIT software license, see the accompanying | ||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
||
#ifndef BITCOIN_POLICY_EPHEMERAL_POLICY_H | ||
#define BITCOIN_POLICY_EPHEMERAL_POLICY_H | ||
|
||
#include <policy/packages.h> | ||
#include <policy/policy.h> | ||
#include <primitives/transaction.h> | ||
#include <txmempool.h> | ||
|
||
/** These utility functions ensure that ephemeral dust is safely | ||
* created and spent without risking them entering the utxo | ||
* set. | ||
* This is ensured by requiring: | ||
* - CheckValidEphemeralTx 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 | ||
* | ||
* Imagine three transactions: | ||
* TxA, 0-fee with two outputs, one non-dust, one dust | ||
* TxB, spends TxA's non-dust | ||
* TxC, spends TxA's dust | ||
* | ||
* All the dust is spent if TxA+TxB+TxC is accepted, but the mining template may just pick | ||
* up TxA+TxB rather than the three "legal configurations: | ||
* 1) None | ||
* 2) TxA+TxB+TxC | ||
* 3) TxA+TxC | ||
* By requiring the child transaction to sweep any dust from the parent txn, we ensure that | ||
* there is a single child only, and this child is the only transaction possible for | ||
* bringing fees, or itself being spent by another child, and so on. | ||
*/ | ||
|
||
/** Does context-less checks about a single transaction. | ||
* If it has relay dust, it returns false if any are true: | ||
* - tx has non-0 fee | ||
- tx has more than one dust output | ||
* and sets relevant invalid state. | ||
* Otherwise it returns true. | ||
*/ | ||
bool CheckValidEphemeralTx(const CTransaction& tx, CFeeRate dust_relay_fee, CAmount txfee, TxValidationState& state); | ||
|
||
/** Checks that all dust in a package ends up spent by an only-child. Assumes package is well-formed and sorted. | ||
* The function returns std::nullopt if all dust is properly spent, or the txid of a violated ephemeral transaction. | ||
*/ | ||
std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate); | ||
|
||
/** Checks that individual transactions' parents have all their dust spent by this only-child transaction. | ||
*/ | ||
std::optional<std::string> CheckEphemeralSpends(const CTransactionRef& ptx, | ||
const CTxMemPool::setEntries& ancestors, | ||
CFeeRate dust_relay_feerate); | ||
|
||
#endif // BITCOIN_POLICY_EPHEMERAL_POLICY_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters