From 61e18dec306cfb8bc17ad2133ea1867b78000c62 Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Mon, 25 Nov 2024 21:04:42 +0100 Subject: [PATCH 1/2] doc: ephemeral policy: add missing closing double quote --- src/policy/ephemeral_policy.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/policy/ephemeral_policy.h b/src/policy/ephemeral_policy.h index f9c6e4363e733..4667f25b5af47 100644 --- a/src/policy/ephemeral_policy.h +++ b/src/policy/ephemeral_policy.h @@ -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 From 160799d9135528dbdea40690f0bb0d56c6c4803a Mon Sep 17 00:00:00 2001 From: Sebastian Falbesoner Date: Sun, 24 Nov 2024 02:12:35 +0100 Subject: [PATCH 2/2] test: refactor: introduce `create_ephemeral_dust_package` helper --- test/functional/mempool_ephemeral_dust.py | 93 ++++++++--------------- 1 file changed, 30 insertions(+), 63 deletions(-) diff --git a/test/functional/mempool_ephemeral_dust.py b/test/functional/mempool_ephemeral_dust.py index 3e0cbabeec021..56e4b404e4e71 100755 --- a/test/functional/mempool_ephemeral_dust.py +++ b/test/functional/mempool_ephemeral_dust.py @@ -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] @@ -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"]]) @@ -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") @@ -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") @@ -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) @@ -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") @@ -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"] @@ -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") @@ -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"]]) @@ -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) @@ -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") @@ -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"]]) @@ -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(), []) @@ -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"]])