Merge bitcoin/bitcoin#31371: doc, test: more ephemeral dust follow-ups
Some checks failed
CI / test each commit (push) Has been cancelled
CI / macOS 14 native, arm64, no depends, sqlite only, gui (push) Has been cancelled
CI / macOS 14 native, arm64, fuzz (push) Has been cancelled
CI / Win64 native, VS 2022 (push) Has been cancelled
CI / Win64 native fuzz, VS 2022 (push) Has been cancelled
CI / ASan + LSan + UBSan + integer, no depends, USDT (push) Has been cancelled

160799d913 test: refactor: introduce `create_ephemeral_dust_package` helper (Sebastian Falbesoner)
61e18dec30 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:

  https://github.com/bitcoin/bitcoin/pull/30239#discussion_r1828577696
  https://github.com/bitcoin/bitcoin/pull/30239#discussion_r1825279952

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

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

Tree-SHA512: e9a80c6733f1e7fe9e834d81b404f6e8ef7a61fe986f61b3dcdbda1a0bc547145fc279ec02f54361df56cb4e62a6fedaa0f3991c6e084c3a703ed1b1bfbdbe4e
This commit is contained in:
glozow 2024-11-29 08:33:37 -05:00
commit dbc8ba12f3
No known key found for this signature in database
GPG key ID: BA03F4DBE0C63FB4
2 changed files with 31 additions and 64 deletions

View file

@ -30,7 +30,7 @@ class TxValidationState;
* TxC, spends TxA's dust * TxC, spends TxA's dust
* *
* All the dust is spent if TxA+TxB+TxC is accepted, but the mining template may just pick * 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 * 1) None
* 2) TxA+TxB+TxC * 2) TxA+TxB+TxC
* 3) TxA+TxC * 3) TxA+TxC

View file

