diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -16,6 +16,15 @@ - An `initialblockdownload` boolean has been added to the `getblockchaininfo` RPC to indicate whether the node is currently in IBD or not. - The '-usehd' option has been removed. It is no longer possible to create a non HD wallet. +External wallet files +--------------------- + +The `-wallet=` option now accepts full paths instead of requiring wallets +to be located in the -walletdir directory. When wallets are located in +different directories, wallet data will be stored independently, so data from +every wallet is not mixed into the same /database/log.?????????? +files. + Transaction index changes ------------------------- diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -91,11 +91,11 @@ "until EOF/Ctrl-D (recommended for sensitive information " "such as passphrases)"), false, OptionsCategory::OPTIONS); - gArgs.AddArg("-rpcwallet=", - _("Send RPC for non-default wallet on RPC server (argument is " - "wallet filename in bitcoind directory, required if " - "bitcoind/-Qt runs with multiple wallets)"), - false, OptionsCategory::OPTIONS); + gArgs.AddArg( + "-rpcwallet=", + _("Send RPC for non-default wallet on RPC server (needs to exactly " + "match corresponding -wallet option passed to bitcoind)"), + false, OptionsCategory::OPTIONS); } ////////////////////////////////////////////////////////////////////////////// diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -136,6 +136,7 @@ boost::this_thread::interruption_point(); fs::path pathIn = strPath; + TryCreateDirectories(pathIn); if (!LockDirectory(pathIn, ".walletlock")) { LogPrintf("Cannot obtain a lock on wallet directory %s. Another " "instance of bitcoin may be using it.\n", diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -88,10 +88,13 @@ gArgs.AddArg("-upgradewallet", _("Upgrade wallet to latest format on startup"), false, OptionsCategory::WALLET); - gArgs.AddArg("-wallet=", - _("Specify wallet file (within data directory)") + " " + - strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT), - false, OptionsCategory::WALLET); + gArgs.AddArg( + "-wallet=", + _("Specify wallet database path. Can be specified multiple times to " + "load multiple wallets. Path is interpreted relative to " + "if it is not absolute, and will be created if it does not exist.") + + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT), + false, OptionsCategory::WALLET); gArgs.AddArg("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %d)"), DEFAULT_WALLETBROADCAST), @@ -323,19 +326,6 @@ std::set wallet_paths; for (const std::string &walletFile : gArgs.GetArgs("-wallet")) { - if (fs::path(walletFile).filename() != walletFile) { - return InitError( - strprintf(_("Error loading wallet %s. -wallet parameter must " - "only specify a filename (not a path)."), - walletFile)); - } - - if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { - return InitError(strprintf(_("Error loading wallet %s. Invalid " - "characters in -wallet filename."), - walletFile)); - } - fs::path wallet_path = fs::absolute(walletFile, GetWalletDir()); if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -20,8 +20,6 @@ def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 2 - self.extra_args = [ - ['-wallet=w1', '-wallet=w2', '-wallet=w3', '-wallet=w'], []] self.supports_cli = True def run_test(self): @@ -32,9 +30,30 @@ def wallet(name): return node.get_wallet_rpc(name) - assert_equal(set(node.listwallets()), {"w1", "w2", "w3", "w"}) - + # check wallet.dat is created self.stop_nodes() + assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True) + + # 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 + # wallet.dat - to verify existing wallet file is loaded correctly + wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', + os.path.join(self.options.tmpdir, 'extern/w6'), 'wallet.dat'] + 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: + assert_equal(os.path.isfile(wallet_dir(wallet_name)), True) + + # should not initialize if wallet path can't be created + self.assert_start_raises_init_error( + 0, ['-wallet=wallet.dat/bad'], 'File exists') self.assert_start_raises_init_error( 0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist') @@ -95,15 +114,18 @@ self.assert_start_raises_init_error( 1, ['-walletdir='+competing_wallet_dir], 'Error initializing wallet database environment') - self.restart_node(0, self.extra_args[0]) + self.restart_node(0, extra_args) - w1 = wallet("w1") - w2 = wallet("w2") - w3 = wallet("w3") - w4 = wallet("w") + wallets = [wallet(w) for w in wallet_names] wallet_bad = wallet("bad") - w1.generate(1) + # 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) # accessing invalid wallet fails assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", @@ -113,24 +135,7 @@ assert_raises_rpc_error(-19, "Wallet file not specified (must request wallet RPC through /wallet/ uri-path).", node.getwalletinfo) - # check w1 wallet balance - w1_info = w1.getwalletinfo() - assert_equal(w1_info['immature_balance'], 50) - w1_name = w1_info['walletname'] - assert_equal(w1_name, "w1") - - # check w2 wallet balance - w2_info = w2.getwalletinfo() - assert_equal(w2_info['immature_balance'], 0) - w2_name = w2_info['walletname'] - assert_equal(w2_name, "w2") - - w3_name = w3.getwalletinfo()['walletname'] - assert_equal(w3_name, "w3") - - w4_name = w4.getwalletinfo()['walletname'] - assert_equal(w4_name, "w") - + w1, w2, w3, w4, *_ = wallets w1.generate(101) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0)