Merge #11687: External wallet files

be8ab7d08 Create new wallet databases as directories rather than files (Russell Yanofsky)
26c06f24e Allow wallet files not in -walletdir directory (Russell Yanofsky)
d8a99f65e Allow wallet files in multiple directories (Russell Yanofsky)

Pull request description:

  This change consists of three commits:

  * The first commit is a pure refactoring that removes the restriction that two wallets can only be opened at the same time if they are contained in the same directory.
  * The second commit removes the restriction that `-wallet` filenames can only refer to files in the `-walletdir` directory.
  * The third commit makes second commit a little safer by changing bitcoin to create wallet databases as directories rather than files, so they can be safely backed up.

  All three commits should be straightforward:

  *  The first commit adds around 20 lines of new code and then updates a bunch of function signatures (generally updating them to take plain fs::path parameters, instead of combinations of strings, fs::paths, and objects like CDBEnv and CWalletDBWrapper).
  * The second commit removes two `-wallet` filename checks and adds some test cases to the multiwallet unit test.
  * The third commit just changes the mapping from specified wallet paths to bdb environment & data paths.

    ---

  **Note:** For anybody looking at this PR for the first time, I think you can skip the comments before _20 Nov_ and start reading at https://github.com/bitcoin/bitcoin/pull/11687#issuecomment-345625565. Comments before  _20 Nov_ were about an earlier version of the PR that didn't include the third commit, and then confusion from not seeing the first commit.

Tree-SHA512: 00bbb120fe0df847cf57014f75f1f7f1f58b0b62fa0b3adab4560163ebdfe06ccdfff33b4231693f03c5dc23601cb41954a07bcea9a4919c8d42f7d62bcf6024
This commit is contained in:
Wladimir J. van der Laan 2018-03-07 17:05:08 +01:00
commit 98bc27fb59
No known key found for this signature in database
GPG key ID: 1E4AED62986CD25D
17 changed files with 313 additions and 207 deletions

View file

@ -63,6 +63,36 @@ RPC changes
- The `fundrawtransaction` rpc will reject the previously deprecated `reserveChangeKey` option.
External wallet files
---------------------
The `-wallet=<path>` option now accepts full paths instead of requiring wallets
to be located in the -walletdir directory.
Newly created wallet format
---------------------------
If `-wallet=<path>` is specified with a path that does not exist, it will now
create a wallet directory at the specified location (containing a wallet.dat
data file, a db.log file, and database/log.?????????? files) instead of just
creating a data file at the path and storing log files in the parent
directory. This should make backing up wallets more straightforward than
before because the specified wallet path can just be directly archived without
having to look in the parent directory for transaction log files.
For backwards compatibility, wallet paths that are names of existing data files
in the `-walletdir` directory will continue to be accepted and interpreted the
same as before.
Low-level RPC changes
---------------------
- When bitcoin is not started with any `-wallet=<path>` options, the name of
the default wallet returned by `getwalletinfo` and `listwallets` RPCs is
now the empty string `""` instead of `"wallet.dat"`. If bitcoin is started
with any `-wallet=<path>` options, there is no change in behavior, and the
name of any wallet is just its `<path>` string.
Credits
=======

View file

