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:
commit
98bc27fb59
17 changed files with 313 additions and 207 deletions
|
@ -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
|
||||
=======
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue