wallet_keypool.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2014-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 the wallet keypool and interaction with wallet encryption/locking."""
6
7 from decimal import Decimal
8
9 from test_framework.test_framework import BitcoinTestFramework
10 from test_framework.descriptors import descsum_create
11 from test_framework.extendedkey import ExtendedPrivateKey
12 from test_framework.util import (
13 assert_equal,
14 assert_not_equal,
15 assert_raises_rpc_error,
16 )
17 from test_framework.wallet_util import WalletUnlock
18
19 class KeyPoolTest(BitcoinTestFramework):
20 def set_test_params(self):
21 self.num_nodes = 1
22
23 def skip_test_if_missing_module(self):
24 self.skip_if_no_wallet()
25
26 def run_test(self):
27 nodes = self.nodes
28 addr_before_encrypting = nodes[0].getnewaddress()
29 addr_before_encrypting_data = nodes[0].getaddressinfo(addr_before_encrypting)
30
31 # Encrypt wallet and wait to terminate
32 nodes[0].encryptwallet('test')
33 # Import hardened derivation only descriptors
34 nodes[0].walletpassphrase('test', 10)
35 nodes[0].importdescriptors([
36 {
37 "desc": descsum_create(f"wpkh({ExtendedPrivateKey.generate().to_string()}/0h/*h)"),
38 "timestamp": "now",
39 "range": [0,0],
40 "active": True
41 },
42 {
43 "desc": descsum_create(f"pkh({ExtendedPrivateKey.generate().to_string()}/1h/*h)"),
44 "timestamp": "now",
45 "range": [0,0],
46 "active": True
47 },
48 {
49 "desc": descsum_create(f"sh(wpkh({ExtendedPrivateKey.generate().to_string()}/2h/*h))"),
50 "timestamp": "now",
51 "range": [0,0],
52 "active": True
53 },
54 {
55 "desc": descsum_create(f"wpkh({ExtendedPrivateKey.generate().to_string()}/3h/*h)"),
56 "timestamp": "now",
57 "range": [0,0],
58 "active": True,
59 "internal": True
60 },
61 {
62 "desc": descsum_create(f"pkh({ExtendedPrivateKey.generate().to_string()}/4h/*h)"),
63 "timestamp": "now",
64 "range": [0,0],
65 "active": True,
66 "internal": True
67 },
68 {
69 "desc": descsum_create(f"sh(wpkh({ExtendedPrivateKey.generate().to_string()}/5h/*h))"),
70 "timestamp": "now",
71 "range": [0,0],
72 "active": True,
73 "internal": True
74 }
75 ])
76 nodes[0].walletlock()
77 # Keep creating keys
78 addr = nodes[0].getnewaddress()
79 addr_data = nodes[0].getaddressinfo(addr)
80 assert_not_equal(addr_before_encrypting_data['hdmasterfingerprint'], addr_data['hdmasterfingerprint'])
81 assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
82
83 # put six (plus 2) new keys in the keypool (100% external-, +100% internal-keys, 1 in min)
84 with WalletUnlock(nodes[0], 'test'):
85 nodes[0].keypoolrefill(6)
86 wi = nodes[0].getwalletinfo()
87 assert_equal(wi['keypoolsize_hd_internal'], 24)
88 assert_equal(wi['keypoolsize'], 24)
89
90 # drain the internal keys
91 nodes[0].getrawchangeaddress()
92 nodes[0].getrawchangeaddress()
93 nodes[0].getrawchangeaddress()
94 nodes[0].getrawchangeaddress()
95 nodes[0].getrawchangeaddress()
96 nodes[0].getrawchangeaddress()
97 # remember keypool sizes
98 wi = nodes[0].getwalletinfo()
99 kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
100 # the next one should fail
101 assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getrawchangeaddress)
102 # check that keypool sizes did not change
103 wi = nodes[0].getwalletinfo()
104 kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
105 assert_equal(kp_size_before, kp_size_after)
106
107 # drain the external keys
108 addr = set()
109 addr.add(nodes[0].getnewaddress(address_type="bech32"))
110 addr.add(nodes[0].getnewaddress(address_type="bech32"))
111 addr.add(nodes[0].getnewaddress(address_type="bech32"))
112 addr.add(nodes[0].getnewaddress(address_type="bech32"))
113 addr.add(nodes[0].getnewaddress(address_type="bech32"))
114 addr.add(nodes[0].getnewaddress(address_type="bech32"))
115 assert_equal(len(addr), 6)
116 # remember keypool sizes
117 wi = nodes[0].getwalletinfo()
118 kp_size_before = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
119 # the next one should fail
120 assert_raises_rpc_error(-12, "Error: Keypool ran out, please call keypoolrefill first", nodes[0].getnewaddress)
121 # check that keypool sizes did not change
122 wi = nodes[0].getwalletinfo()
123 kp_size_after = [wi['keypoolsize_hd_internal'], wi['keypoolsize']]
124 assert_equal(kp_size_before, kp_size_after)
125
126 # refill keypool with three new addresses
127 nodes[0].walletpassphrase('test', 1)
128 nodes[0].keypoolrefill(3)
129
130 # test walletpassphrase timeout
131 # CScheduler relies on condition_variable::wait_until() which does not
132 # guarantee accurate timing. We'll wait up to 5 seconds to execute a 1
133 # second scheduled event.
134 nodes[0].wait_until(lambda: nodes[0].getwalletinfo()["unlocked_until"] == 0, timeout=5)
135
136 # drain the keypool
137 for _ in range(3):
138 nodes[0].getnewaddress()
139 assert_raises_rpc_error(-12, "Keypool ran out", nodes[0].getnewaddress)
140
141 with WalletUnlock(nodes[0], 'test'):
142 nodes[0].keypoolrefill(100)
143 wi = nodes[0].getwalletinfo()
144 assert_equal(wi['keypoolsize_hd_internal'], 400)
145 assert_equal(wi['keypoolsize'], 400)
146
147 # create a blank wallet
148 nodes[0].createwallet(wallet_name='w2', blank=True, disable_private_keys=True)
149 w2 = nodes[0].get_wallet_rpc('w2')
150
151 # refer to initial wallet as w1
152 w1 = nodes[0].get_wallet_rpc(self.default_wallet_name)
153
154 # import private key and fund it
155 address = addr.pop()
156 desc = w1.getaddressinfo(address)['desc']
157 res = w2.importdescriptors([{'desc': desc, 'timestamp': 'now'}])
158 assert_equal(res[0]['success'], True)
159
160 with WalletUnlock(w1, 'test'):
161 res = w1.sendtoaddress(address=address, amount=0.00010000)
162 self.generate(nodes[0], 1)
163 destination = addr.pop()
164
165 # Using a fee rate (10 sat / byte) well above the minimum relay rate
166 # creating a 5,000 sat transaction with change should not be possible
167 assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", w2.walletcreatefundedpsbt, inputs=[], outputs=[{addr.pop(): 0.00005000}], subtractFeeFromOutputs=[0], feeRate=0.00010)
168
169 # creating a 10,000 sat transaction without change, with a manual input, should still be possible
170 res = w2.walletcreatefundedpsbt(inputs=w2.listunspent(), outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.00010)
171 assert_equal("psbt" in res, True)
172
173 # creating a 10,000 sat transaction without change should still be possible
174 res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.00010)
175 assert_equal("psbt" in res, True)
176 # should work without subtractFeeFromOutputs if the exact fee is subtracted from the amount
177 res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008900}], feeRate=0.00010)
178 assert_equal("psbt" in res, True)
179
180 # dust change should be removed
181 res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00008800}], feeRate=0.00010)
182 assert_equal("psbt" in res, True)
183
184 # create a transaction without change at the maximum fee rate, such that the output is still spendable:
185 res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.0008823)
186 assert_equal("psbt" in res, True)
187 assert_equal(res["fee"], Decimal("0.00009706"))
188
189 # creating a 10,000 sat transaction with a manual change address should be possible
190 res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], subtractFeeFromOutputs=[0], feeRate=0.00010, changeAddress=addr.pop())
191 assert_equal("psbt" in res, True)
192
193 if __name__ == '__main__':
194 KeyPoolTest(__file__).main()
195