@ -32,7 +32,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<CO
// (https://github.com/bitcoin/bitcoin/issues/7883#issuecomment-224807484)
static void CoinSelection(benchmark::State& state)
{
const CWallet wallet;
const CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
std::vector<COutput> vCoins;
LOCK(wallet.cs_wallet);

View file

@ -46,7 +46,7 @@ std::string HelpMessageCli()
strUsage += HelpMessageOpt("-rpcport=<port>", strprintf(_("Connect to JSON-RPC on <port> (default: %u or testnet: %u)"), defaultBaseParams->RPCPort(), testnetBaseParams->RPCPort()));
strUsage += HelpMessageOpt("-rpcuser=<user>", _("Username for JSON-RPC connections"));
strUsage += HelpMessageOpt("-rpcwait", _("Wait for RPC server to start"));
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)"));
strUsage += HelpMessageOpt("-rpcwallet=<walletname>", _("Send RPC for non-default wallet on RPC server (needs to exactly match corresponding -wallet option passed to bitcoind)"));
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."));
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.")));
@ -339,8 +339,8 @@ static UniValue CallRPC(BaseRequestHandler *rh, const std::string& strMethod, co
// check if we should use a special wallet endpoint
std::string endpoint = "/";
std::string walletName = gArgs.GetArg("-rpcwallet", "");
if (!walletName.empty()) {
if (!gArgs.GetArgs("-rpcwallet").empty()) {
std::string walletName = gArgs.GetArg("-rpcwallet", "");
char *encodedURI = evhttp_uriencode(walletName.c_str(), walletName.size(), false);
if (encodedURI) {
endpoint = "/wallet/"+ std::string(encodedURI);

View file

@ -158,9 +158,7 @@ void TestGUI()
for (int i = 0; i < 5; ++i) {
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
}
bitdb.MakeMock();
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
CWallet wallet(std::move(dbw));
CWallet wallet("mock", CWalletDBWrapper::CreateMock());
bool firstRun;
wallet.LoadWallet(firstRun);
{
@ -261,9 +259,6 @@ void TestGUI()
QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>("removeRequestButton");
removeRequestButton->click();
QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1);
bitdb.Flush(true);
bitdb.Reset();
}
}

View file

@ -52,20 +52,55 @@ void CheckUniqueFileid(const CDBEnv& env, const std::string& filename, Db& db)
}
}
}
CCriticalSection cs_db;
std::map<std::string, CDBEnv> g_dbenvs; //!< Map from directory name to open db environment.
} // namespace
CDBEnv* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename)
{
fs::path env_directory;
if (fs::is_regular_file(wallet_path)) {
// Special case for backwards compatibility: if wallet path points to an
// existing file, treat it as the path to a BDB data file in a parent
// directory that also contains BDB log files.
env_directory = wallet_path.parent_path();
database_filename = wallet_path.filename().string();
} else {
// Normal case: Interpret wallet path as a directory path containing
// data and log files.
env_directory = wallet_path;
database_filename = "wallet.dat";
}
LOCK(cs_db);
// Note: An ununsed temporary CDBEnv object may be created inside the
// emplace function if the key already exists. This is a little inefficient,
// but not a big concern since the map will be changed in the future to hold
// pointers instead of objects, anyway.
return &g_dbenvs.emplace(std::piecewise_construct, std::forward_as_tuple(env_directory.string()), std::forward_as_tuple(env_directory)).first->second;
}
//
// CDB
//
CDBEnv bitdb;
void CDBEnv::EnvShutdown()
void CDBEnv::Close()
{
if (!fDbEnvInit)
return;
fDbEnvInit = false;
for (auto& db : mapDb) {
auto count = mapFileUseCount.find(db.first);
assert(count == mapFileUseCount.end() || count->second == 0);
if (db.second) {
db.second->close(0);
delete db.second;
db.second = nullptr;
}
}
int ret = dbenv->close(0);
if (ret != 0)
LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
@ -80,29 +115,25 @@ void CDBEnv::Reset()
fMockDb = false;
}
CDBEnv::CDBEnv()
CDBEnv::CDBEnv(const fs::path& dir_path) : strPath(dir_path.string())
{
Reset();
}
CDBEnv::~CDBEnv()
{
EnvShutdown();
Close();
}
void CDBEnv::Close()
{
EnvShutdown();
}
bool CDBEnv::Open(const fs::path& pathIn, bool retry)
bool CDBEnv::Open(bool retry)
{
if (fDbEnvInit)
return true;
boost::this_thread::interruption_point();
strPath = pathIn.string();
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", strPath);
return false;
@ -150,7 +181,7 @@ bool CDBEnv::Open(const fs::path& pathIn, bool retry)
// failure is ok (well, not really, but it's not worse than what we started with)
}
// try opening it again one more time
if (!Open(pathIn, false)) {
if (!Open(false /* retry */)) {
// if it still fails, it probably means we can't even create the database env
return false;
}
@ -209,12 +240,15 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type
return RECOVER_FAIL;
// Try to recover:
bool fRecovered = (*recoverFunc)(strFile, out_backup_filename);
bool fRecovered = (*recoverFunc)(fs::path(strPath) / strFile, out_backup_filename);
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
}
bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
bool CDB::Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
{
std::string filename;
CDBEnv* env = GetWalletEnv(file_path, filename);
// Recovery procedure:
// move wallet file to walletfilename.timestamp.bak
// Call Salvage with fAggressive=true to
@ -225,7 +259,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
int64_t now = GetTime();
newFilename = strprintf("%s.%d.bak", filename, now);
int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr,
int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
newFilename.c_str(), DB_AUTO_COMMIT);
if (result == 0)
LogPrintf("Renamed %s to %s\n", filename, newFilename);
@ -236,7 +270,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
}
std::vector<CDBEnv::KeyValPair> salvagedData;
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData);
bool fSuccess = env->Salvage(newFilename, true, salvagedData);
if (salvagedData.empty())
{
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
@ -244,7 +278,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
}
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb.dbenv.get(), 0);
std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(env->dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
filename.c_str(), // Filename
"main", // Logical db name
@ -257,7 +291,7 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
return false;
}
DbTxn* ptxn = bitdb.TxnBegin();
DbTxn* ptxn = env->TxnBegin();
for (CDBEnv::KeyValPair& row : salvagedData)
{
if (recoverKVcallback)
@ -279,8 +313,12 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
return fSuccess;
}
bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr)
bool CDB::VerifyEnvironment(const fs::path& file_path, std::string& errorStr)
{
std::string walletFile;
CDBEnv* env = GetWalletEnv(file_path, walletFile);
fs::path walletDir = env->Directory();
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
LogPrintf("Using wallet %s\n", walletFile);
@ -291,7 +329,7 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle
return false;
}
if (!bitdb.Open(walletDir, true)) {
if (!env->Open(true /* retry */)) {
errorStr = strprintf(_("Error initializing wallet database environment %s!"), walletDir);
return false;
}
@ -299,12 +337,16 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walle
return true;
}
bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
bool CDB::VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
{
std::string walletFile;
CDBEnv* env = GetWalletEnv(file_path, walletFile);
fs::path walletDir = env->Directory();
if (fs::exists(walletDir / walletFile))
{
std::string backup_filename;
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename);
CDBEnv::VerifyResult r = env->Verify(walletFile, recoverFunc, backup_filename);
if (r == CDBEnv::RECOVER_OK)
{
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
@ -414,8 +456,8 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
nFlags |= DB_CREATE;
{
LOCK(env->cs_db);
if (!env->Open(GetWalletDir()))
LOCK(cs_db);
if (!env->Open(false /* retry */))
throw std::runtime_error("CDB: Failed to open database environment.");
pdb = env->mapDb[strFilename];
@ -442,7 +484,25 @@ CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb
if (ret != 0) {
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
}
CheckUniqueFileid(*env, strFilename, *pdb_temp);
// Call CheckUniqueFileid on the containing BDB environment to
// avoid BDB data consistency bugs that happen when different data
// files in the same environment have the same fileid.
//
// Also call CheckUniqueFileid on all the other g_dbenvs to prevent
// bitcoin from opening the same data file through another
// environment when the file is referenced through equivalent but
// not obviously identical symlinked or hard linked or bind mounted
// paths. In the future a more relaxed check for equal inode and
// device ids could be done instead, which would allow opening
// different backup copies of a wallet at the same time. Maybe even
// more ideally, an exclusive lock for accessing the database could
// be implemented, so no equality checks are needed at all. (Newer
// versions of BDB have an set_lk_exclusive method for this
// purpose, but the older version we use does not.)
for (auto& env : g_dbenvs) {
CheckUniqueFileid(env.second, strFilename, *pdb_temp);
}
pdb = pdb_temp.release();
env->mapDb[strFilename] = pdb;
@ -490,7 +550,7 @@ void CDB::Close()
Flush();
{
LOCK(env->cs_db);
LOCK(cs_db);
--env->mapFileUseCount[strFile];
}
}
@ -518,7 +578,7 @@ bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
const std::string& strFile = dbw.strFile;
while (true) {
{
LOCK(env->cs_db);
LOCK(cs_db);
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
@ -646,7 +706,7 @@ bool CDB::PeriodicFlush(CWalletDBWrapper& dbw)
bool ret = false;
CDBEnv *env = dbw.env;
const std::string& strFile = dbw.strFile;
TRY_LOCK(bitdb.cs_db,lockDb);
TRY_LOCK(cs_db, lockDb);
if (lockDb)
{
// Don't do this if any databases are in use
@ -694,7 +754,7 @@ bool CWalletDBWrapper::Backup(const std::string& strDest)
while (true)
{
{
LOCK(env->cs_db);
LOCK(cs_db);
if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0)
{
// Flush log data to the dat file

View file

@ -11,6 +11,7 @@
#include <serialize.h>
#include <streams.h>
#include <sync.h>
#include <util.h>
#include <version.h>
#include <atomic>
@ -32,20 +33,19 @@ private:
// shutdown problems/crashes caused by a static initialized internal pointer.
std::string strPath;
void EnvShutdown();
public:
mutable CCriticalSection cs_db;
std::unique_ptr<DbEnv> dbenv;
std::map<std::string, int> mapFileUseCount;
std::map<std::string, Db*> mapDb;
CDBEnv();
CDBEnv(const fs::path& env_directory);
~CDBEnv();
void Reset();
void MakeMock();
bool IsMock() const { return fMockDb; }
bool IsInitialized() const { return fDbEnvInit; }
fs::path Directory() const { return strPath; }
/**
* Verify that database file strFile is OK. If it is not,
@ -56,7 +56,7 @@ public:
enum VerifyResult { VERIFY_OK,
RECOVER_OK,
RECOVER_FAIL };
typedef bool (*recoverFunc_type)(const std::string& strFile, std::string& out_backup_filename);
typedef bool (*recoverFunc_type)(const fs::path& file_path, std::string& out_backup_filename);
VerifyResult Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename);
/**
* Salvage data from a file that Verify says is bad.
@ -68,7 +68,7 @@ public:
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
bool Open(const fs::path& path, bool retry = 0);
bool Open(bool retry);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
@ -85,7 +85,8 @@ public:
}
};
extern CDBEnv bitdb;
/** Get CDBEnv and database filename given a wallet path. */
CDBEnv* GetWalletEnv(const fs::path& wallet_path, std::string& database_filename);
/** An instance of this class represents one database.
* For BerkeleyDB this is just a (env, strFile) tuple.
@ -100,9 +101,33 @@ public:
}
/** Create DB handle to real database */
CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in) :
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(env_in), strFile(strFile_in)
CWalletDBWrapper(const fs::path& wallet_path, bool mock = false) :
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0)
{
env = GetWalletEnv(wallet_path, strFile);
if (mock) {
env->Close();
env->Reset();
env->MakeMock();
}
}
/** Return object for accessing database at specified path. */
static std::unique_ptr<CWalletDBWrapper> Create(const fs::path& path)
{
return MakeUnique<CWalletDBWrapper>(path);
}
/** Return object for accessing dummy database with no read/write capabilities. */
static std::unique_ptr<CWalletDBWrapper> CreateDummy()
{
return MakeUnique<CWalletDBWrapper>();
}
/** Return object for accessing temporary in-memory database. */
static std::unique_ptr<CWalletDBWrapper> CreateMock()
{
return MakeUnique<CWalletDBWrapper>("", true /* mock */);
}
/** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
@ -113,10 +138,6 @@ public:
*/
bool Backup(const std::string& strDest);
/** Get a name for this database, for debugging etc.
*/
std::string GetName() const { return strFile; }
/** Make sure all changes are flushed to disk.
*/
void Flush(bool shutdown);
@ -161,15 +182,15 @@ public:
void Flush();
void Close();
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
static bool Recover(const fs::path& file_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
/* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */
static bool PeriodicFlush(CWalletDBWrapper& dbw);
/* verifies the database environment */
static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr);
static bool VerifyEnvironment(const fs::path& file_path, std::string& errorStr);
/* verifies the database file */
static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc);
static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc);
public:
template <typename K, typename T>
@ -329,7 +350,7 @@ public:
{
if (!pdb || activeTxn)
return false;
DbTxn* ptxn = bitdb.TxnBegin();
DbTxn* ptxn = env->TxnBegin();
if (!ptxn)
return false;
activeTxn = ptxn;

View file

@ -35,7 +35,7 @@ std::string GetWalletHelpString(bool showDebug)
strUsage += HelpMessageOpt("-spendzeroconfchange", strprintf(_("Spend unconfirmed change when sending transactions (default: %u)"), DEFAULT_SPEND_ZEROCONF_CHANGE));
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));
strUsage += HelpMessageOpt("-upgradewallet", _("Upgrade wallet to latest format on startup"));
strUsage += HelpMessageOpt("-wallet=<file>", _("Specify wallet file (within data directory)") + " " + strprintf(_("(default: %s)"), DEFAULT_WALLET_DAT));
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 (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)"));
strUsage += HelpMessageOpt("-walletbroadcast", _("Make the wallet broadcast transactions") + " " + strprintf(_("(default: %u)"), 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)"));
@ -66,7 +66,7 @@ bool WalletParameterInteraction()
return true;
}
gArgs.SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
gArgs.SoftSetArg("-wallet", "");
const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY) && gArgs.SoftSetBoolArg("-walletbroadcast", false)) {
@ -230,18 +230,22 @@ bool VerifyWallets()
std::set<fs::path> wallet_paths;
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
if (boost::filesystem::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));
}
// Do some checking on wallet path. It should be either a:
//
// 1. Path where a directory can be created.
// 2. Path to an existing directory.
// 3. Path to a symlink to a directory.
// 4. For backwards compatibility, the name of a data file in -walletdir.
fs::path wallet_path = fs::absolute(walletFile, GetWalletDir());
if (fs::exists(wallet_path) && (!fs::is_regular_file(wallet_path) || fs::is_symlink(wallet_path))) {
return InitError(strprintf(_("Error loading wallet %s. -wallet filename must be a regular file."), walletFile));
fs::file_type path_type = fs::symlink_status(wallet_path).type();
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
(path_type == fs::regular_file && fs::path(walletFile).filename() == walletFile))) {
return InitError(strprintf(
_("Invalid -wallet path '%s'. -wallet path should point to a directory where wallet.dat and "
"database/log.?????????? files can be stored, a location where such a directory could be created, "
"or (for backwards compatibility) the name of an existing data file in -walletdir (%s)"),
walletFile, GetWalletDir()));
}
if (!wallet_paths.insert(wallet_path).second) {
@ -249,21 +253,21 @@ bool VerifyWallets()
}
std::string strError;
if (!CWalletDB::VerifyEnvironment(walletFile, GetWalletDir().string(), strError)) {
if (!CWalletDB::VerifyEnvironment(wallet_path, strError)) {
return InitError(strError);
}
if (gArgs.GetBoolArg("-salvagewallet", false)) {
// Recover readable keypairs:
CWallet dummyWallet;
CWallet dummyWallet("dummy", CWalletDBWrapper::CreateDummy());
std::string backup_filename;
if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) {
if (!CWalletDB::Recover(wallet_path, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) {
return false;
}
}
std::string strWarning;
bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetWalletDir().string(), strWarning, strError);
bool dbV = CWalletDB::VerifyDatabaseFile(wallet_path, strWarning, strError);
if (!strWarning.empty()) {
InitWarning(strWarning);
}
@ -284,7 +288,7 @@ bool OpenWallets()
}
for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile);
CWallet * const pwallet = CWallet::CreateWalletFromFile(walletFile, fs::absolute(walletFile, GetWalletDir()));
if (!pwallet) {
return false;
}

