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