wallet_createwallet.py raw

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