View file

@ -13,13 +13,13 @@
BOOST_FIXTURE_TEST_SUITE(accounting_tests, WalletTestingSetup)
static void
GetResults(CWallet *wallet, std::map<CAmount, CAccountingEntry>& results)
GetResults(CWallet& wallet, std::map<CAmount, CAccountingEntry>& results)
{
std::list<CAccountingEntry> aes;
results.clear();
BOOST_CHECK(wallet->ReorderTransactions() == DB_LOAD_OK);
wallet->ListAccountCreditDebit("", aes);
BOOST_CHECK(wallet.ReorderTransactions() == DB_LOAD_OK);
wallet.ListAccountCreditDebit("", aes);
for (CAccountingEntry& ae : aes)
{
results[ae.nOrderPos] = ae;
@ -33,28 +33,28 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
CAccountingEntry ae;
std::map<CAmount, CAccountingEntry> results;
LOCK(pwalletMain->cs_wallet);
LOCK(m_wallet.cs_wallet);
ae.strAccount = "";
ae.nCreditDebit = 1;
ae.nTime = 1333333333;
ae.strOtherAccount = "b";
ae.strComment = "";
pwalletMain->AddAccountingEntry(ae);
m_wallet.AddAccountingEntry(ae);
wtx.mapValue["comment"] = "z";
pwalletMain->AddToWallet(wtx);
vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
m_wallet.AddToWallet(wtx);
vpwtx.push_back(&m_wallet.mapWallet[wtx.GetHash()]);
vpwtx[0]->nTimeReceived = (unsigned int)1333333335;
vpwtx[0]->nOrderPos = -1;
ae.nTime = 1333333336;
ae.strOtherAccount = "c";
pwalletMain->AddAccountingEntry(ae);
m_wallet.AddAccountingEntry(ae);
GetResults(pwalletMain.get(), results);
GetResults(m_wallet, results);
BOOST_CHECK(pwalletMain->nOrderPosNext == 3);
BOOST_CHECK(m_wallet.nOrderPosNext == 3);
BOOST_CHECK(2 == results.size());
BOOST_CHECK(results[0].nTime == 1333333333);
BOOST_CHECK(results[0].strComment.empty());
@ -65,13 +65,13 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333330;
ae.strOtherAccount = "d";
ae.nOrderPos = pwalletMain->IncOrderPosNext();
pwalletMain->AddAccountingEntry(ae);
ae.nOrderPos = m_wallet.IncOrderPosNext();
m_wallet.AddAccountingEntry(ae);
GetResults(pwalletMain.get(), results);
GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 3);
BOOST_CHECK(pwalletMain->nOrderPosNext == 4);
BOOST_CHECK(m_wallet.nOrderPosNext == 4);
BOOST_CHECK(results[0].nTime == 1333333333);
BOOST_CHECK(1 == vpwtx[0]->nOrderPos);
BOOST_CHECK(results[2].nTime == 1333333336);
@ -85,8 +85,8 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
++tx.nLockTime; // Just to change the hash :)
wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
pwalletMain->AddToWallet(wtx);
vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
m_wallet.AddToWallet(wtx);
vpwtx.push_back(&m_wallet.mapWallet[wtx.GetHash()]);
vpwtx[1]->nTimeReceived = (unsigned int)1333333336;
wtx.mapValue["comment"] = "x";
@ -95,15 +95,15 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
++tx.nLockTime; // Just to change the hash :)
wtx.SetTx(MakeTransactionRef(std::move(tx)));
}
pwalletMain->AddToWallet(wtx);
vpwtx.push_back(&pwalletMain->mapWallet[wtx.GetHash()]);
m_wallet.AddToWallet(wtx);
vpwtx.push_back(&m_wallet.mapWallet[wtx.GetHash()]);
vpwtx[2]->nTimeReceived = (unsigned int)1333333329;
vpwtx[2]->nOrderPos = -1;
GetResults(pwalletMain.get(), results);
GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 3);
BOOST_CHECK(pwalletMain->nOrderPosNext == 6);
BOOST_CHECK(m_wallet.nOrderPosNext == 6);
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
BOOST_CHECK(results[1].nTime == 1333333333);
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);
@ -116,12 +116,12 @@ BOOST_AUTO_TEST_CASE(acc_orderupgrade)
ae.nTime = 1333333334;
ae.strOtherAccount = "e";
ae.nOrderPos = -1;
pwalletMain->AddAccountingEntry(ae);
m_wallet.AddAccountingEntry(ae);
GetResults(pwalletMain.get(), results);
GetResults(m_wallet, results);
BOOST_CHECK(results.size() == 4);
BOOST_CHECK(pwalletMain->nOrderPosNext == 7);
BOOST_CHECK(m_wallet.nOrderPosNext == 7);
BOOST_CHECK(0 == vpwtx[2]->nOrderPos);
BOOST_CHECK(results[1].nTime == 1333333333);
BOOST_CHECK(2 == vpwtx[0]->nOrderPos);