@ -47,6 +47,22 @@ class EphemeralDustTest(BitcoinTestFramework):
result["new_utxos"].append({"txid": new_txid, "vout": len(result["tx"].vout) - 1, "value": Decimal(output_value) / COIN, "height": 0, "coinbase": False, "confirmations": 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): def run_test(self):
node = self.nodes[0] node = self.nodes[0]
@ -67,11 +83,7 @@ class EphemeralDustTest(BitcoinTestFramework):
self.log.info("Create 0-value dusty output, show that it works inside truc when spent in package") self.log.info("Create 0-value dusty output, show that it works inside truc when spent in package")
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3)
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)
# Test doesn't work because lack of package feerates # Test doesn't work because lack of package feerates
test_res = self.nodes[0].testmempoolaccept([dusty_tx["hex"], sweep_tx["hex"]]) test_res = self.nodes[0].testmempoolaccept([dusty_tx["hex"], sweep_tx["hex"]])
@ -107,11 +119,7 @@ class EphemeralDustTest(BitcoinTestFramework):
self.log.info("Test that an ephemeral package is rejected on restart due to individual evaluation") self.log.info("Test that an ephemeral package is rejected on restart due to individual evaluation")
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3)
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)
res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]]) res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "success") assert_equal(res["package_msg"], "success")
@ -132,14 +140,11 @@ class EphemeralDustTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
sats_fee = 1 sats_fee = 1
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=sats_fee, version=3) dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, dust_tx_fee=sats_fee)
self.add_output_to_create_multi_result(dusty_tx)
assert_equal(int(COIN * dusty_tx["fee"]), sats_fee) # has fees 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_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 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 # When base fee is non-0, we report dust like usual
res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]]) res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "transaction failed") assert_equal(res["package_msg"], "transaction failed")
@ -153,10 +158,7 @@ class EphemeralDustTest(BitcoinTestFramework):
assert_equal(res["tx-results"][dusty_tx["wtxid"]]["error"], "dust, tx with dust output must be 0-fee") 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 # 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) dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_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)
self.nodes[0].prioritisetransaction(txid=dusty_tx["txid"], dummy=0, fee_delta=1000) 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) self.nodes[1].prioritisetransaction(txid=dusty_tx["txid"], dummy=0, fee_delta=1000)
@ -177,12 +179,7 @@ class EphemeralDustTest(BitcoinTestFramework):
self.log.info("Test that a transaction with multiple ephemeral dusts is not allowed") self.log.info("Test that a transaction with multiple ephemeral dusts is not allowed")
assert_mempool_contents(self, self.nodes[0], expected=[]) assert_mempool_contents(self, self.nodes[0], expected=[])
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, num_dust_outputs=2)
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)
res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]]) res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "transaction failed") assert_equal(res["package_msg"], "transaction failed")
@ -200,10 +197,7 @@ class EphemeralDustTest(BitcoinTestFramework):
# 330 is dust threshold for taproot outputs # 330 is dust threshold for taproot outputs
for value in [1, 329, 330]: for value in [1, 329, 330]:
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_value=value)
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx, value)
test_res = self.nodes[0].testmempoolaccept([dusty_tx["hex"]]) test_res = self.nodes[0].testmempoolaccept([dusty_tx["hex"]])
assert test_res[0]["allowed"] assert test_res[0]["allowed"]
@ -217,11 +211,7 @@ class EphemeralDustTest(BitcoinTestFramework):
self.log.info("Test that v2 dust-having transaction is rejected even if spent, because of min relay requirement") 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(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=2)
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)
res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]]) res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "transaction failed") assert_equal(res["package_msg"], "transaction failed")
@ -233,12 +223,9 @@ class EphemeralDustTest(BitcoinTestFramework):
self.log.info("Test that spending from a tx with ephemeral outputs is only allowed if dust is spent as well") 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(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=3, dust_value=329)
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3)
self.add_output_to_create_multi_result(dusty_tx, 329)
# Valid sweep we will RBF incorrectly by not spending dust as well # 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"]]) self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"], sweep_tx["tx"]]) assert_mempool_contents(self, self.nodes[0], expected=[dusty_tx["tx"], sweep_tx["tx"]])
@ -260,8 +247,7 @@ class EphemeralDustTest(BitcoinTestFramework):
self.generate(self.nodes[0], 1) self.generate(self.nodes[0], 1)
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3) dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_value=329)
self.add_output_to_create_multi_result(dusty_tx, 329)
# Spend non-dust only # Spend non-dust only
unspent_sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[dusty_tx["new_utxos"][0]], version=3) unspent_sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[dusty_tx["new_utxos"][0]], version=3)
@ -286,18 +272,9 @@ class EphemeralDustTest(BitcoinTestFramework):
self.log.info("Test that dust txn is not evicted when it becomes childless, but won't be mined") 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(), []) 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() sponsor_coin = self.wallet.get_utxo()
# Bring "fee" input that can be double-spend separately # 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"]]) res = self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])
assert_equal(res["package_msg"], "success") assert_equal(res["package_msg"], "success")
@ -345,8 +322,7 @@ class EphemeralDustTest(BitcoinTestFramework):
# Get dusty tx mined, then check that it makes it back into mempool on reorg # Get dusty tx mined, then check that it makes it back into mempool on reorg
# due to bypass_limits allowing 0-fee individually # due to bypass_limits allowing 0-fee individually
dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=0, version=3) dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3)
self.add_output_to_create_multi_result(dusty_tx)
assert_raises_rpc_error(-26, "min relay fee not met", self.nodes[0].sendrawtransaction, dusty_tx["hex"]) 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"]]) block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [dusty_tx["hex"]])
@ -380,18 +356,13 @@ class EphemeralDustTest(BitcoinTestFramework):
assert_equal(self.nodes[0].getrawmempool(), []) 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") 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) multi_dusty_tx, _ = self.create_ephemeral_dust_package(tx_version=3, num_dust_outputs=2)
self.add_output_to_create_multi_result(multi_dusty_tx)
self.add_output_to_create_multi_result(multi_dusty_tx)
block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [multi_dusty_tx["hex"]]) block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [multi_dusty_tx["hex"]])
self.nodes[0].invalidateblock(block_res["hash"]) self.nodes[0].invalidateblock(block_res["hash"])
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
# With fee and one dust # With fee and one dust
dusty_fee_tx = self.wallet.create_self_transfer_multi(fee_per_output=1, version=3) dusty_fee_tx, _ = self.create_ephemeral_dust_package(tx_version=3, dust_tx_fee=1)
self.add_output_to_create_multi_result(dusty_fee_tx)
block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [dusty_fee_tx["hex"]]) block_res = self.nodes[0].rpc.generateblock(self.wallet.get_address(), [dusty_fee_tx["hex"]])
self.nodes[0].invalidateblock(block_res["hash"]) self.nodes[0].invalidateblock(block_res["hash"])
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
@ -410,11 +381,7 @@ class EphemeralDustTest(BitcoinTestFramework):
self.connect_nodes(0, 1) self.connect_nodes(0, 1)
assert_equal(self.nodes[0].getrawmempool(), []) assert_equal(self.nodes[0].getrawmempool(), [])
dusty_tx, sweep_tx = self.create_ephemeral_dust_package(tx_version=2)
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)
self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]]) self.nodes[0].submitpackage([dusty_tx["hex"], sweep_tx["hex"]])