Skip to content

Commit

Permalink
Merge bitcoin/bitcoin#31371: doc, test: more ephemeral dust follow-ups
Browse files Browse the repository at this point in the history
160799d test: refactor: introduce `create_ephemeral_dust_package` helper (Sebastian Falbesoner)
61e18de doc: ephemeral policy: add missing closing double quote (Sebastian Falbesoner)

Pull request description:

  This small PR contains ephemeral dust follow-ups mentioned in #30329 that were not tackled in the first follow-up PR #31279:

  bitcoin/bitcoin#30239 (comment)
  bitcoin/bitcoin#30239 (comment)

  Happy to add more if I missed some or anyone has concrete commits to add.

ACKs for top commit:
  rkrux:
    tACK 160799d
  instagibbs:
    ACK 160799d
  tdb3:
    Code review ACK 160799d

Tree-SHA512: e9a80c6733f1e7fe9e834d81b404f6e8ef7a61fe986f61b3dcdbda1a0bc547145fc279ec02f54361df56cb4e62a6fedaa0f3991c6e084c3a703ed1b1bfbdbe4e
  • Loading branch information
glozow committed Nov 29, 2024
2 parents 7590e93 + 160799d commit dbc8ba1
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/policy/ephemeral_policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class TxValidationState;
* 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:
* up TxA+TxB rather than the three "legal configurations":
* 1) None
* 2) TxA+TxB+TxC
* 3) TxA+TxC
Expand Down
93 changes: 30 additions & 63 deletions test/functional/mempool_ephemeral_dust.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,22 @@ def add_output_to_create_multi_result(self, result, output_value=0):

result["new_utxos"].append({"txid": new_txid, "vout": len(result["tx"].vout) - 1, "value": Decimal(output_value) / COIN, "height": 0, "coinbase": False, "confirmations": 0})

def create_ephemeral_dust_package(self, *, tx_version, dust_tx_fee=0, dust_value=0, num_dust_outputs=1, extra_sponsors=None):
"""Creates a 1P1C package containing ephemeral dust. By default, the parent transaction
is zero-fee and creates a single zero-value dust output, and all of its outputs are
spent by the child."""
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=dust_tx_fee, version=tx_version)
for _ in range(num_dust_outputs):
self.add_output_to_create_multi_result(dusty_tx, dust_value)

extra_sponsors = extra_sponsors or []
sweep_tx = self.wallet.create_self_transfer_multi(
utxos_to_spend=dusty_tx["new_utxos"] + extra_sponsors,
version=tx_version,
)

return dusty_tx, sweep_tx

def run_test(self):

node = self.nodes[0]
Expand All @@ -67,11 +83,7 @@ def test_normal_dust(self):
self.log.info("Create 0-value dusty output, show that it works inside truc when spent in package")

assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx)

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=3)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3)

# Test doesn't work because lack of package feerates
test_res = self.nodes[0].testmempoolaccept([dusty_tx["hex"], sweep_tx["hex"]])
Expand Down Expand Up @@ -107,11 +119,7 @@ def test_node_restart(self):
self.log.info("Test that an ephemeral package is rejected on restart due to individual evaluation")

assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx)

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=3)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3)

res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "success")
Expand All @@ -132,14 +140,11 @@ def test_fee_having_parent(self):
assert_equal(self.nodes[0].getrawmempool(), [])

sats_fee = 1
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=sats_fee, version=3)
self.add_output_to_create_multi_result(dusty_tx)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, dust_tx_fee=sats_fee)
assert_equal(int(COIN * dusty_tx["fee"]), sats_fee) # has fees
assert_greater_than(dusty_tx["tx"].vout[0].nValue, 330) # main output is not dust
assert_equal(dusty_tx["tx"].vout[1].nValue, 0) # added one is dust

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=3)

# When base fee is non-0, we report dust like usual
res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "transaction failed")
Expand All @@ -153,10 +158,7 @@ def test_fee_having_parent(self):
assert_equal(res["tx-results"][dusty_tx["wtxid"]]["error"], "dust, tx with dust output must be 0-fee")