View file

@ -6,26 +6,21 @@
#include <rpc/server.h>
#include <wallet/db.h>
#include <wallet/wallet.h>
WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
TestingSetup(chainName)
TestingSetup(chainName), m_wallet("mock", CWalletDBWrapper::CreateMock())
{
bitdb.MakeMock();
bool fFirstRun;
g_address_type = OUTPUT_TYPE_DEFAULT;
g_change_type = OUTPUT_TYPE_DEFAULT;
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
pwalletMain = MakeUnique<CWallet>(std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);
RegisterValidationInterface(pwalletMain.get());
m_wallet.LoadWallet(fFirstRun);
RegisterValidationInterface(&m_wallet);
RegisterWalletRPCCommands(tableRPC);
}
WalletTestingSetup::~WalletTestingSetup()
{
UnregisterValidationInterface(pwalletMain.get());
bitdb.Flush(true);
bitdb.Reset();
UnregisterValidationInterface(&m_wallet);
}

View file

@ -15,7 +15,7 @@ struct WalletTestingSetup: public TestingSetup {
explicit WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN);
~WalletTestingSetup();
std::unique_ptr<CWallet> pwalletMain;
CWallet m_wallet;
};
#endif

View file

@ -36,7 +36,7 @@ typedef std::set<CInputCoin> CoinSet;
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
static const CWallet testWallet;
static const CWallet testWallet("dummy", CWalletDBWrapper::CreateDummy());
static std::vector<COutput> vCoins;
static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
@ -382,7 +382,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
CWallet wallet;
CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@ -397,7 +397,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
CWallet wallet;
CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(&wallet);
reserver.reserve();
@ -409,7 +409,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// before the missing block, and success for a key whose creation time is
// after.
{
CWallet wallet;
CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
vpwallets.insert(vpwallets.begin(), &wallet);
UniValue keys;
keys.setArray();
@ -471,7 +471,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Import key into wallet and call dumpwallet to create backup file.
{
CWallet wallet;
CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
LOCK(wallet.cs_wallet);
wallet.mapKeyMetadata[coinbaseKey.GetPubKey().GetID()].nCreateTime = KEY_TIME;
wallet.AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
@ -486,7 +486,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
// were scanned, and no prior blocks were scanned.
{
CWallet wallet;
CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
JSONRPCRequest request;
request.params.setArray();
@ -516,7 +516,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
// debit functions.
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{
CWallet wallet;
CWallet wallet("dummy", CWalletDBWrapper::CreateDummy());
CWalletTx wtx(&wallet, MakeTransactionRef(coinbaseTxns.back()));
LOCK2(cs_main, wallet.cs_wallet);
wtx.hashBlock = chainActive.Tip()->GetBlockHash();
@ -562,27 +562,25 @@ static int64_t AddTx(CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64
// expanded to cover more corner cases of smart time logic.
BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
{
CWallet wallet;
// New transaction should use clock time if lower than block time.
BOOST_CHECK_EQUAL(AddTx(wallet, 1, 100, 120), 100);
BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100);
// Test that updating existing transaction does not change smart time.
BOOST_CHECK_EQUAL(AddTx(wallet, 1, 200, 220), 100);
BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100);
// New transaction should use clock time if there's no block time.
BOOST_CHECK_EQUAL(AddTx(wallet, 2, 300, 0), 300);
BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300);
// New transaction should use block time if lower than clock time.
BOOST_CHECK_EQUAL(AddTx(wallet, 3, 420, 400), 400);
BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400);
// New transaction should use latest entry time if higher than
// min(block time, clock time).
BOOST_CHECK_EQUAL(AddTx(wallet, 4, 500, 390), 400);
BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400);
// If there are future entries, new transaction should use time of the
// newest entry that is no more than 300 seconds ahead of the clock time.
BOOST_CHECK_EQUAL(AddTx(wallet, 5, 50, 600), 300);
BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300);
// Reset mock time for other tests.
SetMockTime(0);
@ -591,12 +589,12 @@ BOOST_AUTO_TEST_CASE(ComputeTimeSmart)
BOOST_AUTO_TEST_CASE(LoadReceiveRequests)
{
CTxDestination dest = CKeyID();
LOCK(pwalletMain->cs_wallet);
pwalletMain->AddDestData(dest, "misc", "val_misc");
pwalletMain->AddDestData(dest, "rr0", "val_rr0");
pwalletMain->AddDestData(dest, "rr1", "val_rr1");
LOCK(m_wallet.cs_wallet);
m_wallet.AddDestData(dest, "misc", "val_misc");
m_wallet.AddDestData(dest, "rr0", "val_rr0");
m_wallet.AddDestData(dest, "rr1", "val_rr1");
auto values = pwalletMain->GetDestValues("rr");
auto values = m_wallet.GetDestValues("rr");
BOOST_CHECK_EQUAL(values.size(), 2);
BOOST_CHECK_EQUAL(values[0], "val_rr0");
BOOST_CHECK_EQUAL(values[1], "val_rr1");
@ -608,10 +606,9 @@ public:
ListCoinsTestingSetup()
{
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
::bitdb.MakeMock();
g_address_type = OUTPUT_TYPE_DEFAULT;
g_change_type = OUTPUT_TYPE_DEFAULT;
wallet.reset(new CWallet(std::unique_ptr<CWalletDBWrapper>(new CWalletDBWrapper(&bitdb, "wallet_test.dat"))));
wallet = MakeUnique<CWallet>("mock", CWalletDBWrapper::CreateMock());
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
@ -623,8 +620,6 @@ public:
~ListCoinsTestingSetup()
{
wallet.reset();
::bitdb.Flush(true);
::bitdb.Reset();
}
CWalletTx& AddTx(CRecipient recipient)

View file

@ -45,7 +45,6 @@ OutputType g_address_type = OUTPUT_TYPE_NONE;
OutputType g_change_type = OUTPUT_TYPE_NONE;
bool g_wallet_allow_fallback_fee = true; //<! will be defined via chainparams
const char * DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
/**
@ -3908,16 +3907,17 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const
return values;
}
CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
CWallet* CWallet::CreateWalletFromFile(const std::string& name, const fs::path& path)
{
const std::string& walletFile = name;
// needed to restore wallet transaction meta data after -zapwallettxes
std::vector<CWalletTx> vWtx;
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(std::move(dbw));
std::unique_ptr<CWallet> tempWallet = MakeUnique<CWallet>(name, CWalletDBWrapper::Create(path));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DB_LOAD_OK) {
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
@ -3929,8 +3929,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
CWallet *walletInstance = new CWallet(std::move(dbw));
CWallet *walletInstance = new CWallet(name, CWalletDBWrapper::Create(path));
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK)
{

View file

@ -15,6 +15,7 @@
#include <validationinterface.h>
#include <script/ismine.h>
#include <script/sign.h>
#include <util.h>
#include <wallet/crypter.h>
#include <wallet/walletdb.h>
#include <wallet/rpcwallet.h>
@ -67,8 +68,6 @@ static const bool DEFAULT_WALLET_RBF = false;
static const bool DEFAULT_WALLETBROADCAST = true;
static const bool DEFAULT_DISABLE_WALLET = false;
extern const char * DEFAULT_WALLET_DAT;
static const int64_t TIMESTAMP_MIN = 0;
class CBlockIndex;
@ -737,6 +736,14 @@ private:
*/
bool AddWatchOnly(const CScript& dest) override;
/**
* Wallet filename from wallet=<path> command line or config option.
* Used in debug logs and to send RPCs to the right wallet instance when
* more than one wallet is loaded.
*/
std::string m_name;
/** Internal database handle. */
std::unique_ptr<CWalletDBWrapper> dbw;
/**
@ -768,14 +775,7 @@ public:
/** Get a name for this wallet for logging/debugging purposes.
*/
std::string GetName() const
{
if (dbw) {
return dbw->GetName();
} else {
return "dummy";
}
}
const std::string& GetName() const { return m_name; }
void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool);
@ -789,14 +789,8 @@ public:
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID;
// Create wallet with dummy database handle
CWallet(): dbw(new CWalletDBWrapper())
{
SetNull();
}
// Create wallet with passed-in database handle
explicit CWallet(std::unique_ptr<CWalletDBWrapper> dbw_in) : dbw(std::move(dbw_in))
/** Construct wallet with specified name and database implementation. */
CWallet(std::string name, std::unique_ptr<CWalletDBWrapper> dbw) : m_name(std::move(name)), dbw(std::move(dbw))
{
SetNull();
}
@ -1116,7 +1110,7 @@ public:
bool MarkReplaced(const uint256& originalHash, const uint256& newHash);
/* Initializes the wallet, returns a new CWallet instance or a null pointer in case of an error */
static CWallet* CreateWalletFromFile(const std::string walletFile);
static CWallet* CreateWalletFromFile(const std::string& name, const fs::path& path);
/**
* Wallet post-init setup

View file

@ -771,16 +771,16 @@ void MaybeCompactWalletDB()
//
// Try to (very carefully!) recover wallet file if there is a problem.
//
bool CWalletDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename)
bool CWalletDB::Recover(const fs::path& wallet_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename)
{
return CDB::Recover(filename, callbackDataIn, recoverKVcallback, out_backup_filename);
return CDB::Recover(wallet_path, callbackDataIn, recoverKVcallback, out_backup_filename);
}
bool CWalletDB::Recover(const std::string& filename, std::string& out_backup_filename)
bool CWalletDB::Recover(const fs::path& wallet_path, std::string& out_backup_filename)
{
// recover without a key filter callback
// results in recovering all record types
return CWalletDB::Recover(filename, nullptr, nullptr, out_backup_filename);
return CWalletDB::Recover(wallet_path, nullptr, nullptr, out_backup_filename);
}
bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue)
@ -806,14 +806,14 @@ bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDa
return true;
}
bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr)
bool CWalletDB::VerifyEnvironment(const fs::path& wallet_path, std::string& errorStr)
{
return CDB::VerifyEnvironment(walletFile, walletDir, errorStr);
return CDB::VerifyEnvironment(wallet_path, errorStr);
}
bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr)
bool CWalletDB::VerifyDatabaseFile(const fs::path& wallet_path, std::string& warningStr, std::string& errorStr)
{
return CDB::VerifyDatabaseFile(walletFile, walletDir, warningStr, errorStr, CWalletDB::Recover);
return CDB::VerifyDatabaseFile(wallet_path, warningStr, errorStr, CWalletDB::Recover);
}
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)

View file

@ -218,17 +218,17 @@ public:
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
/* Try to (very carefully!) recover wallet database (with a possible key type filter) */
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
static bool Recover(const fs::path& wallet_path, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
/* Recover convenience-function to bypass the key filter callback, called when verify fails, recovers everything */
static bool Recover(const std::string& filename, std::string& out_backup_filename);
static bool Recover(const fs::path& wallet_path, std::string& out_backup_filename);
/* Recover filter (used as callback), will only let keys (cryptographical keys) as KV/key-type pass through */
static bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue);
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
static bool IsKeyType(const std::string& strType);
/* verifies the database environment */
static bool VerifyEnvironment(const std::string& walletFile, const fs::path& walletDir, std::string& errorStr);
static bool VerifyEnvironment(const fs::path& wallet_path, std::string& errorStr);
/* verifies the database file */
static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& walletDir, std::string& warningStr, std::string& errorStr);
static bool VerifyDatabaseFile(const fs::path& wallet_path, std::string& warningStr, std::string& errorStr);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);

