2017-07-13 17:49:46 +02:00
#!/usr/bin/env python3
# Copyright (c) 2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-06-15 15:05:32 +02:00
""" Test multiwallet.
Verify that a bitcoind node can load multiple wallet files
"""
2017-07-27 01:57:02 +02:00
import os
2017-10-10 21:27:26 +02:00
import shutil
2017-07-27 01:57:02 +02:00
2017-07-13 17:49:46 +02:00
from test_framework . test_framework import BitcoinTestFramework
2018-03-28 15:37:09 +02:00
from test_framework . test_node import ErrorMatch
2018-01-02 14:57:27 +01:00
from test_framework . util import (
assert_equal ,
assert_raises_rpc_error ,
)
2017-07-13 17:49:46 +02:00
class MultiWalletTest ( BitcoinTestFramework ) :
2017-06-10 00:21:21 +02:00
def set_test_params ( self ) :
2017-07-13 17:49:46 +02:00
self . setup_clean_chain = True
2017-12-14 23:15:18 +01:00
self . num_nodes = 2
2017-12-21 00:37:34 +01:00
self . supports_cli = True
2017-07-13 17:49:46 +02:00
def run_test ( self ) :
2017-12-21 00:37:34 +01:00
node = self . nodes [ 0 ]
data_dir = lambda * p : os . path . join ( node . datadir , ' regtest ' , * p )
wallet_dir = lambda * p : data_dir ( ' wallets ' , * p )
wallet = lambda name : node . get_wallet_rpc ( name )
2017-11-14 19:32:41 +01:00
# check wallet.dat is created
2017-12-14 23:15:18 +01:00
self . stop_nodes ( )
2017-11-14 19:32:41 +01:00
assert_equal ( os . path . isfile ( wallet_dir ( ' wallet.dat ' ) ) , True )
2017-11-15 21:44:36 +01:00
# create symlink to verify wallet directory path can be referenced
# through symlink
os . mkdir ( wallet_dir ( ' w7 ' ) )
os . symlink ( ' w7 ' , wallet_dir ( ' w7_symlink ' ) )
# rename wallet.dat to make sure plain wallet file paths (as opposed to
# directory paths) can be loaded
os . rename ( wallet_dir ( " wallet.dat " ) , wallet_dir ( " w8 " ) )
2017-11-14 19:32:41 +01:00
# restart node with a mix of wallet names:
# w1, w2, w3 - to verify new wallets created when non-existing paths specified
# w - to verify wallet name matching works when one wallet path is prefix of another
# sub/w5 - to verify relative wallet path is created correctly
# extern/w6 - to verify absolute wallet path is created correctly
2017-11-15 21:44:36 +01:00
# w7_symlink - to verify symlinked wallet path is initialized correctly
# w8 - to verify existing wallet file is loaded correctly
# '' - to verify default wallet file is created correctly
wallet_names = [ ' w1 ' , ' w2 ' , ' w3 ' , ' w ' , ' sub/w5 ' , os . path . join ( self . options . tmpdir , ' extern/w6 ' ) , ' w7_symlink ' , ' w8 ' , ' ' ]
2017-11-14 19:32:41 +01:00
extra_args = [ ' -wallet= {} ' . format ( n ) for n in wallet_names ]
self . start_node ( 0 , extra_args )
assert_equal ( set ( node . listwallets ( ) ) , set ( wallet_names ) )
# check that all requested wallets were created
self . stop_node ( 0 )
for wallet_name in wallet_names :
2017-11-15 21:44:36 +01:00
if os . path . isdir ( wallet_dir ( wallet_name ) ) :
assert_equal ( os . path . isfile ( wallet_dir ( wallet_name , " wallet.dat " ) ) , True )
else :
assert_equal ( os . path . isfile ( wallet_dir ( wallet_name ) ) , True )
2017-11-14 19:32:41 +01:00
# should not initialize if wallet path can't be created
2018-03-19 20:35:04 +01:00
exp_stderr = " boost::filesystem::create_directory: (The system cannot find the path specified|Not a directory): "
2018-03-28 15:37:09 +02:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -wallet=wallet.dat/bad ' ] , exp_stderr , match = ErrorMatch . PARTIAL_REGEX )
2017-07-27 01:56:30 +02:00
2018-02-07 15:36:13 +01:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -walletdir=wallets ' ] , ' Error: Specified -walletdir " wallets " does not exist ' )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -walletdir=wallets ' ] , ' Error: Specified -walletdir " wallets " is a relative path ' , cwd = data_dir ( ) )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -walletdir=debug.log ' ] , ' Error: Specified -walletdir " debug.log " is not a directory ' , cwd = data_dir ( ) )
2018-01-18 19:15:00 +01:00
2017-07-27 01:56:30 +02:00
# should not initialize if there are duplicate wallets
2018-02-07 16:38:25 +01:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -wallet=w1 ' , ' -wallet=w1 ' ] , ' Error: Error loading wallet w1. Duplicate -wallet filename specified. ' )
2017-07-27 01:56:30 +02:00
2017-10-10 21:27:26 +02:00
# should not initialize if one wallet is a copy of another
2017-11-15 21:44:36 +01:00
shutil . copyfile ( wallet_dir ( ' w8 ' ) , wallet_dir ( ' w8_copy ' ) )
2017-12-08 12:39:22 +01:00
exp_stderr = " BerkeleyBatch: Can ' t open database w8_copy \ (duplicates fileid \ w+ from w8 \ ) "
2018-03-28 15:37:09 +02:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -wallet=w8 ' , ' -wallet=w8_copy ' ] , exp_stderr , match = ErrorMatch . PARTIAL_REGEX )
2017-10-10 21:27:26 +02:00
2017-07-27 01:57:02 +02:00
# should not initialize if wallet file is a symlink
2017-11-15 21:44:36 +01:00
os . symlink ( ' w8 ' , wallet_dir ( ' w8_symlink ' ) )
2018-03-28 15:37:09 +02:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -wallet=w8_symlink ' ] , ' Error: Invalid -wallet path \' w8_symlink \' \ . .* ' , match = ErrorMatch . FULL_REGEX )
2017-07-27 01:57:02 +02:00
2017-10-09 10:32:07 +02:00
# should not initialize if the specified walletdir does not exist
2018-02-07 15:36:13 +01:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -walletdir=bad ' ] , ' Error: Specified -walletdir " bad " does not exist ' )
2017-11-18 01:36:37 +01:00
# should not initialize if the specified walletdir is not a directory
2017-12-21 00:37:34 +01:00
not_a_dir = wallet_dir ( ' notadir ' )
2018-06-12 17:49:20 +02:00
open ( not_a_dir , ' a ' , encoding = " utf8 " ) . close ( )
2018-03-28 15:37:09 +02:00
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -walletdir= ' + not_a_dir ] , ' Error: Specified -walletdir " ' + not_a_dir + ' " is not a directory ' )
2017-10-09 10:32:07 +02:00
2018-04-19 17:13:47 +02:00
self . log . info ( " Do not allow -zapwallettxes with multiwallet " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -zapwallettxes ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -zapwallettxes is only allowed with a single wallet file " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -zapwallettxes=1 ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -zapwallettxes is only allowed with a single wallet file " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -zapwallettxes=2 ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -zapwallettxes is only allowed with a single wallet file " )
self . log . info ( " Do not allow -salvagewallet with multiwallet " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -salvagewallet ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -salvagewallet is only allowed with a single wallet file " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -salvagewallet=1 ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -salvagewallet is only allowed with a single wallet file " )
self . log . info ( " Do not allow -upgradewallet with multiwallet " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -upgradewallet ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -upgradewallet is only allowed with a single wallet file " )
self . nodes [ 0 ] . assert_start_raises_init_error ( [ ' -upgradewallet=1 ' , ' -wallet=w1 ' , ' -wallet=w2 ' ] , " Error: -upgradewallet is only allowed with a single wallet file " )
2017-10-12 11:04:46 +02:00
# if wallets/ doesn't exist, datadir should be the default wallet dir
2017-12-21 00:37:34 +01:00
wallet_dir2 = data_dir ( ' walletdir ' )
os . rename ( wallet_dir ( ) , wallet_dir2 )
2017-10-12 11:04:46 +02:00
self . start_node ( 0 , [ ' -wallet=w4 ' , ' -wallet=w5 ' ] )
2017-12-21 00:37:34 +01:00
assert_equal ( set ( node . listwallets ( ) ) , { " w4 " , " w5 " } )
w5 = wallet ( " w5 " )
2017-10-12 11:04:46 +02:00
w5 . generate ( 1 )
# now if wallets/ exists again, but the rootdir is specified as the walletdir, w4 and w5 should still be loaded
2017-12-21 00:37:34 +01:00
os . rename ( wallet_dir2 , wallet_dir ( ) )
2017-12-14 23:15:18 +01:00
self . restart_node ( 0 , [ ' -wallet=w4 ' , ' -wallet=w5 ' , ' -walletdir= ' + data_dir ( ) ] )
2017-12-21 00:37:34 +01:00
assert_equal ( set ( node . listwallets ( ) ) , { " w4 " , " w5 " } )
w5 = wallet ( " w5 " )
2017-10-09 10:32:07 +02:00
w5_info = w5 . getwalletinfo ( )
2017-10-12 11:04:46 +02:00
assert_equal ( w5_info [ ' immature_balance ' ] , 50 )
2017-10-09 10:32:07 +02:00
2017-12-14 23:15:18 +01:00
competing_wallet_dir = os . path . join ( self . options . tmpdir , ' competing_walletdir ' )
os . mkdir ( competing_wallet_dir )
2018-02-07 16:38:25 +01:00
self . restart_node ( 0 , [ ' -walletdir= ' + competing_wallet_dir ] )
exp_stderr = " Error: Error initializing wallet database environment \" \ S+competing_walletdir \" ! "
2018-03-28 15:37:09 +02:00
self . nodes [ 1 ] . assert_start_raises_init_error ( [ ' -walletdir= ' + competing_wallet_dir ] , exp_stderr , match = ErrorMatch . PARTIAL_REGEX )
2017-10-09 10:32:07 +02:00
2017-11-14 19:32:41 +01:00
self . restart_node ( 0 , extra_args )
2017-07-27 01:56:30 +02:00
2017-11-14 19:32:41 +01:00
wallets = [ wallet ( w ) for w in wallet_names ]
2017-12-21 00:37:34 +01:00
wallet_bad = wallet ( " bad " )
2017-06-02 20:30:36 +02:00
2017-11-14 19:32:41 +01:00
# check wallet names and balances
wallets [ 0 ] . generate ( 1 )
for wallet_name , wallet in zip ( wallet_names , wallets ) :
info = wallet . getwalletinfo ( )
assert_equal ( info [ ' immature_balance ' ] , 50 if wallet is wallets [ 0 ] else 0 )
assert_equal ( info [ ' walletname ' ] , wallet_name )
2017-07-13 17:49:46 +02:00
2017-07-17 11:42:30 +02:00
# accessing invalid wallet fails
2017-07-12 16:33:46 +02:00
assert_raises_rpc_error ( - 18 , " Requested wallet does not exist or is not loaded " , wallet_bad . getwalletinfo )
2017-07-17 11:42:30 +02:00
2017-06-15 15:05:32 +02:00
# accessing wallet RPC without using wallet endpoint fails
2017-12-21 00:37:34 +01:00
assert_raises_rpc_error ( - 19 , " Wallet file not specified " , node . getwalletinfo )
2017-07-13 17:49:46 +02:00
2017-11-14 19:32:41 +01:00
w1 , w2 , w3 , w4 , * _ = wallets
2017-07-13 17:49:46 +02:00
w1 . generate ( 101 )
assert_equal ( w1 . getbalance ( ) , 100 )
assert_equal ( w2 . getbalance ( ) , 0 )
assert_equal ( w3 . getbalance ( ) , 0 )
2017-11-15 17:03:49 +01:00
assert_equal ( w4 . getbalance ( ) , 0 )
2017-07-13 17:49:46 +02:00
w1 . sendtoaddress ( w2 . getnewaddress ( ) , 1 )
w1 . sendtoaddress ( w3 . getnewaddress ( ) , 2 )
2017-11-15 17:03:49 +01:00
w1 . sendtoaddress ( w4 . getnewaddress ( ) , 3 )
2017-07-13 17:49:46 +02:00
w1 . generate ( 1 )
assert_equal ( w2 . getbalance ( ) , 1 )
assert_equal ( w3 . getbalance ( ) , 2 )
2017-11-15 17:03:49 +01:00
assert_equal ( w4 . getbalance ( ) , 3 )
2017-07-13 17:49:46 +02:00
2017-09-07 23:40:25 +02:00
batch = w1 . batch ( [ w1 . getblockchaininfo . get_request ( ) , w1 . getwalletinfo . get_request ( ) ] )
assert_equal ( batch [ 0 ] [ " result " ] [ " chain " ] , " regtest " )
assert_equal ( batch [ 1 ] [ " result " ] [ " walletname " ] , " w1 " )
2018-04-07 18:12:46 +02:00
self . log . info ( ' Check for per-wallet settxfee call ' )
assert_equal ( w1 . getwalletinfo ( ) [ ' paytxfee ' ] , 0 )
assert_equal ( w2 . getwalletinfo ( ) [ ' paytxfee ' ] , 0 )
w2 . settxfee ( 4.0 )
assert_equal ( w1 . getwalletinfo ( ) [ ' paytxfee ' ] , 0 )
assert_equal ( w2 . getwalletinfo ( ) [ ' paytxfee ' ] , 4.0 )
2018-04-18 22:32:16 +02:00
self . log . info ( " Test dynamic wallet loading " )
self . restart_node ( 0 , [ ' -nowallet ' ] )
assert_equal ( node . listwallets ( ) , [ ] )
assert_raises_rpc_error ( - 32601 , " Method not found " , node . getwalletinfo )
self . log . info ( " Load first wallet " )
loadwallet_name = node . loadwallet ( wallet_names [ 0 ] )
assert_equal ( loadwallet_name [ ' name ' ] , wallet_names [ 0 ] )
assert_equal ( node . listwallets ( ) , wallet_names [ 0 : 1 ] )
node . getwalletinfo ( )
w1 = node . get_wallet_rpc ( wallet_names [ 0 ] )
w1 . getwalletinfo ( )
self . log . info ( " Load second wallet " )
loadwallet_name = node . loadwallet ( wallet_names [ 1 ] )
assert_equal ( loadwallet_name [ ' name ' ] , wallet_names [ 1 ] )
assert_equal ( node . listwallets ( ) , wallet_names [ 0 : 2 ] )
assert_raises_rpc_error ( - 19 , " Wallet file not specified " , node . getwalletinfo )
w2 = node . get_wallet_rpc ( wallet_names [ 1 ] )
w2 . getwalletinfo ( )
self . log . info ( " Load remaining wallets " )
for wallet_name in wallet_names [ 2 : ] :
loadwallet_name = self . nodes [ 0 ] . loadwallet ( wallet_name )
assert_equal ( loadwallet_name [ ' name ' ] , wallet_name )
assert_equal ( set ( self . nodes [ 0 ] . listwallets ( ) ) , set ( wallet_names ) )
# Fail to load if wallet doesn't exist
assert_raises_rpc_error ( - 18 , ' Wallet wallets not found. ' , self . nodes [ 0 ] . loadwallet , ' wallets ' )
# Fail to load duplicate wallets
assert_raises_rpc_error ( - 4 , ' Wallet file verification failed: Error loading wallet w1. Duplicate -wallet filename specified. ' , self . nodes [ 0 ] . loadwallet , wallet_names [ 0 ] )
# Fail to load if one wallet is a copy of another
assert_raises_rpc_error ( - 1 , " BerkeleyBatch: Can ' t open database w8_copy (duplicates fileid " , self . nodes [ 0 ] . loadwallet , ' w8_copy ' )
# Fail to load if wallet file is a symlink
assert_raises_rpc_error ( - 4 , " Wallet file verification failed: Invalid -wallet path ' w8_symlink ' " , self . nodes [ 0 ] . loadwallet , ' w8_symlink ' )
2018-04-23 19:11:53 +02:00
self . log . info ( " Test dynamic wallet creation. " )
# Fail to create a wallet if it already exists.
assert_raises_rpc_error ( - 4 , " Wallet w2 already exists. " , self . nodes [ 0 ] . createwallet , ' w2 ' )
# Successfully create a wallet with a new name
loadwallet_name = self . nodes [ 0 ] . createwallet ( ' w9 ' )
assert_equal ( loadwallet_name [ ' name ' ] , ' w9 ' )
w9 = node . get_wallet_rpc ( ' w9 ' )
assert_equal ( w9 . getwalletinfo ( ) [ ' walletname ' ] , ' w9 ' )
assert ' w9 ' in self . nodes [ 0 ] . listwallets ( )
# Successfully create a wallet using a full path
new_wallet_dir = os . path . join ( self . options . tmpdir , ' new_walletdir ' )
new_wallet_name = os . path . join ( new_wallet_dir , ' w10 ' )
loadwallet_name = self . nodes [ 0 ] . createwallet ( new_wallet_name )
assert_equal ( loadwallet_name [ ' name ' ] , new_wallet_name )
w10 = node . get_wallet_rpc ( new_wallet_name )
assert_equal ( w10 . getwalletinfo ( ) [ ' walletname ' ] , new_wallet_name )
assert new_wallet_name in self . nodes [ 0 ] . listwallets ( )
2018-06-02 00:24:29 +02:00
self . log . info ( " Test dynamic wallet unloading " )
# Test `unloadwallet` errors
assert_raises_rpc_error ( - 1 , " JSON value is not a string as expected " , self . nodes [ 0 ] . unloadwallet )
assert_raises_rpc_error ( - 18 , " Requested wallet does not exist or is not loaded " , self . nodes [ 0 ] . unloadwallet , " dummy " )
assert_raises_rpc_error ( - 18 , " Requested wallet does not exist or is not loaded " , node . get_wallet_rpc ( " dummy " ) . unloadwallet )
assert_raises_rpc_error ( - 8 , " Cannot unload the requested wallet " , w1 . unloadwallet , " w2 " ) ,
# Successfully unload the specified wallet name
self . nodes [ 0 ] . unloadwallet ( " w1 " )
assert ' w1 ' not in self . nodes [ 0 ] . listwallets ( )
# Successfully unload the wallet referenced by the request endpoint
w2 . unloadwallet ( )
assert ' w2 ' not in self . nodes [ 0 ] . listwallets ( )
# Successfully unload all wallets
for wallet_name in self . nodes [ 0 ] . listwallets ( ) :
self . nodes [ 0 ] . unloadwallet ( wallet_name )
assert_equal ( self . nodes [ 0 ] . listwallets ( ) , [ ] )
2018-06-12 16:30:32 +02:00
assert_raises_rpc_error ( - 32601 , " Method not found (wallet method is disabled because no wallet is loaded) " , self . nodes [ 0 ] . getwalletinfo )
2018-06-02 00:24:29 +02:00
2018-06-05 00:15:03 +02:00
# Successfully load a previously unloaded wallet
self . nodes [ 0 ] . loadwallet ( ' w1 ' )
assert_equal ( self . nodes [ 0 ] . listwallets ( ) , [ ' w1 ' ] )
assert_equal ( w1 . getwalletinfo ( ) [ ' walletname ' ] , ' w1 ' )
2017-07-13 17:49:46 +02:00
if __name__ == ' __main__ ' :
MultiWalletTest ( ) . main ( )