# Will not be accepted if base fee is 0 with modified fee of non-0
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx)

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=3)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3)

self.nodes[0].prioritisetransaction(txid=dusty_tx["txid"], dummy=0, fee_delta=1000)
self.nodes[1].prioritisetransaction(txid=dusty_tx["txid"], dummy=0, fee_delta=1000)
Expand All @@ -177,12 +179,7 @@ def test_multidust(self):
self.log.info("Test that a transaction with multiple ephemeral dusts is not allowed")

assert_mempool_contents(self, self.nodes[0], expected=[])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx)
self.add_output_to_create_multi_result(dusty_tx)

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=3)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, num_dust_outputs=2)

res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "transaction failed")
Expand All @@ -200,10 +197,7 @@ def test_nonzero_dust(self):
# 330 is dust threshold for taproot outputs
for value in [1, 329, 330]:
assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx, value)

dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_value=value)
test_res = self.nodes[0].testmempoolaccept([dusty_tx["hex"]])
assert test_res[0]["allowed"]

Expand All @@ -217,11 +211,7 @@ def test_non_truc(self):
self.log.info("Test that v2 dust-having transaction is rejected even if spent, because of min relay requirement")

assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=2)
self.add_output_to_create_multi_result(dusty_tx)

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=2)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=2)

res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "transaction failed")
Expand All @@ -233,12 +223,9 @@ def test_unspent_ephemeral(self):
self.log.info("Test that spending from a tx with ephemeral outputs is only allowed if dust is spent as well")

assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx, 329)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, dust_value=329)

# Valid sweep we will RBF incorrectly by not spending dust as well
sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=3)
self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"], sweep_tx["tx"]])

Expand All @@ -260,8 +247,7 @@ def test_unspent_ephemeral(self):
self.generate(self.nodes[0], 1)
assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx, 329)
dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_value=329)

# Spend non-dust only
unspent_sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[dusty_tx["new_utxos"][0]], version=3)
Expand All @@ -286,18 +272,9 @@ def test_sponsor_cycle(self):
self.log.info("Test that dust txn is not evicted when it becomes childless, but won't be mined")

assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(
fee_per_output=0,
version=3
)

self.add_output_to_create_multi_result(dusty_tx)

sponsor_coin = self.wallet.get_utxo()

# Bring "fee" input that can be double-spend separately
sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"] + [sponsor_coin], version=3)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, extra_sponsors=[sponsor_coin])

res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "success")
Expand Down Expand Up @@ -345,8 +322,7 @@ def test_reorgs(self):

# Get dusty tx mined, then check that it makes it back into mempool on reorg
# due to bypass_limits allowing 0-fee individually
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx)
dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3)
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, dusty_tx["hex"])

block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [dusty_tx["hex"]])
Expand Down Expand Up @@ -380,18 +356,13 @@ def test_reorgs(self):
assert_equal(self.nodes[0].getrawmempool(), [])

self.log.info("Test that ephemeral dust tx with fees or multi dust don't enter mempool via reorg")
multi_dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(multi_dusty_tx)
self.add_output_to_create_multi_result(multi_dusty_tx)

multi_dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, num_dust_outputs=2)
block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [multi_dusty_tx["hex"]])
self.nodes[0].invalidateblock(block_res["hash"])
assert_equal(self.nodes[0].getrawmempool(), [])

# With fee and one dust
dusty_fee_tx = self.wallet.create_self_transfer_multi(fee_per_output=1, version=3)
self.add_output_to_create_multi_result(dusty_fee_tx)

dusty_fee_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_tx_fee=1)
block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [dusty_fee_tx["hex"]])
self.nodes[0].invalidateblock(block_res["hash"])
assert_equal(self.nodes[0].getrawmempool(), [])
Expand All @@ -410,11 +381,7 @@ def test_no_minrelay_fee(self):
self.connect_nodes(0, 1)

assert_equal(self.nodes[0].getrawmempool(), [])

dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=2)
self.add_output_to_create_multi_result(dusty_tx)

sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=dusty_tx["new_utxos"], version=2)
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=2)

self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])

Expand Down

0 comments on commit dbc8ba1

Please sign in to comment.