Skip to content

Commit 26c06f2

Browse files
committed
Allow wallet files not in -walletdir directory
Remove restriction that -wallet filenames can only refer to files in the -walletdir directory.
1 parent d8a99f6 commit 26c06f2

File tree

5 files changed

+42
-37
lines changed

5 files changed

+42
-37
lines changed

doc/release-notes.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ RPC changes
6363

6464
- The `fundrawtransaction` rpc will reject the previously deprecated `reserveChangeKey` option.
6565

66+
External wallet files
67+
---------------------
68+
69+
The `-wallet=<path>` option now accepts full paths instead of requiring wallets
70+
to be located in the -walletdir directory. When wallets are located in
71+
different directories, wallet data will be stored independently, so data from
72+
every wallet is not mixed into the same <walletdir>/database/log.??????????
73+
files.
74+
6675
Credits
6776
=======
6877

src/bitcoin-cli.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ std::string HelpMessageCli()
4646
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()));
4747
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
4848
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
49-
strUsage += HelpMessageOpt("-rpcwallet=<walletname>", _("Send RPC for non-default wallet on RPC server (argument is wallet filename in bitcoind directory, required if bitcoind/-Qt runs with multiple wallets)"));
49+
strUsage += HelpMessageOpt("-rpcwallet=<walletname>", _("Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind)"));
5050
strUsage += HelpMessageOpt("-stdin", _("Read extra arguments from standard input, one per line until EOF/Ctrl-D (recommended for sensitive information such as passphrases). When combined with -stdinrpcpass, the first line from standard input is used for the RPC password."));
5151
strUsage += HelpMessageOpt("-stdinrpcpass", strprintf(_("Read RPC password from standard input as a single line. When combined with -stdin, the first line from standard input is used for the RPC password.")));
5252

src/wallet/db.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ bool CDBEnv::Open(bool retry)
122122
boost::this_thread::interruption_point();
123123

124124
fs::path pathIn = strPath;
125+
TryCreateDirectories(pathIn);
125126
if (!LockDirectory(pathIn, ".walletlock")) {
126127
LogPrintf("Cannot obtain a lock on wallet directory %s. Another instance of bitcoin may be using it.\n", strPath);
127128
return false;

src/wallet/init.cpp

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ std::string GetWalletHelpString(bool showDebug)
3535
strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE));
3636
strUsage += HelpMessageOpt("-txconfirmtarget=<n>", strprintf(_("If paytxfee is not set, include enough fee so transactions begin confirmation on average within n blocks (default: %u)"), DEFAULT_TX_CONFIRM_TARGET));
3737
strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup"));
38-
strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
38+
strUsage += HelpMessageOpt("-wallet=<path>", _("Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist.") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
3939
strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), DEFAULT_WALLETBROADCAST));
4040
strUsage += HelpMessageOpt("-walletdir=<dir>", _("Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)"));
4141
strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)"));
@@ -230,14 +230,6 @@ bool VerifyWallets()
230230
std::set<fs::path> wallet_paths;
231231

232232
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
233-
if (boost::filesystem::path(walletFile).filename() != walletFile) {
234-
return InitError(strprintf(_("Error loading wallet %s. -wallet parameter must only specify a filename (not a path)."), walletFile));
235-
}
236-
237-
if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) {
238-
return InitError(strprintf(_("Error loading wallet %s. Invalid characters in -wallet filename."), walletFile));
239-
}
240-
241233
fs::path wallet_path = fs::absolute(walletFile, GetWalletDir());
242234

243235
if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) {

test/functional/wallet_multiwallet.py

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class MultiWalletTest(BitcoinTestFramework):
1616
def set_test_params(self):
1717
self.setup_clean_chain = True
1818
self.num_nodes = 2
19-
self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3', '-wallet=w'], []]
2019
self.supports_cli = True
2120

2221
def run_test(self):
@@ -26,9 +25,28 @@ def run_test(self):
2625
wallet_dir = lambda *p: data_dir('wallets', *p)
2726
wallet = lambda name: node.get_wallet_rpc(name)
2827

29-
assert_equal(set(node.listwallets()), {"w1", "w2", "w3", "w"})
30-
28+
# check wallet.dat is created
3129
self.stop_nodes()
30+
assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True)
31+
32+
# restart node with a mix of wallet names:
33+
# w1, w2, w3 - to verify new wallets created when non-existing paths specified
34+
# w - to verify wallet name matching works when one wallet path is prefix of another
35+
# sub/w5 - to verify relative wallet path is created correctly
36+
# extern/w6 - to verify absolute wallet path is created correctly
37+
# wallet.dat - to verify existing wallet file is loaded correctly
38+
wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5', os.path.join(self.options.tmpdir, 'extern/w6'), 'wallet.dat']
39+
extra_args = ['-wallet={}'.format(n) for n in wallet_names]
40+
self.start_node(0, extra_args)
41+
assert_equal(set(node.listwallets()), set(wallet_names))
42+
43+
# check that all requested wallets were created
44+
self.stop_node(0)
45+
for wallet_name in wallet_names:
46+
assert_equal(os.path.isfile(wallet_dir(wallet_name)), True)
47+
48+
# should not initialize if wallet path can't be created
49+
self.assert_start_raises_init_error(0, ['-wallet=wallet.dat/bad'], 'File exists')
3250

3351
self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
3452
self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
@@ -77,40 +95,25 @@ def run_test(self):
7795
self.restart_node(0, ['-walletdir='+competing_wallet_dir])
7896
self.assert_start_raises_init_error(1, ['-walletdir='+competing_wallet_dir], 'Error initializing wallet database environment')
7997

80-
self.restart_node(0, self.extra_args[0])
98+
self.restart_node(0, extra_args)
8199

82-
w1 = wallet("w1")
83-
w2 = wallet("w2")
84-
w3 = wallet("w3")
85-
w4 = wallet("w")
100+
wallets = [wallet(w) for w in wallet_names]
86101
wallet_bad = wallet("bad")
87102

88-
w1.generate(1)
103+
# check wallet names and balances
104+
wallets[0].generate(1)
105+
for wallet_name, wallet in zip(wallet_names, wallets):
106+
info = wallet.getwalletinfo()
107+
assert_equal(info['immature_balance'], 50 if wallet is wallets[0] else 0)
108+
assert_equal(info['walletname'], wallet_name)
89109

90110
# accessing invalid wallet fails
91111
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo)
92112

93113
# accessing wallet RPC without using wallet endpoint fails
94114
assert_raises_rpc_error(-19, "Wallet file not specified", node.getwalletinfo)
95115

96-
# check w1 wallet balance
97-
w1_info = w1.getwalletinfo()
98-
assert_equal(w1_info['immature_balance'], 50)
99-
w1_name = w1_info['walletname']
100-
assert_equal(w1_name, "w1")
101-
102-
# check w2 wallet balance
103-
w2_info = w2.getwalletinfo()
104-
assert_equal(w2_info['immature_balance'], 0)
105-
w2_name = w2_info['walletname']
106-
assert_equal(w2_name, "w2")
107-
108-
w3_name = w3.getwalletinfo()['walletname']
109-
assert_equal(w3_name, "w3")
110-
111-
w4_name = w4.getwalletinfo()['walletname']
112-
assert_equal(w4_name, "w")
113-
116+
w1, w2, w3, w4, *_ = wallets
114117
w1.generate(101)
115118
assert_equal(w1.getbalance(), 100)
116119
assert_equal(w2.getbalance(), 0)

0 commit comments

Comments
 (0)