forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ephemeral Dust #69
Merged
ajtowns
merged 8 commits into
bitcoin-inquisition:28.x
from
instagibbs:2024-11-eph_dust_inq_backport
Nov 19, 2024
Merged
Ephemeral Dust #69
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
e29da81
functional test: Add new -dustrelayfee=0 test case
instagibbs c22b317
policy: Allow dust in transactions, spent in-mempool
instagibbs 298a0a0
rpc: disallow in-mempool prioritisation of dusty tx
instagibbs 315b8e1
functional test: Add ephemeral dust tests
instagibbs a72914e
test: Add CheckMempoolEphemeralInvariants
instagibbs 37961f2
fuzz: add ephemeral_package_eval harness
instagibbs 386c599
test: unit test for CheckEphemeralSpends
instagibbs efa9ff7
bench: Add basic CheckEphemeralSpends benchmark
instagibbs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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,83 @@ | ||
// Copyright (c) 2011-2022 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 <bench/bench.h> | ||
#include <consensus/amount.h> | ||
#include <kernel/cs_main.h> | ||
#include <policy/ephemeral_policy.h> | ||
#include <policy/policy.h> | ||
#include <primitives/transaction.h> | ||
#include <script/script.h> | ||
#include <sync.h> | ||
#include <test/util/setup_common.h> | ||
#include <txmempool.h> | ||
#include <util/check.h> | ||
|
||
#include <cstdint> | ||
#include <memory> | ||
#include <vector> | ||
|
||
|
||
static void AddTx(const CTransactionRef& tx, CTxMemPool& pool) EXCLUSIVE_LOCKS_REQUIRED(cs_main, pool.cs) | ||
{ | ||
int64_t nTime{0}; | ||
unsigned int nHeight{1}; | ||
uint64_t sequence{0}; | ||
bool spendsCoinbase{false}; | ||
unsigned int sigOpCost{4}; | ||
uint64_t fee{0}; | ||
LockPoints lp; | ||
pool.addUnchecked(CTxMemPoolEntry( | ||
tx, fee, nTime, nHeight, sequence, | ||
spendsCoinbase, sigOpCost, lp)); | ||
} | ||
|
||
static void MempoolCheckEphemeralSpends(benchmark::Bench& bench) | ||
{ | ||
const auto testing_setup = MakeNoLogFileContext<const TestingSetup>(); | ||
|
||
int number_outputs{1000}; | ||
if (bench.complexityN() > 1) { | ||
number_outputs = static_cast<int>(bench.complexityN()); | ||
} | ||
|
||
// Tx with many outputs | ||
CMutableTransaction tx1 = CMutableTransaction(); | ||
tx1.vin.resize(1); | ||
tx1.vout.resize(number_outputs); | ||
for (size_t i = 0; i < tx1.vout.size(); i++) { | ||
tx1.vout[i].scriptPubKey = CScript(); | ||
// Each output progressively larger | ||
tx1.vout[i].nValue = i * CENT; | ||
} | ||
|
||
const auto& parent_txid = tx1.GetHash(); | ||
|
||
// Spends all outputs of tx1, other details don't matter | ||
CMutableTransaction tx2 = CMutableTransaction(); | ||
tx2.vin.resize(tx1.vout.size()); | ||
for (size_t i = 0; i < tx2.vin.size(); i++) { | ||
tx2.vin[0].prevout.hash = parent_txid; | ||
tx2.vin[0].prevout.n = i; | ||
} | ||
tx2.vout.resize(1); | ||
|
||
CTxMemPool& pool = *Assert(testing_setup->m_node.mempool); | ||
LOCK2(cs_main, pool.cs); | ||
// Create transaction references outside the "hot loop" | ||
const CTransactionRef tx1_r{MakeTransactionRef(tx1)}; | ||
const CTransactionRef tx2_r{MakeTransactionRef(tx2)}; | ||
|
||
AddTx(tx1_r, pool); | ||
|
||
uint32_t iteration{0}; | ||
|
||
bench.run([&]() NO_THREAD_SAFETY_ANALYSIS { | ||
|
||
CheckEphemeralSpends({tx2_r}, /*dust_relay_rate=*/CFeeRate(iteration * COIN / 10), pool); | ||
iteration++; | ||
}); | ||
} | ||
|
||
BENCHMARK(MempoolCheckEphemeralSpends, benchmark::PriorityLevel::HIGH); |
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,78 @@ | ||
// 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 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); }); | ||
} | ||
|
||
bool CheckValidEphemeralTx(const CTransactionRef& 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)) { | ||
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) | ||
{ | ||
if (!Assume(std::all_of(package.cbegin(), package.cend(), [](const auto& tx){return tx != nullptr;}))) { | ||
// Bail out of spend checks if caller gave us an invalid package | ||
return std::nullopt; | ||
} | ||
|
||
std::map<Txid, CTransactionRef> map_txid_ref; | ||
for (const auto& tx : package) { | ||
map_txid_ref[tx->GetHash()] = tx; | ||
} | ||
|
||
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; | ||
|
||
for (const auto& tx_input : tx->vin) { | ||
const Txid& parent_txid{tx_input.prevout.hash}; | ||
// Skip parents we've already checked dust for | ||
if (processed_parent_set.contains(parent_txid)) continue; | ||
|
||
// We look for an in-package or in-mempool dependency | ||
CTransactionRef parent_ref = nullptr; | ||
if (auto it = map_txid_ref.find(parent_txid); it != map_txid_ref.end()) { | ||
parent_ref = it->second; | ||
} else { | ||
parent_ref = tx_pool.get(parent_txid); | ||
} | ||
|
||
// Check for dust on parents | ||
if (parent_ref) { | ||
for (uint32_t out_index = 0; out_index < parent_ref->vout.size(); out_index++) { | ||
const auto& tx_output = parent_ref->vout[out_index]; | ||
if (IsDust(tx_output, dust_relay_rate)) { | ||
unspent_parent_dust.insert(COutPoint(parent_txid, out_index)); | ||
} | ||
} | ||
} | ||
|
||
processed_parent_set.insert(parent_txid); | ||
} | ||
|
||
// 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; | ||
} | ||
} | ||
|
||
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,55 @@ | ||
// 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 unduly 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, or the child's descendants, | ||
* 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. | ||
*/ | ||
bool CheckValidEphemeralTx(const CTransactionRef& 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. | ||
*/ | ||
std::optional<Txid> CheckEphemeralSpends(const Package& package, CFeeRate dust_relay_rate, const CTxMemPool& tx_pool); | ||
|
||
#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
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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Skimming this, two thoughts come to mind:
If the idea is that all dust created by a tx should be swept by the immediate next tx (so that the package can't be split and leave dust in the utxo set), you could move the empty() check to the middle of the main loop. Reporting the txid of the transaction that created the dust seems more logical than the one that should have spent it but didn't to me, fwiw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Er, dunno, I chose the name 2 years ago I think. Could amend the name change I have in the follow-up!
There are two interlocking rules:
The snippet above is different in at least a couple of ways, the more important being that it would not detect the second case where there are in-mempool ancestors to the package.
p2p relay questions aside, it's not really the case. Batched CPFP is possible for one, and if/when
submitpackage
is generalized further, could allow arbitrary chains of transactions with single dust outputs.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I was handwaving that any in-mempool ancestors were already part of the package.
Mostly, I think I was misled by:
(missing close quote after
"legal
btw)But we don't consider (2) legal, and can't because our current mining algorithm isn't smart enough to be able to call/satisfy
CheckEphemeralSpends()
. Cluster mempool could conceivably be smart enough to preserve that property, though, by ensuring that txs are never split from a chunk if it would cause a function like this to be unhappy... (In that case, the chunk would likely be [TxA, TxC, TxB] anyway...) In any event, relaying TxB prior to TxA being confirmed probably increases bad incentives for leaving dust in the utxo set anyway...(This rationale would be better in a BIP than in code comments... /grumble)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right this could have been phrased better, "legal" here means a block template may be created where there is no dust entering into the utxo set. ie If TxA is mined, we want TxC mined, not that the implementation would allow that config to happen currently.
I noodled on stuff like this for a while half a year ago and came away unsatisfied. I think it's hard to get something more advanced that acts natural and non-pinny from a wallet perspective but I don't have notes in front of me.
Enjoy the aborted BIP attempt bitcoin/bips#1524