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