-
Notifications
You must be signed in to change notification settings - Fork 267
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge bitcoin/bitcoin#30239: Ephemeral Dust
5c2e291 bench: Add basic CheckEphemeralSpends benchmark (Greg Sanders) 3f6559f Add release note for ephemeral dust (Greg Sanders) 71a6ab4 test: unit test for CheckEphemeralSpends (Greg Sanders) 21d28b2 fuzz: add ephemeral_package_eval harness (Greg Sanders) 127719f test: Add CheckMempoolEphemeralInvariants (Greg Sanders) e2e30e8 functional test: Add ephemeral dust tests (Greg Sanders) 4e68f90 rpc: disallow in-mempool prioritisation of dusty tx (Greg Sanders) e1d3e81 policy: Allow dust in transactions, spent in-mempool (Greg Sanders) 04b2714 functional test: Add new -dustrelayfee=0 test case (Greg Sanders) Pull request description: A replacement for bitcoin/bitcoin#29001 Now that we have 1P1C relay, TRUC transactions and sibling eviction, it makes sense to retarget this feature more narrowly by not introducing a new output type, and simple focusing on the feature of allowing temporary dust in the mempool. Users of this can immediately use dust outputs as: 1. Single keyed anchor (can be shared by multiple parties) 2. Single unkeyed anchor, ala P2A Which is useful when the parent transaction cannot have fees for technical or accounting reasons. What I'm calling "keyed" anchors would be used anytime you don't want a third party to be able to run off with the utxo. As a motivating example, in Ark there is the concept of a "forfeit transaction" which spends a "connector output". The connector output would ideally be 0-value, but you would not want that utxo spend by anyone, because this would cause financial loss for the coordinator of the service: https://arkdev.info/docs/learn/concepts#forfeit-transaction Note that this specific use-case likely doesn't work as it involves a tree of dust, but the connector idea in general demonstrates how it could be used. Another related example is connector outputs in BitVM2: https://bitvm.org/bitvm2.html . Note that non-TRUC usage will be impractical unless the minrelay requirement on individual transactions are dropped in general, which should happen post-cluster mempool. Lightning Network intends to use this feature post-29.0 if available: lightning/bolts#1171 (comment) It's also useful for Ark, ln-symmetry, spacechains, Timeout Trees, and other constructs with large presigned trees or other large-N party smart contracts. ACKs for top commit: glozow: reACK 5c2e291 via range-diff. Nothing but a rebase and removing the conflict. theStack: re-ACK 5c2e291 Tree-SHA512: 88e6a6b3b91dc425de47ccd68b7668c8e98c5683712e892c588f79ad639ae95c665e2d5563dd5e5797983e7542cbd1d4353bc90a7298d45a1843b05a417f09f5
- Loading branch information
Showing
20 changed files
with
1,218 additions
and
3 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
P2P and network changes | ||
----------------------- | ||
|
||
Ephemeral dust is a new concept that allows a single | ||
dust output in a transaction, provided the transaction | ||
is zero fee. In order to spend any unconfirmed outputs | ||
from this transaction, the spender must also spend | ||
this dust in addition to any other outputs. | ||
|
||
In other words, this type of transaction | ||
should be created in a transaction package where | ||
the dust is both created and spent simultaneously. |
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
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.