View file

@ -37,13 +37,13 @@ class ConfArgsTest(BitcoinTestFramework):
os.mkdir(new_data_dir)
self.start_node(0, ['-conf='+conf_file, '-wallet=w1'])
self.stop_node(0)
assert os.path.isfile(os.path.join(new_data_dir, 'regtest', 'wallets', 'w1'))
assert os.path.exists(os.path.join(new_data_dir, 'regtest', 'wallets', 'w1'))
# Ensure command line argument overrides datadir in conf
os.mkdir(new_data_dir_2)
self.nodes[0].datadir = new_data_dir_2
self.start_node(0, ['-datadir='+new_data_dir_2, '-conf='+conf_file, '-wallet=w2'])
assert os.path.isfile(os.path.join(new_data_dir_2, 'regtest', 'wallets', 'w2'))
assert os.path.exists(os.path.join(new_data_dir_2, 'regtest', 'wallets', 'w2'))
if __name__ == '__main__':
ConfArgsTest().main()

View file

@ -16,7 +16,6 @@ class MultiWalletTest(BitcoinTestFramework):
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):
@ -26,9 +25,42 @@ class MultiWalletTest(BitcoinTestFramework):
wallet_dir = lambda *p: data_dir('wallets', *p)
wallet = lambda name: 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)
# 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"))
# 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
# 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', '']
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:
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)
# should not initialize if wallet path can't be created
self.assert_start_raises_init_error(0, ['-wallet=wallet.dat/bad'], 'Not a directory')
self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
self.assert_start_raises_init_error(0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
@ -37,17 +69,13 @@ class MultiWalletTest(BitcoinTestFramework):
# should not initialize if there are duplicate wallets
self.assert_start_raises_init_error(0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.')
# should not initialize if wallet file is a directory
os.mkdir(wallet_dir('w11'))
self.assert_start_raises_init_error(0, ['-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.')
# should not initialize if one wallet is a copy of another
shutil.copyfile(wallet_dir('w2'), wallet_dir('w22'))
self.assert_start_raises_init_error(0, ['-wallet=w2', '-wallet=w22'], 'duplicates fileid')
shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
self.assert_start_raises_init_error(0, ['-wallet=w8', '-wallet=w8_copy'], 'duplicates fileid')
# should not initialize if wallet file is a symlink
os.symlink(wallet_dir('w1'), wallet_dir('w12'))
self.assert_start_raises_init_error(0, ['-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.')
os.symlink('w8', wallet_dir('w8_symlink'))
self.assert_start_raises_init_error(0, ['-wallet=w8_symlink'], 'Invalid -wallet path')
# should not initialize if the specified walletdir does not exist
self.assert_start_raises_init_error(0, ['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist')
@ -77,15 +105,17 @@ class MultiWalletTest(BitcoinTestFramework):
self.restart_node(0, ['-walletdir='+competing_wallet_dir])
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", wallet_bad.getwalletinfo)
@ -93,24 +123,7 @@ class MultiWalletTest(BitcoinTestFramework):
# accessing wallet RPC without using wallet endpoint fails
assert_raises_rpc_error(-19, "Wallet file not specified", 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)