Skip to content

Commit 2182149

Browse files
author
MarcoFalke
committed
Merge bitcoin/bitcoin#26631: test: add coverage for dust mempool policy (-dustrelayfee setting)
d6fc1d6 test: add coverage for dust mempool policy (`-dustrelayfee` setting) (Sebastian Falbesoner) 8a5dbe2 test: add `CScript` method for checking for witness program (Sebastian Falbesoner) Pull request description: This PR adds missing test coverage for the `-dustrelayfee` setting, which specifies the fee-rate used to define dust. Output scripts for all common types that are treated as standard by default (P2PK, P2(W)PKH, P2(W)SH, P2TR, bare multisig, null data, unknown witness versions v2+) are created and then checked for dust-mempool-policy each via the `testmempoolaccept` RPC: a tx with an output's nValue equal to the dust threshold should be accepted, one with an nValue of just one 1 satoshi below that should be rejected with reason `dust`. This is repeatedly done for a fixed (but obviously somewhat arbitrary) list of different `-dustrelayfee` settings on a single node, including the default and zero (i.e. no dust limit) settings. Note that the first commit introduces a necessary `CScript` helper method `IsWitnessProgram` (using PascalCase in Python is likely controversial; in this case the style for the already existing method `GetSigOpCount` was followed, which also refers to a method in the core `CScript` class). Some historical information about dust, contributed by pablomartin4btc: "The concept of dust was first introduced in bitcoin/bitcoin#2577. This [commit](bitcoin/bitcoin@eb30d1a) from bitcoin/bitcoin#9380 introduced the -dustrelayfee option. Previous to that PR, the dust feerate was whatever -minrelaytxfee was set to." ACKs for top commit: LarryRuane: ACK d6fc1d6 glozow: ACK d6fc1d6 kouloumos: ACK d6fc1d6 Tree-SHA512: 35ea2b2497dfb466395af5665bb217f7250aa7cab9dc43539a5658ab69a454e3623ff58fce7489fcc1105b37f8cb4840a93cec658c5df1de611732bc6439ccad
2 parents 08d2a3a + d6fc1d6 commit 2182149

File tree

3 files changed

+124
-0
lines changed

3 files changed

+124
-0
lines changed

test/functional/mempool_dust.py

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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()

test/functional/test_framework/script.py

+7
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,13 @@ def GetSigOpCount(self, fAccurate):
597597
lastOpcode = opcode
598598
return n
599599

600+
def IsWitnessProgram(self):
601+
"""A witness program is any valid CScript that consists of a 1-byte
602+
push opcode followed by a data push between 2 and 40 bytes."""
603+
return ((4 <= len(self) <= 42) and
604+
(self[0] == OP_0 or (OP_1 <= self[0] <= OP_16)) and
605+
(self[1] + 2 == len(self)))
606+
600607

601608
SIGHASH_DEFAULT = 0 # Taproot-only default, semantics same as SIGHASH_ALL
602609
SIGHASH_ALL = 1

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@
318318
'mempool_unbroadcast.py',
319319
'mempool_compatibility.py',
320320
'mempool_accept_wtxid.py',
321+
'mempool_dust.py',
321322
'rpc_deriveaddresses.py',
322323
'rpc_deriveaddresses.py --usecli',
323324
'p2p_ping.py',

0 commit comments

Comments
 (0)