diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,3 +3,5 @@ <https://download.bitcoinabc.org/0.18.7/> This release includes the following features and fixes: + - Add the `-walletdir` option to configure the directory in which the wallet + files are stored. If a relative path is used, it is relative to tha data dir. diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -210,6 +210,7 @@ wallet/rpcwallet.h \ wallet/wallet.h \ wallet/walletdb.h \ + wallet/walletutil.h \ warnings.h \ zmq/zmqabstractnotifier.h \ zmq/zmqconfig.h\ @@ -297,6 +298,7 @@ wallet/rpcwallet.cpp \ wallet/wallet.cpp \ wallet/walletdb.cpp \ + wallet/walletutil.cpp \ $(BITCOIN_CORE_H) # crypto primitives library diff --git a/src/wallet/CMakeLists.txt b/src/wallet/CMakeLists.txt --- a/src/wallet/CMakeLists.txt +++ b/src/wallet/CMakeLists.txt @@ -19,6 +19,7 @@ rpcwallet.cpp wallet.cpp walletdb.cpp + walletutil.cpp ) target_link_libraries(wallet util univalue Event ${BDBXX_LIBRARY}) diff --git a/src/wallet/db.h b/src/wallet/db.h --- a/src/wallet/db.h +++ b/src/wallet/db.h @@ -171,11 +171,11 @@ static bool PeriodicFlush(CWalletDBWrapper &dbw); /* verifies the database environment */ static bool VerifyEnvironment(const std::string &walletFile, - const fs::path &dataDir, + const fs::path &walletDir, std::string &errorStr); /* verifies the database file */ static bool VerifyDatabaseFile(const std::string &walletFile, - const fs::path &dataDir, + const fs::path &walletDir, std::string &warningStr, std::string &errorStr, CDBEnv::recoverFunc_type recoverFunc); diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -11,6 +11,7 @@ #include "protocol.h" #include "util.h" #include "utilstrencodings.h" +#include "wallet/walletutil.h" #include <boost/thread.hpp> #include <boost/version.hpp> @@ -265,22 +266,22 @@ } bool CDB::VerifyEnvironment(const std::string &walletFile, - const fs::path &dataDir, std::string &errorStr) { + const fs::path &walletDir, std::string &errorStr) { LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0)); LogPrintf("Using wallet %s\n", walletFile); // Wallet file must be a plain filename without a directory if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) { - errorStr = strprintf(_("Wallet %s resides outside data directory %s"), - walletFile, dataDir.string()); + errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"), + walletFile, walletDir.string()); return false; } - if (!bitdb.Open(dataDir)) { + if (!bitdb.Open(walletDir)) { // try moving the database env out of the way - fs::path pathDatabase = dataDir / "database"; + fs::path pathDatabase = walletDir / "database"; fs::path pathDatabaseBak = - dataDir / strprintf("database.%d.bak", GetTime()); + walletDir / strprintf("database.%d.bak", GetTime()); try { fs::rename(pathDatabase, pathDatabaseBak); LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), @@ -291,12 +292,12 @@ } // try again - if (!bitdb.Open(dataDir)) { + if (!bitdb.Open(walletDir)) { // if it still fails, it probably means we can't even create the // database env errorStr = strprintf( _("Error initializing wallet database environment %s!"), - GetDataDir()); + walletDir); return false; } } @@ -304,10 +305,10 @@ } bool CDB::VerifyDatabaseFile(const std::string &walletFile, - const fs::path &dataDir, std::string &warningStr, + const fs::path &walletDir, std::string &warningStr, std::string &errorStr, CDBEnv::recoverFunc_type recoverFunc) { - if (fs::exists(dataDir / walletFile)) { + if (fs::exists(walletDir / walletFile)) { std::string backup_filename; CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename); @@ -317,7 +318,7 @@ " Original %s saved as %s in %s; if" " your balance or transactions are incorrect you should" " restore from a backup."), - walletFile, backup_filename, dataDir); + walletFile, backup_filename, walletDir); } if (r == CDBEnv::RECOVER_FAIL) { errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile); @@ -430,7 +431,7 @@ { LOCK(env->cs_db); - if (!env->Open(GetDataDir())) { + if (!env->Open(GetWalletDir())) { throw std::runtime_error( "CDB: Failed to open database environment."); } @@ -743,7 +744,7 @@ env->mapFileUseCount.erase(strFile); // Copy wallet file. - fs::path pathSrc = GetDataDir() / strFile; + fs::path pathSrc = GetWalletDir() / strFile; fs::path pathDest(strDest); if (fs::is_directory(pathDest)) { pathDest /= strFile; diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -12,6 +12,7 @@ #include "validation.h" #include "wallet/rpcwallet.h" #include "wallet/wallet.h" +#include "wallet/walletutil.h" #include "walletinitinterface.h" class WalletInit : public WalletInitInterface { @@ -102,6 +103,10 @@ "-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %d)"), DEFAULT_WALLETBROADCAST)); + strUsage += HelpMessageOpt( + "-walletdir=<dir>", + _("Specify directory to hold wallets (default: <datadir>/wallets if it " + "exists, otherwise <datadir>)")); strUsage += HelpMessageOpt("-walletnotify=<cmd>", _("Execute command when a wallet transaction " "changes (%s in cmd is replaced by TxID)")); @@ -301,6 +306,14 @@ return true; } + if (gArgs.IsArgSet("-walletdir") && !fs::is_directory(GetWalletDir())) { + return InitError(strprintf( + _("Error: Specified wallet directory \"%s\" does not exist."), + gArgs.GetArg("-walletdir", "").c_str())); + } + + LogPrintf("Using wallet directory %s\n", GetWalletDir().string()); + uiInterface.InitMessage(_("Verifying wallet(s)...")); // Keep track of each wallet absolute path to detect duplicates. @@ -320,7 +333,7 @@ walletFile)); } - fs::path wallet_path = fs::absolute(walletFile, GetDataDir()); + fs::path wallet_path = fs::absolute(walletFile, GetWalletDir()); if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) { @@ -336,7 +349,7 @@ } std::string strError; - if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), + if (!CWalletDB::VerifyEnvironment(walletFile, GetWalletDir().string(), strError)) { return InitError(strError); } @@ -354,7 +367,7 @@ std::string strWarning; bool dbV = CWalletDB::VerifyDatabaseFile( - walletFile, GetDataDir().string(), strWarning, strError); + walletFile, GetWalletDir().string(), strWarning, strError); if (!strWarning.empty()) { InitWarning(strWarning); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -21,11 +21,10 @@ #include "util.h" #include "utilmoneystr.h" #include "validation.h" -#include "wallet.h" #include "wallet/coincontrol.h" #include "wallet/wallet.h" #include "wallet/walletdb.h" -#include "walletdb.h" +#include "wallet/walletutil.h" // Input src/init.h (not wallet/init.h) for StartShutdown #include <init.h> diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -240,11 +240,11 @@ static bool IsKeyType(const std::string &strType); /* verifies the database environment */ static bool VerifyEnvironment(const std::string &walletFile, - const fs::path &dataDir, + const fs::path &walletDir, std::string &errorStr); /* verifies the database file */ static bool VerifyDatabaseFile(const std::string &walletFile, - const fs::path &dataDir, + const fs::path &walletDir, std::string &warningStr, std::string &errorStr); diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -836,16 +836,16 @@ } bool CWalletDB::VerifyEnvironment(const std::string &walletFile, - const fs::path &dataDir, + const fs::path &walletDir, std::string &errorStr) { - return CDB::VerifyEnvironment(walletFile, dataDir, errorStr); + return CDB::VerifyEnvironment(walletFile, walletDir, errorStr); } bool CWalletDB::VerifyDatabaseFile(const std::string &walletFile, - const fs::path &dataDir, + const fs::path &walletDir, std::string &warningStr, std::string &errorStr) { - return CDB::VerifyDatabaseFile(walletFile, dataDir, warningStr, errorStr, + return CDB::VerifyDatabaseFile(walletFile, walletDir, warningStr, errorStr, CWalletDB::Recover); } diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h new file mode 100644 --- /dev/null +++ b/src/wallet/walletutil.h @@ -0,0 +1,13 @@ +// 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. + +#ifndef BITCOIN_WALLET_UTIL_H +#define BITCOIN_WALLET_UTIL_H + +#include "util.h" + +//! Get the path of the wallet directory. +fs::path GetWalletDir(); + +#endif // BITCOIN_WALLET_UTIL_H diff --git a/src/wallet/walletutil.cpp b/src/wallet/walletutil.cpp new file mode 100644 --- /dev/null +++ b/src/wallet/walletutil.cpp @@ -0,0 +1,22 @@ +// 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. + +#include "wallet/walletutil.h" + +fs::path GetWalletDir() { + fs::path path; + + if (gArgs.IsArgSet("-walletdir")) { + path = fs::system_complete(gArgs.GetArg("-walletdir", "")); + if (!fs::is_directory(path)) { + // If the path specified doesn't exist, we return the deliberately + // invalid empty string. + path = ""; + } + } else { + path = GetDataDir(); + } + + return path; +} diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py --- a/test/functional/multiwallet.py +++ b/test/functional/multiwallet.py @@ -47,6 +47,22 @@ self.assert_start_raises_init_error( 0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.') + # should not initialize if the specified walletdir does not exist + self.assert_start_raises_init_error( + 0, ['-walletdir=bad'], 'Error: Specified wallet directory "bad" does not exist.') + + # running the node with specified walletdir should only have the default wallet in it + os.mkdir(os.path.join(self.options.tmpdir, + 'node0', 'regtest', 'walletdir')) + self.start_node(0, ['-wallet=w4', '-wallet=w5', '-walletdir=' + + os.path.join(self.options.tmpdir, 'node0', 'regtest', 'walletdir')]) + assert_equal(set(self.nodes[0].listwallets()), {"w4", "w5"}) + w5 = self.nodes[0].get_wallet_rpc("w5") + w5_info = w5.getwalletinfo() + assert_equal(w5_info['immature_balance'], 0) + + self.stop_node(0) + self.start_node(0, self.extra_args[0]) w1 = self.nodes[0].get_wallet_rpc("w1")