|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Copyright (c) 2022 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.key import ECKey |
| 9 | +from test_framework.messages import ( |
| 10 | + COIN, |
| 11 | + CTxOut, |
| 12 | +) |
| 13 | +from test_framework.script import ( |
| 14 | + CScript, |
| 15 | + OP_RETURN, |
| 16 | + OP_TRUE, |
| 17 | +) |
| 18 | +from test_framework.script_util import ( |
| 19 | + key_to_p2pk_script, |
| 20 | + key_to_p2pkh_script, |
| 21 | + key_to_p2wpkh_script, |
| 22 | + keys_to_multisig_script, |
| 23 | + output_key_to_p2tr_script, |
| 24 | + program_to_witness_script, |
| 25 | + script_to_p2sh_script, |
| 26 | + script_to_p2wsh_script, |
| 27 | +) |
| 28 | +from test_framework.test_framework import BitcoinTestFramework |
| 29 | +from test_framework.test_node import TestNode |
| 30 | +from test_framework.util import ( |
| 31 | + assert_equal, |
| 32 | + get_fee, |
| 33 | +) |
| 34 | +from test_framework.wallet import MiniWallet |
| 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 | + |
| 44 | + def test_dust_output(self, node: TestNode, dust_relay_fee: Decimal, |
| 45 | + output_script: CScript, type_desc: str) -> None: |
| 46 | + # determine dust threshold (see `GetDustThreshold`) |
| 47 | + if output_script[0] == OP_RETURN: |
| 48 | + dust_threshold = 0 |
| 49 | + else: |
| 50 | + tx_size = len(CTxOut(nValue=0, scriptPubKey=output_script).serialize()) |
| 51 | + tx_size += 67 if output_script.IsWitnessProgram() else 148 |
| 52 | + dust_threshold = int(get_fee(tx_size, dust_relay_fee) * COIN) |
| 53 | + self.log.info(f"-> Test {type_desc} output (size {len(output_script)}, limit {dust_threshold})") |
| 54 | + |
| 55 | + # amount right on the dust threshold should pass |
| 56 | + tx = self.wallet.create_self_transfer()["tx"] |
| 57 | + tx.vout.append(CTxOut(nValue=dust_threshold, scriptPubKey=output_script)) |
| 58 | + tx.vout[0].nValue -= dust_threshold # keep total output value constant |
| 59 | + tx_good_hex = tx.serialize().hex() |
| 60 | + res = node.testmempoolaccept([tx_good_hex])[0] |
| 61 | + assert_equal(res['allowed'], True) |
| 62 | + |
| 63 | + # amount just below the dust threshold should fail |
| 64 | + if dust_threshold > 0: |
| 65 | + tx.vout[1].nValue -= 1 |
| 66 | + res = node.testmempoolaccept([tx.serialize().hex()])[0] |
| 67 | + assert_equal(res['allowed'], False) |
| 68 | + assert_equal(res['reject-reason'], 'dust') |
| 69 | + |
| 70 | + # finally send the transaction to avoid running out of MiniWallet UTXOs |
| 71 | + self.wallet.sendrawtransaction(from_node=node, tx_hex=tx_good_hex) |
| 72 | + |
| 73 | + def run_test(self): |
| 74 | + self.wallet = MiniWallet(self.nodes[0]) |
| 75 | + self.wallet.rescan_utxos() |
| 76 | + |
| 77 | + # prepare output scripts of each standard type |
| 78 | + key = ECKey() |
| 79 | + key.generate(compressed=False) |
| 80 | + uncompressed_pubkey = key.get_pubkey().get_bytes() |
| 81 | + key.generate(compressed=True) |
| 82 | + pubkey = key.get_pubkey().get_bytes() |
| 83 | + |
| 84 | + output_scripts = ( |
| 85 | + (key_to_p2pk_script(uncompressed_pubkey), "P2PK (uncompressed)"), |
| 86 | + (key_to_p2pk_script(pubkey), "P2PK (compressed)"), |
| 87 | + (key_to_p2pkh_script(pubkey), "P2PKH"), |
| 88 | + (script_to_p2sh_script(CScript([OP_TRUE])), "P2SH"), |
| 89 | + (key_to_p2wpkh_script(pubkey), "P2WPKH"), |
| 90 | + (script_to_p2wsh_script(CScript([OP_TRUE])), "P2WSH"), |
| 91 | + (output_key_to_p2tr_script(pubkey[1:]), "P2TR"), |
| 92 | + # witness programs for segwitv2+ can be between 2 and 40 bytes |
| 93 | + (program_to_witness_script(2, b'\x66' * 2), "P2?? (future witness version 2)"), |
| 94 | + (program_to_witness_script(16, b'\x77' * 40), "P2?? (future witness version 16)"), |
| 95 | + # largest possible output script considered standard |
| 96 | + (keys_to_multisig_script([uncompressed_pubkey]*3), "bare multisig (m-of-3)"), |
| 97 | + (CScript([OP_RETURN, b'superimportanthash']), "null data (OP_RETURN)"), |
| 98 | + ) |
| 99 | + |
| 100 | + # test default (no parameter), disabled (=0) and a bunch of arbitrary dust fee rates [sat/kvB] |
| 101 | + for dustfee_sat_kvb in (DUST_RELAY_TX_FEE, 0, 1, 66, 500, 1337, 12345, 21212, 333333): |
| 102 | + dustfee_btc_kvb = dustfee_sat_kvb / Decimal(COIN) |
| 103 | + if dustfee_sat_kvb == DUST_RELAY_TX_FEE: |
| 104 | + self.log.info(f"Test default dust limit setting ({dustfee_sat_kvb} sat/kvB)...") |
| 105 | + else: |
| 106 | + dust_parameter = f"-dustrelayfee={dustfee_btc_kvb:.8f}" |
| 107 | + self.log.info(f"Test dust limit setting {dust_parameter} ({dustfee_sat_kvb} sat/kvB)...") |
| 108 | + self.restart_node(0, extra_args=[dust_parameter]) |
| 109 | + |
| 110 | + for output_script, description in output_scripts: |
| 111 | + self.test_dust_output(self.nodes[0], dustfee_btc_kvb, output_script, description) |
| 112 | + self.generate(self.nodes[0], 1) |
| 113 | + |
| 114 | + |
| 115 | +if __name__ == '__main__': |
| 116 | + DustRelayFeeTest().main() |
0 commit comments