wallet_createwallet.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2018-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 createwallet arguments.
   6  """
   7  import os
   8  import stat
   9  
  10  from test_framework.descriptors import descsum_create
  11  from test_framework.extendedkey import ExtendedPrivateKey
  12  from test_framework.test_framework import BitcoinTestFramework
  13  from test_framework.util import (
  14      assert_equal,
  15      assert_raises_rpc_error,
  16      is_dir_writable,
  17      wallet_importprivkey,
  18  )
  19  from test_framework.wallet_util import generate_keypair, WalletUnlock
  20  
  21  
  22  EMPTY_PASSPHRASE_MSG = "Empty string given as passphrase, wallet will not be encrypted."
  23  
  24  
  25  class CreateWalletTest(BitcoinTestFramework):
  26      def set_test_params(self):
  27          self.num_nodes = 1
  28  
  29      def skip_test_if_missing_module(self):
  30          self.skip_if_no_wallet()
  31  
  32      def test_bad_dir_permissions(self, node):
  33          self.log.info("Test wallet creation failure due to non-writable directory")
  34          wallet_name = "bad_permissions"
  35          dir_path = node.wallets_path / wallet_name
  36          dir_path.mkdir(parents=True)
  37          original_dir_perms = dir_path.stat().st_mode
  38          os.chmod(dir_path, original_dir_perms & ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH))
  39          if is_dir_writable(dir_path):
  40              self.log.warning("Skipping non-writable directory test: unable to enforce read-only permissions")
  41          else:
  42              # Run actual test
  43              assert_raises_rpc_error(-4, f"SQLiteDatabase: Failed to open database in directory '{str(dir_path)}': directory is not writable", node.createwallet, wallet_name=wallet_name)
  44          # Reset directory permissions for cleanup
  45          dir_path.chmod(original_dir_perms)
  46  
  47  
  48      def run_test(self):
  49          node = self.nodes[0]
  50  
  51          self.test_bad_dir_permissions(node)
  52  
  53          self.log.info("Run createwallet with invalid parameters.")
  54          # Run createwallet with invalid parameters. This must not prevent a new wallet with the same name from being created with the correct parameters.
  55          assert_raises_rpc_error(-4, "Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.",
  56              self.nodes[0].createwallet, wallet_name='w0', disable_private_keys=True, passphrase="passphrase")
  57          assert_raises_rpc_error(-8, "Wallet name cannot be empty", self.nodes[0].createwallet, "")
  58  
  59          self.nodes[0].createwallet(wallet_name='w0')
  60          w0 = node.get_wallet_rpc('w0')
  61          address1 = w0.getnewaddress()
  62  
  63          self.log.info("Test disableprivatekeys creation.")
  64          self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True)
  65          w1 = node.get_wallet_rpc('w1')
  66          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getnewaddress)
  67          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress)
  68          import_res = w1.importdescriptors([{"desc": w0.getaddressinfo(address1)['desc'], "timestamp": "now"}])
  69          assert_equal(import_res[0]["success"], True)
  70          assert_equal(sorted(w1.getwalletinfo()["flags"]), sorted(["last_hardened_xpub_cached", "descriptor_wallet", "disable_private_keys"]))
  71  
  72          self.log.info('Test that private keys cannot be imported')
  73          privkey, pubkey = generate_keypair(wif=True)
  74          result = w1.importdescriptors([{'desc': descsum_create('wpkh(' + privkey + ')'), 'timestamp': 'now'}])
  75          assert not result[0]['success']
  76          assert 'warnings' not in result[0]
  77          assert_equal(result[0]['error']['code'], -4)
  78          assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled')
  79  
  80          self.log.info("Test blank creation with private keys disabled.")
  81          self.nodes[0].createwallet(wallet_name='w2', disable_private_keys=True, blank=True)
  82          w2 = node.get_wallet_rpc('w2')
  83          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getnewaddress)
  84          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getrawchangeaddress)
  85          import_res = w2.importdescriptors([{"desc": w0.getaddressinfo(address1)['desc'], "timestamp": "now"}])
  86          assert_equal(import_res[0]["success"], True)
  87  
  88          self.log.info("Test blank creation with private keys enabled.")
  89          self.nodes[0].createwallet(wallet_name='w3', disable_private_keys=False, blank=True)
  90          w3 = node.get_wallet_rpc('w3')
  91          assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
  92          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
  93          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress)
  94          # Import private key
  95          wallet_importprivkey(w3, generate_keypair(wif=True)[0], "now")
  96          # Imported private keys are currently ignored by the keypool
  97          assert_equal(w3.getwalletinfo()['keypoolsize'], 0)
  98          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress)
  99          # Set the seed
 100          w3.importdescriptors([{
 101              'desc': descsum_create(f'wpkh({ExtendedPrivateKey.generate().to_string()}/0h/*)'),
 102              'timestamp': 'now',
 103              'active': True
 104          },
 105          {
 106              'desc': descsum_create(f'wpkh({ExtendedPrivateKey.generate().to_string()}/1h/*)'),
 107              'timestamp': 'now',
 108              'active': True,
 109              'internal': True
 110          }])
 111          assert_equal(w3.getwalletinfo()['keypoolsize'], 1)
 112          w3.getnewaddress()
 113          w3.getrawchangeaddress()
 114  
 115          self.log.info("Test blank creation with privkeys enabled and then encryption")
 116          self.nodes[0].createwallet(wallet_name='w4', disable_private_keys=False, blank=True)
 117          w4 = node.get_wallet_rpc('w4')
 118          assert_equal(w4.getwalletinfo()['keypoolsize'], 0)
 119          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
 120          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
 121          # Encrypt the wallet. Nothing should change about the keypool
 122          w4.encryptwallet('pass')
 123          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress)
 124          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress)
 125          with WalletUnlock(w4, "pass"):
 126              # Now set a seed and it should work. Wallet should also be encrypted
 127              w4.importdescriptors([{
 128                  'desc': descsum_create(f'wpkh({ExtendedPrivateKey.generate().to_string()}/0h/*)'),
 129                  'timestamp': 'now',
 130                  'active': True
 131              },
 132              {
 133                  'desc': descsum_create(f'wpkh({ExtendedPrivateKey.generate().to_string()}/1h/*)'),
 134                  'timestamp': 'now',
 135                  'active': True,
 136                  'internal': True
 137              }])
 138              w4.getnewaddress()
 139              w4.getrawchangeaddress()
 140  
 141          self.log.info("Test blank creation with privkeys disabled and then encryption")
 142          self.nodes[0].createwallet(wallet_name='w5', disable_private_keys=True, blank=True)
 143          w5 = node.get_wallet_rpc('w5')
 144          assert_equal(w5.getwalletinfo()['keypoolsize'], 0)
 145          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
 146          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
 147          # Encrypt the wallet
 148          assert_raises_rpc_error(-16, "Error: wallet does not contain private keys, nothing to encrypt.", w5.encryptwallet, 'pass')
 149          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress)
 150          assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress)
 151  
 152          self.log.info('New blank and encrypted wallets can be created')
 153          self.nodes[0].createwallet(wallet_name='wblank', disable_private_keys=False, blank=True, passphrase='thisisapassphrase')
 154          wblank = node.get_wallet_rpc('wblank')
 155          assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", wblank.signmessage, "needanargument", "test")
 156          with WalletUnlock(wblank, "thisisapassphrase"):
 157              assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getnewaddress)
 158              assert_raises_rpc_error(-4, "Error: This wallet has no available keys", wblank.getrawchangeaddress)
 159  
 160          self.log.info('Test creating a new encrypted wallet.')
 161          # Born encrypted wallet is created (has keys)
 162          self.nodes[0].createwallet(wallet_name='w6', disable_private_keys=False, blank=False, passphrase='thisisapassphrase')
 163          w6 = node.get_wallet_rpc('w6')
 164          assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", w6.signmessage, "needanargument", "test")
 165          with WalletUnlock(w6, "thisisapassphrase"):
 166              w6.signmessage(w6.getnewaddress('', 'legacy'), "test")
 167              w6.keypoolrefill(1)
 168              # There should only be 1 key for legacy, 3 for descriptors
 169              walletinfo = w6.getwalletinfo()
 170              keys = 4
 171              assert_equal(walletinfo['keypoolsize'], keys)
 172              assert_equal(walletinfo['keypoolsize_hd_internal'], keys)
 173          # Allow empty passphrase, but there should be a warning
 174          resp = self.nodes[0].createwallet(wallet_name='w7', disable_private_keys=False, blank=False, passphrase='')
 175          assert_equal(resp["warnings"], [EMPTY_PASSPHRASE_MSG])
 176  
 177          w7 = node.get_wallet_rpc('w7')
 178          assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60)
 179  
 180          self.log.info('Test making a wallet with avoid reuse flag')
 181          self.nodes[0].createwallet('w8', False, False, '', True) # Use positional arguments to check for bug where avoid_reuse could not be set for wallets without needing them to be encrypted
 182          w8 = node.get_wallet_rpc('w8')
 183          assert_raises_rpc_error(-15, 'Error: running with an unencrypted wallet, but walletpassphrase was called.', w7.walletpassphrase, '', 60)
 184          assert_equal(w8.getwalletinfo()["avoid_reuse"], True)
 185  
 186          self.log.info('Using a passphrase with private keys disabled returns error')
 187          assert_raises_rpc_error(-4, 'Passphrase provided but private keys are disabled. A passphrase is only used to encrypt private keys, so cannot be used for wallets with private keys disabled.', self.nodes[0].createwallet, wallet_name='w9', disable_private_keys=True, passphrase='thisisapassphrase')
 188  
 189          self.log.info("Test that legacy wallets cannot be created")
 190          assert_raises_rpc_error(-4, 'descriptors argument must be set to "true"; it is no longer possible to create a legacy wallet.', self.nodes[0].createwallet, wallet_name="legacy", descriptors=False)
 191  
 192          self.log.info("Check that the version number is being logged correctly")
 193  
 194          # Craft the expected version message.
 195          client_version = node.getnetworkinfo()["version"]
 196          version_message = f"Last client version = {client_version}"
 197  
 198          # Should not be logged when creating.
 199          with node.assert_debug_log(expected_msgs=[], unexpected_msgs=[version_message]):
 200              node.createwallet("version_check")
 201              node.unloadwallet("version_check")
 202          # Should be logged when loading.
 203          with node.assert_debug_log(expected_msgs=[version_message]):
 204              node.loadwallet("version_check")
 205  
 206  
 207  if __name__ == '__main__':
 208      CreateWalletTest(__file__).main()
 209