mempool_dust.py
1 #!/usr/bin/env python3 2 # Copyright (c) 2022-present The Bitcoin Core developers 3 # Distributed under the MIT software license, see the accompanying 4 # file COPYING or http://www.opensource.org/licenses/mit-license.php. 5 """Test dust limit mempool policy (`-dustrelayfee` parameter)""" 6 from decimal import Decimal 7 8 from test_framework.messages import ( 9 COIN, 10 CTxOut, 11 ) 12 from test_framework.script import ( 13 CScript, 14 OP_RETURN, 15 OP_TRUE, 16 ) 17 from test_framework.script_util import ( 18 key_to_p2pk_script, 19 key_to_p2pkh_script, 20 key_to_p2wpkh_script, 21 keys_to_multisig_script, 22 output_key_to_p2tr_script, 23 program_to_witness_script, 24 script_to_p2sh_script, 25 script_to_p2wsh_script, 26 ) 27 from test_framework.test_framework import BitcoinTestFramework 28 from test_framework.test_node import TestNode 29 from test_framework.util import ( 30 assert_equal, 31 get_fee, 32 ) 33 from test_framework.wallet import MiniWallet 34 from test_framework.wallet_util import generate_keypair 35 36 37 DUST_RELAY_TX_FEE = 3000 # default setting [sat/kvB] 38 39 40 class DustRelayFeeTest(BitcoinTestFramework): 41 def set_test_params(self): 42 self.num_nodes = 1 43 self.extra_args = [['-permitbaremultisig']] 44 45 def test_dust_output(self, node: TestNode, dust_relay_fee: Decimal, 46 output_script: CScript, type_desc: str) -> None: 47 # determine dust threshold (see `GetDustThreshold`) 48 if output_script[0] == OP_RETURN: 49 dust_threshold = 0 50 else: 51 tx_size = len(CTxOut(nValue=0, scriptPubKey=output_script).serialize()) 52 tx_size += 67 if output_script.IsWitnessProgram() else 148 53 dust_threshold = int(get_fee(tx_size, dust_relay_fee) * COIN) 54 self.log.info(f"-> Test {type_desc} output (size {len(output_script)}, limit {dust_threshold})") 55 56 # amount right on the dust threshold should pass 57 tx = self.wallet.create_self_transfer()["tx"] 58 tx.vout.append(CTxOut(nValue=dust_threshold, scriptPubKey=output_script)) 59 tx.vout[0].nValue -= dust_threshold # keep total output value constant 60 tx_good_hex = tx.serialize().hex() 61 res = node.testmempoolaccept([tx_good_hex])[0] 62 assert_equal(res['allowed'], True) 63 64 # amount just below the dust threshold should fail 65 if dust_threshold > 0: 66 tx.vout[1].nValue -= 1 67 res = node.testmempoolaccept([tx.serialize().hex()])[0] 68 assert_equal(res['allowed'], False) 69 assert_equal(res['reject-reason'], 'dust') 70 71 # finally send the transaction to avoid running out of MiniWallet UTXOs 72 self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_good_hex) 73 74 def test_dustrelay(self): 75 self.log.info("Test that small outputs are acceptable when dust relay rate is set to 0 that would otherwise trigger ephemeral dust rules") 76 77 self.restart_node(0, extra_args=["-dustrelayfee=0"]) 78 79 assert_equal(self.nodes[0].getrawmempool(), []) 80 81 # Create two dust outputs. Transaction has zero fees. both dust outputs are unspent, and would have failed individual checks. 82 # The amount is 1 satoshi because create_self_transfer_multi disallows 0. 83 dusty_tx = self.wallet.create_self_transfer_multi(fee_per_output=1000, amount_per_output=1, num_outputs=2) 84 dust_txid = self.nodes[0].sendrawtransaction(hexstring=dusty_tx["hex"], maxfeerate=0) 85 86 assert_equal(self.nodes[0].getrawmempool(), [dust_txid]) 87 88 # Spends one dust along with fee input, leave other dust unspent to check ephemeral dust checks aren't being enforced 89 sweep_tx = self.wallet.create_self_transfer_multi(utxos_to_spend=[self.wallet.get_utxo(), dusty_tx["new_utxos"][0]]) 90 sweep_txid = self.nodes[0].sendrawtransaction(sweep_tx["hex"]) 91 92 mempool_entries = self.nodes[0].getrawmempool() 93 assert dust_txid in mempool_entries 94 assert sweep_txid in mempool_entries 95 assert_equal(len(mempool_entries), 2) 96 97 # Wipe extra arg to reset dust relay 98 self.restart_node(0, extra_args=[]) 99 100 assert_equal(self.nodes[0].getrawmempool(), []) 101 102 def run_test(self): 103 self.wallet = MiniWallet(self.nodes[0]) 104 105 self.test_dustrelay() 106 107 # prepare output scripts of each standard type 108 _, uncompressed_pubkey = generate_keypair(compressed=False) 109 _, pubkey = generate_keypair(compressed=True) 110 111 output_scripts = ( 112 (key_to_p2pk_script(uncompressed_pubkey), "P2PK (uncompressed)"), 113 (key_to_p2pk_script(pubkey), "P2PK (compressed)"), 114 (key_to_p2pkh_script(pubkey), "P2PKH"), 115 (script_to_p2sh_script(CScript([OP_TRUE])), "P2SH"), 116 (key_to_p2wpkh_script(pubkey), "P2WPKH"), 117 (script_to_p2wsh_script(CScript([OP_TRUE])), "P2WSH"), 118 (output_key_to_p2tr_script(pubkey[1:]), "P2TR"), 119 # witness programs for segwitv2+ can be between 2 and 40 bytes 120 (program_to_witness_script(2, b'\x66' * 2), "P2?? (future witness version 2)"), 121 (program_to_witness_script(16, b'\x77' * 40), "P2?? (future witness version 16)"), 122 # largest possible output script considered standard 123 (keys_to_multisig_script([uncompressed_pubkey]*3), "bare multisig (m-of-3)"), 124 (CScript([OP_RETURN, b'superimportanthash']), "null data (OP_RETURN)"), 125 ) 126 127 # test default (no parameter), disabled (=0) and a bunch of arbitrary dust fee rates [sat/kvB] 128 for dustfee_sat_kvb in (DUST_RELAY_TX_FEE, 0, 1, 66, 500, 1337, 12345, 21212, 333333): 129 dustfee_btc_kvb = dustfee_sat_kvb / Decimal(COIN) 130 if dustfee_sat_kvb == DUST_RELAY_TX_FEE: 131 self.log.info(f"Test default dust limit setting ({dustfee_sat_kvb} sat/kvB)...") 132 else: 133 dust_parameter = f"-dustrelayfee={dustfee_btc_kvb:.8f}" 134 self.log.info(f"Test dust limit setting {dust_parameter} ({dustfee_sat_kvb} sat/kvB)...") 135 self.restart_node(0, extra_args=[dust_parameter, "-permitbaremultisig"]) 136 137 for output_script, description in output_scripts: 138 self.test_dust_output(self.nodes[0], dustfee_btc_kvb, output_script, description) 139 self.generate(self.nodes[0], 1) 140 141 142 if __name__ == '__main__': 143 DustRelayFeeTest(__file__).main()