Skip to content

Commit 647b897

Browse files
committed
test: add dust relay output value test
1 parent b63f7ab commit 647b897

File tree

2 files changed

+124
-0
lines changed

2 files changed

+124
-0
lines changed

test/functional/test_runner.py

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
'wallet_elements_regression_1172.py --legacy-wallet',
114114
'wallet_elements_regression_1259.py --legacy-wallet',
115115
'wallet_elements_21million.py',
116+
'wallet_elements_dust_relay.py',
116117
'feature_trim_headers.py',
117118
# Longest test should go first, to favor running tests in parallel
118119
'wallet_hd.py --legacy-wallet',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2017-2020 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+
6+
from decimal import Decimal
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
assert_equal,
10+
assert_raises_rpc_error,
11+
)
12+
13+
class WalletTest(BitcoinTestFramework):
14+
def set_test_params(self):
15+
self.setup_clean_chain = False
16+
self.num_nodes = 2
17+
args = [
18+
"-blindedaddresses=1",
19+
"-minrelaytxfee=0.00000100",
20+
]
21+
self.extra_args = [
22+
args,
23+
args + ["-dustrelayfee=0.00003000"], # second node uses default upstream dustrelayfee
24+
]
25+
26+
def skip_test_if_missing_module(self):
27+
self.skip_if_no_wallet()
28+
29+
def select_unblinded_utxo(self, node):
30+
for utxo in node.listunspent():
31+
if utxo["amountblinder"] == "00" * 32:
32+
return utxo
33+
else:
34+
continue
35+
raise Exception("no unblinded utxo")
36+
37+
def run_test(self):
38+
assert_equal(self.nodes[0].getbalance(), {'bitcoin': 1250})
39+
assert_equal(self.nodes[1].getbalance(), {'bitcoin': 1250})
40+
41+
addr = self.nodes[0].getnewaddress()
42+
43+
# test dust threshold for upstream dustrelayfee=3sat/vb
44+
# 495 sats should succeed
45+
amt = "0.00000495"
46+
self.nodes[1].sendtoaddress(address=addr, amount=amt)
47+
self.generate(self.nodes[1], 1, sync_fun=self.no_op)
48+
49+
# 494 sats should fail
50+
amt = "0.00000494"
51+
assert_raises_rpc_error(-6, "Transaction amount too small", self.nodes[1].sendtoaddress, address=addr, amount=amt)
52+
53+
addr = self.nodes[1].getnewaddress()
54+
55+
# test dust threshold for elements default dustrelayfee=0.1sat/vb
56+
# 17 sats should succeed
57+
amt = "0.00000017"
58+
self.nodes[0].sendtoaddress(address=addr, amount=amt)
59+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
60+
61+
# 16 sats should fail
62+
amt = "0.00000016"
63+
assert_raises_rpc_error(-6, "Transaction amount too small", self.nodes[0].sendtoaddress, address=addr, amount=amt)
64+
65+
# a blinded transaction created manually can have an output value as low as 1 sat
66+
addr = self.nodes[1].getnewaddress()
67+
changeaddr = self.nodes[0].getnewaddress()
68+
utxo = self.nodes[0].listunspent()[0]
69+
amt = Decimal("0.00000001")
70+
fee = Decimal("0.00000258")
71+
change = utxo["amount"] - amt - fee
72+
inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
73+
outputs = [{addr: amt}, {changeaddr: change}, {"fee": fee}]
74+
raw = self.nodes[0].createrawtransaction(inputs, outputs)
75+
blinded = self.nodes[0].blindrawtransaction(raw)
76+
signed = self.nodes[0].signrawtransactionwithwallet(blinded)
77+
assert signed["complete"]
78+
tx = signed["hex"]
79+
assert self.nodes[1].testmempoolaccept([tx])[0]["allowed"]
80+
assert self.nodes[0].testmempoolaccept([tx])[0]["allowed"]
81+
assert_equal(self.nodes[1].sendrawtransaction(tx), self.nodes[0].sendrawtransaction(tx))
82+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
83+
84+
# explicit transactions have slightly smaller outputs
85+
# node 0 will accept an output value of 14 sats but node 1 will not
86+
addr = self.nodes[1].getnewaddress(address_type="bech32")
87+
changeaddr = self.nodes[0].getnewaddress(address_type="bech32")
88+
utxo = self.select_unblinded_utxo(self.nodes[0])
89+
amt = Decimal("0.00000014")
90+
fee = Decimal("0.00000258")
91+
change = utxo["amount"] - amt - fee
92+
inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
93+
outputs = [{addr: amt}, {changeaddr: change}, {"fee": fee}]
94+
raw = self.nodes[0].createrawtransaction(inputs, outputs)
95+
signed = self.nodes[0].signrawtransactionwithwallet(raw)
96+
assert signed["complete"]
97+
tx = signed["hex"]
98+
assert self.nodes[0].testmempoolaccept([tx])[0]["allowed"]
99+
assert not self.nodes[1].testmempoolaccept([tx])[0]["allowed"]
100+
self.nodes[0].sendrawtransaction(tx)
101+
assert_raises_rpc_error(-26, "dust", self.nodes[1].sendrawtransaction, tx)
102+
self.generate(self.nodes[0], 1, sync_fun=self.no_op)
103+
104+
# neither node will accept an explicit output value of 13 sats
105+
addr = self.nodes[1].getnewaddress(address_type="bech32")
106+
changeaddr = self.nodes[0].getnewaddress(address_type="bech32")
107+
utxo = self.select_unblinded_utxo(self.nodes[0])
108+
amt = Decimal("0.00000013")
109+
fee = Decimal("0.00000258")
110+
change = utxo["amount"] - amt - fee
111+
inputs = [{"txid": utxo["txid"], "vout": utxo["vout"]}]
112+
outputs = [{addr: amt}, {changeaddr: change}, {"fee": fee}]
113+
raw = self.nodes[0].createrawtransaction(inputs, outputs)
114+
signed = self.nodes[0].signrawtransactionwithwallet(raw)
115+
assert signed["complete"]
116+
tx = signed["hex"]
117+
assert not self.nodes[0].testmempoolaccept([tx])[0]["allowed"]
118+
assert not self.nodes[1].testmempoolaccept([tx])[0]["allowed"]
119+
assert_raises_rpc_error(-26, "dust", self.nodes[0].sendrawtransaction, tx)
120+
assert_raises_rpc_error(-26, "dust", self.nodes[1].sendrawtransaction, tx)
121+
122+
if __name__ == '__main__':
123+
WalletTest().main()

0 commit comments

Comments
 (0)