Skip to content

Commit 8f2e208

Browse files
committed
test: add test for avoidreuse feature
1 parent 0bdfbd3 commit 8f2e208

File tree

2 files changed

+218
-0
lines changed

2 files changed

+218
-0
lines changed

test/functional/test_runner.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
'rpc_misc.py',
121121
'interface_rest.py',
122122
'mempool_spend_coinbase.py',
123+
'wallet_avoidreuse.py',
123124
'mempool_reorg.py',
124125
'mempool_persist.py',
125126
'wallet_multiwallet.py',

test/functional/wallet_avoidreuse.py

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2018 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 the avoid_reuse and setwalletflag features."""
6+
7+
from test_framework.test_framework import BitcoinTestFramework
8+
from test_framework.util import (
9+
assert_equal,
10+
assert_raises_rpc_error,
11+
connect_nodes_bi,
12+
)
13+
14+
# TODO: Copied from wallet_groups.py -- should perhaps move into util.py
15+
def assert_approx(v, vexp, vspan=0.00001):
16+
if v < vexp - vspan:
17+
raise AssertionError("%s < [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
18+
if v > vexp + vspan:
19+
raise AssertionError("%s > [%s..%s]" % (str(v), str(vexp - vspan), str(vexp + vspan)))
20+
21+
def reset_balance(node, discardaddr):
22+
'''Throw away all owned coins by the node so it gets a balance of 0.'''
23+
balance = node.getbalance(avoid_reuse=False)
24+
if balance > 0.5:
25+
node.sendtoaddress(address=discardaddr, amount=balance, subtractfeefromamount=True, avoid_reuse=False)
26+
27+
def count_unspent(node):
28+
'''Count the unspent outputs for the given node and return various statistics'''
29+
r = {
30+
"total": {
31+
"count": 0,
32+
"sum": 0,
33+
},
34+
"reused": {
35+
"count": 0,
36+
"sum": 0,
37+
},
38+
}
39+
supports_reused = True
40+
for utxo in node.listunspent(minconf=0):
41+
r["total"]["count"] += 1
42+
r["total"]["sum"] += utxo["amount"]
43+
if supports_reused and "reused" in utxo:
44+
if utxo["reused"]:
45+
r["reused"]["count"] += 1
46+
r["reused"]["sum"] += utxo["amount"]
47+
else:
48+
supports_reused = False
49+
r["reused"]["supported"] = supports_reused
50+
return r
51+
52+
def assert_unspent(node, total_count=None, total_sum=None, reused_supported=None, reused_count=None, reused_sum=None):
53+
'''Make assertions about a node's unspent output statistics'''
54+
stats = count_unspent(node)
55+
if total_count is not None:
56+
assert_equal(stats["total"]["count"], total_count)
57+
if total_sum is not None:
58+
assert_approx(stats["total"]["sum"], total_sum, 0.001)
59+
if reused_supported is not None:
60+
assert_equal(stats["reused"]["supported"], reused_supported)
61+
if reused_count is not None:
62+
assert_equal(stats["reused"]["count"], reused_count)
63+
if reused_sum is not None:
64+
assert_approx(stats["reused"]["sum"], reused_sum, 0.001)
65+
66+
class AvoidReuseTest(BitcoinTestFramework):
67+
68+
def set_test_params(self):
69+
self.setup_clean_chain = False
70+
self.num_nodes = 2
71+
72+
def skip_test_if_missing_module(self):
73+
self.skip_if_no_wallet()
74+
75+
def run_test(self):
76+
'''Set up initial chain and run tests defined below'''
77+
78+
self.test_persistence()
79+
self.test_immutable()
80+
81+
self.nodes[0].generate(110)
82+
self.sync_all()
83+
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
84+
self.test_fund_send_fund_senddirty()
85+
reset_balance(self.nodes[1], self.nodes[0].getnewaddress())
86+
self.test_fund_send_fund_send()
87+
88+
def test_persistence(self):
89+
'''Test that wallet files persist the avoid_reuse flag.'''
90+
# Configure node 1 to use avoid_reuse
91+
self.nodes[1].setwalletflag('avoid_reuse')
92+
93+
# Flags should be node1.avoid_reuse=false, node2.avoid_reuse=true
94+
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
95+
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
96+
97+
# Stop and restart node 1
98+
self.stop_node(1)
99+
self.start_node(1)
100+
connect_nodes_bi(self.nodes, 0, 1)
101+
102+
# Flags should still be node1.avoid_reuse=false, node2.avoid_reuse=true
103+
assert_equal(self.nodes[0].getwalletinfo()["avoid_reuse"], False)
104+
assert_equal(self.nodes[1].getwalletinfo()["avoid_reuse"], True)
105+
106+
# Attempting to set flag to its current state should throw
107+
assert_raises_rpc_error(-8, "Wallet flag is already set to false", self.nodes[0].setwalletflag, 'avoid_reuse', False)
108+
assert_raises_rpc_error(-8, "Wallet flag is already set to true", self.nodes[1].setwalletflag, 'avoid_reuse', True)
109+
110+
def test_immutable(self):
111+
'''Test immutable wallet flags'''
112+
# Attempt to set the disable_private_keys flag; this should not work
113+
assert_raises_rpc_error(-8, "Wallet flag is immutable", self.nodes[1].setwalletflag, 'disable_private_keys')
114+
115+
tempwallet = ".wallet_avoidreuse.py_test_immutable_wallet.dat"
116+
117+
# Create a wallet with disable_private_keys set; this should work
118+
self.nodes[1].createwallet(tempwallet, True)
119+
w = self.nodes[1].get_wallet_rpc(tempwallet)
120+
121+
# Attempt to unset the disable_private_keys flag; this should not work
122+
assert_raises_rpc_error(-8, "Wallet flag is immutable", w.setwalletflag, 'disable_private_keys', False)
123+
124+
# Unload temp wallet
125+
self.nodes[1].unloadwallet(tempwallet)
126+
127+
def test_fund_send_fund_senddirty(self):
128+
'''
129+
Test the same as test_fund_send_fund_send, except send the 10 BTC with
130+
the avoid_reuse flag set to false. This means the 10 BTC send should succeed,
131+
where it fails in test_fund_send_fund_send.
132+
'''
133+
134+
fundaddr = self.nodes[1].getnewaddress()
135+
retaddr = self.nodes[0].getnewaddress()
136+
137+
self.nodes[0].sendtoaddress(fundaddr, 10)
138+
self.nodes[0].generate(1)
139+
self.sync_all()
140+
141+
# listunspent should show 1 single, unused 10 btc output
142+
assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)
143+
144+
self.nodes[1].sendtoaddress(retaddr, 5)
145+
self.nodes[0].generate(1)
146+
self.sync_all()
147+
148+
# listunspent should show 1 single, unused 5 btc output
149+
assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)
150+
151+
self.nodes[0].sendtoaddress(fundaddr, 10)
152+
self.nodes[0].generate(1)
153+
self.sync_all()
154+
155+
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
156+
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
157+
158+
self.nodes[1].sendtoaddress(address=retaddr, amount=10, avoid_reuse=False)
159+
160+
# listunspent should show 1 total outputs (5 btc), unused
161+
assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_count=0)
162+
163+
# node 1 should now have about 5 btc left (for both cases)
164+
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
165+
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 5, 0.001)
166+
167+
def test_fund_send_fund_send(self):
168+
'''
169+
Test the simple case where [1] generates a new address A, then
170+
[0] sends 10 BTC to A.
171+
[1] spends 5 BTC from A. (leaving roughly 5 BTC useable)
172+
[0] sends 10 BTC to A again.
173+
[1] tries to spend 10 BTC (fails; dirty).
174+
[1] tries to spend 4 BTC (succeeds; change address sufficient)
175+
'''
176+
177+
fundaddr = self.nodes[1].getnewaddress()
178+
retaddr = self.nodes[0].getnewaddress()
179+
180+
self.nodes[0].sendtoaddress(fundaddr, 10)
181+
self.nodes[0].generate(1)
182+
self.sync_all()
183+
184+
# listunspent should show 1 single, unused 10 btc output
185+
assert_unspent(self.nodes[1], total_count=1, total_sum=10, reused_supported=True, reused_count=0)
186+
187+
self.nodes[1].sendtoaddress(retaddr, 5)
188+
self.nodes[0].generate(1)
189+
self.sync_all()
190+
191+
# listunspent should show 1 single, unused 5 btc output
192+
assert_unspent(self.nodes[1], total_count=1, total_sum=5, reused_supported=True, reused_count=0)
193+
194+
self.nodes[0].sendtoaddress(fundaddr, 10)
195+
self.nodes[0].generate(1)
196+
self.sync_all()
197+
198+
# listunspent should show 2 total outputs (5, 10 btc), one unused (5), one reused (10)
199+
assert_unspent(self.nodes[1], total_count=2, total_sum=15, reused_count=1, reused_sum=10)
200+
201+
# node 1 should now have a balance of 5 (no dirty) or 15 (including dirty)
202+
assert_approx(self.nodes[1].getbalance(), 5, 0.001)
203+
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 15, 0.001)
204+
205+
assert_raises_rpc_error(-6, "Insufficient funds", self.nodes[1].sendtoaddress, retaddr, 10)
206+
207+
self.nodes[1].sendtoaddress(retaddr, 4)
208+
209+
# listunspent should show 2 total outputs (1, 10 btc), one unused (1), one reused (10)
210+
assert_unspent(self.nodes[1], total_count=2, total_sum=11, reused_count=1, reused_sum=10)
211+
212+
# node 1 should now have about 1 btc left (no dirty) and 11 (including dirty)
213+
assert_approx(self.nodes[1].getbalance(), 1, 0.001)
214+
assert_approx(self.nodes[1].getbalance(avoid_reuse=False), 11, 0.001)
215+
216+
if __name__ == '__main__':
217+
AvoidReuseTest().main()

0 commit comments

Comments
 (0)