Browse Source

Merge #8694: Basic multiwallet support

c237bd7 wallet: Update formatting (Luke Dashjr)
9cbe8c8 wallet: Forbid -salvagewallet, -zapwallettxes, and -upgradewallet with multiple wallets (Luke Dashjr)
a2a5f3f wallet: Base backup filenames on original wallet filename (Luke Dashjr)
b823a4c wallet: Include actual backup filename in recovery warning message (Luke Dashjr)
84dcb45 Bugfix: wallet: Fix warningStr, errorStr argument order (Luke Dashjr)
008c360 Wallet: Move multiwallet sanity checks to CWallet::Verify, and do other checks on all wallets (Luke Dashjr)
0f08575 Wallet: Support loading multiple wallets if -wallet used more than once (Luke Dashjr)
b124cf0 Wallet: Replace pwalletMain with a vector of wallet pointers (Luke Dashjr)
19b3648 CWalletDB: Store the update counter per wallet (Luke Dashjr)
74e8738 Bugfix: ForceSetArg should replace entr(ies) in mapMultiArgs, not append (Luke Dashjr)
23fb9ad wallet: Move nAccountingEntryNumber from static/global to CWallet (Luke Dashjr)
9d15d55 Bugfix: wallet: Increment "update counter" when modifying account stuff (Luke Dashjr)
f28eb80 Bugfix: wallet: Increment "update counter" only after actually making the applicable db changes to avoid potential races (Luke Dashjr)

Tree-SHA512: 23f5dda58477307bc07997010740f1dc729164cdddefd2f9a2c9c7a877111eb1516d3e2ad4f9b104621f0b7f17369c69fcef13d28b85cb6c01d35f09a8845f23
0.15
Wladimir J. van der Laan 8 years ago
parent
commit
177433ad22
No known key found for this signature in database
GPG Key ID: 1E4AED62986CD25D
  1. 21
      src/init.cpp
  2. 5
      src/qt/bitcoin.cpp
  3. 1
      src/util.cpp
  4. 22
      src/wallet/db.cpp
  5. 20
      src/wallet/db.h
  6. 3
      src/wallet/rpcwallet.cpp
  7. 2
      src/wallet/test/wallet_test_fixture.cpp
  8. 14
      src/wallet/test/wallet_tests.cpp
  9. 89
      src/wallet/wallet.cpp
  10. 5
      src/wallet/wallet.h
  11. 148
      src/wallet/walletdb.cpp
  12. 33
      src/wallet/walletdb.h

21
src/init.cpp

@ -197,8 +197,9 @@ void Shutdown()
StopRPC(); StopRPC();
StopHTTPServer(); StopHTTPServer();
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
if (pwalletMain) for (CWalletRef pwallet : vpwallets) {
pwalletMain->Flush(false); pwallet->Flush(false);
}
#endif #endif
MapPort(false); MapPort(false);
UnregisterValidationInterface(peerLogic.get()); UnregisterValidationInterface(peerLogic.get());
@ -238,8 +239,9 @@ void Shutdown()
pblocktree = NULL; pblocktree = NULL;
} }
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
if (pwalletMain) for (CWalletRef pwallet : vpwallets) {
pwalletMain->Flush(true); pwallet->Flush(true);
}
#endif #endif
#if ENABLE_ZMQ #if ENABLE_ZMQ
@ -259,8 +261,10 @@ void Shutdown()
#endif #endif
UnregisterAllValidationInterfaces(); UnregisterAllValidationInterfaces();
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
delete pwalletMain; for (CWalletRef pwallet : vpwallets) {
pwalletMain = NULL; delete pwallet;
}
vpwallets.clear();
#endif #endif
globalVerifyHandle.reset(); globalVerifyHandle.reset();
ECC_Stop(); ECC_Stop();
@ -1672,8 +1676,9 @@ bool AppInitMain(boost::thread_group& threadGroup, CScheduler& scheduler)
uiInterface.InitMessage(_("Done loading")); uiInterface.InitMessage(_("Done loading"));
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
if (pwalletMain) for (CWalletRef pwallet : vpwallets) {
pwalletMain->postInitProcess(scheduler); pwallet->postInitProcess(scheduler);
}
#endif #endif
return !fRequestShutdown; return !fRequestShutdown;

5
src/qt/bitcoin.cpp

@ -474,9 +474,10 @@ void BitcoinApplication::initializeResult(bool success)
window->setClientModel(clientModel); window->setClientModel(clientModel);
#ifdef ENABLE_WALLET #ifdef ENABLE_WALLET
if(pwalletMain) // TODO: Expose secondary wallets
if (!vpwallets.empty())
{ {
walletModel = new WalletModel(platformStyle, pwalletMain, optionsModel); walletModel = new WalletModel(platformStyle, vpwallets[0], optionsModel);
window->addWallet(BitcoinGUI::DEFAULT_WALLET, walletModel); window->addWallet(BitcoinGUI::DEFAULT_WALLET, walletModel);
window->setCurrentWallet(BitcoinGUI::DEFAULT_WALLET); window->setCurrentWallet(BitcoinGUI::DEFAULT_WALLET);

1
src/util.cpp

@ -474,6 +474,7 @@ void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strV
{ {
LOCK(cs_args); LOCK(cs_args);
mapArgs[strArg] = strValue; mapArgs[strArg] = strValue;
mapMultiArgs[strArg].clear();
mapMultiArgs[strArg].push_back(strValue); mapMultiArgs[strArg].push_back(strValue);
} }

22
src/wallet/db.cpp

@ -142,7 +142,7 @@ void CDBEnv::MakeMock()
fMockDb = true; fMockDb = true;
} }
CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile)) CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename)
{ {
LOCK(cs_db); LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0); assert(mapFileUseCount.count(strFile) == 0);
@ -155,21 +155,21 @@ CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFu
return RECOVER_FAIL; return RECOVER_FAIL;
// Try to recover: // Try to recover:
bool fRecovered = (*recoverFunc)(strFile); bool fRecovered = (*recoverFunc)(strFile, out_backup_filename);
return (fRecovered ? RECOVER_OK : RECOVER_FAIL); return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
} }
bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue)) bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& newFilename)
{ {
// Recovery procedure: // Recovery procedure:
// move wallet file to wallet.timestamp.bak // move wallet file to walletfilename.timestamp.bak
// Call Salvage with fAggressive=true to // Call Salvage with fAggressive=true to
// get as much data as possible. // get as much data as possible.
// Rewrite salvaged data to fresh wallet file // Rewrite salvaged data to fresh wallet file
// Set -rescan so any missing transactions will be // Set -rescan so any missing transactions will be
// found. // found.
int64_t now = GetTime(); int64_t now = GetTime();
std::string newFilename = strprintf("wallet.%d.bak", now); newFilename = strprintf("%s.%d.bak", filename, now);
int result = bitdb.dbenv->dbrename(NULL, filename.c_str(), NULL, int result = bitdb.dbenv->dbrename(NULL, filename.c_str(), NULL,
newFilename.c_str(), DB_AUTO_COMMIT); newFilename.c_str(), DB_AUTO_COMMIT);
@ -259,18 +259,19 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataD
return true; return true;
} }
bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile)) bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc)
{ {
if (fs::exists(dataDir / walletFile)) if (fs::exists(dataDir / walletFile))
{ {
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc); std::string backup_filename;
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc, backup_filename);
if (r == CDBEnv::RECOVER_OK) if (r == CDBEnv::RECOVER_OK)
{ {
warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!" warningStr = strprintf(_("Warning: Wallet file corrupt, data salvaged!"
" Original %s saved as %s in %s; if" " Original %s saved as %s in %s; if"
" your balance or transactions are incorrect you should" " your balance or transactions are incorrect you should"
" restore from a backup."), " restore from a backup."),
walletFile, "wallet.{timestamp}.bak", dataDir); walletFile, backup_filename, dataDir);
} }
if (r == CDBEnv::RECOVER_FAIL) if (r == CDBEnv::RECOVER_FAIL)
{ {
@ -432,6 +433,11 @@ void CDB::Flush()
env->dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0); env->dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
} }
void CWalletDBWrapper::IncrementUpdateCounter()
{
++nUpdateCounter;
}
void CDB::Close() void CDB::Close()
{ {
if (!pdb) if (!pdb)

20
src/wallet/db.h

@ -55,7 +55,8 @@ public:
enum VerifyResult { VERIFY_OK, enum VerifyResult { VERIFY_OK,
RECOVER_OK, RECOVER_OK,
RECOVER_FAIL }; RECOVER_FAIL };
VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(const std::string& strFile)); typedef bool (*recoverFunc_type)(const std::string& strFile, 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. * Salvage data from a file that Verify says is bad.
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation). * fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
@ -93,13 +94,13 @@ class CWalletDBWrapper
friend class CDB; friend class CDB;
public: public:
/** Create dummy DB handle */ /** Create dummy DB handle */
CWalletDBWrapper(): env(nullptr) CWalletDBWrapper() : nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(nullptr)
{ {
} }
/** Create DB handle to real database */ /** Create DB handle to real database */
CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in): CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in) :
env(env_in), strFile(strFile_in) nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(env_in), strFile(strFile_in)
{ {
} }
@ -119,6 +120,13 @@ public:
*/ */
void Flush(bool shutdown); void Flush(bool shutdown);
void IncrementUpdateCounter();
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
int64_t nLastWalletUpdate;
private: private:
/** BerkeleyDB specific */ /** BerkeleyDB specific */
CDBEnv *env; CDBEnv *env;
@ -149,7 +157,7 @@ public:
void Flush(); void Flush();
void Close(); void Close();
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue)); static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename);
/* flush the wallet passively (TRY_LOCK) /* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */ ideal to be called periodically */
@ -157,7 +165,7 @@ public:
/* verifies the database environment */ /* verifies the database environment */
static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr); static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr);
/* verifies the database file */ /* verifies the database file */
static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile)); static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, CDBEnv::recoverFunc_type recoverFunc);
private: private:
CDB(const CDB&); CDB(const CDB&);

3
src/wallet/rpcwallet.cpp

@ -31,7 +31,8 @@
CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request) CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
{ {
return pwalletMain; // TODO: Some way to access secondary wallets
return vpwallets.empty() ? nullptr : vpwallets[0];
} }
std::string HelpRequiringPassphrase(CWallet * const pwallet) std::string HelpRequiringPassphrase(CWallet * const pwallet)

2
src/wallet/test/wallet_test_fixture.cpp

@ -8,6 +8,8 @@
#include "wallet/db.h" #include "wallet/db.h"
#include "wallet/wallet.h" #include "wallet/wallet.h"
CWallet *pwalletMain;
WalletTestingSetup::WalletTestingSetup(const std::string& chainName): WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
TestingSetup(chainName) TestingSetup(chainName)
{ {

14
src/wallet/test/wallet_tests.cpp

@ -18,6 +18,8 @@
#include <boost/test/unit_test.hpp> #include <boost/test/unit_test.hpp>
#include <univalue.h> #include <univalue.h>
extern CWallet* pwalletMain;
extern UniValue importmulti(const JSONRPCRequest& request); extern UniValue importmulti(const JSONRPCRequest& request);
extern UniValue dumpwallet(const JSONRPCRequest& request); extern UniValue dumpwallet(const JSONRPCRequest& request);
extern UniValue importwallet(const JSONRPCRequest& request); extern UniValue importwallet(const JSONRPCRequest& request);
@ -401,8 +403,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// after. // after.
{ {
CWallet wallet; CWallet wallet;
CWallet *backup = ::pwalletMain; vpwallets.insert(vpwallets.begin(), &wallet);
::pwalletMain = &wallet;
UniValue keys; UniValue keys;
keys.setArray(); keys.setArray();
UniValue key; UniValue key;
@ -433,7 +434,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
"downloading and rescanning the relevant blocks (see -reindex and -rescan " "downloading and rescanning the relevant blocks (see -reindex and -rescan "
"options).\"}},{\"success\":true}]", "options).\"}},{\"success\":true}]",
0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW));
::pwalletMain = backup; vpwallets.erase(vpwallets.begin());
} }
} }
@ -443,7 +444,6 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
// than or equal to key birthday. // than or equal to key birthday.
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
{ {
CWallet *pwalletMainBackup = ::pwalletMain;
LOCK(cs_main); LOCK(cs_main);
// Create two blocks with same timestamp to verify that importwallet rescan // Create two blocks with same timestamp to verify that importwallet rescan
@ -469,7 +469,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
JSONRPCRequest request; JSONRPCRequest request;
request.params.setArray(); request.params.setArray();
request.params.push_back("wallet.backup"); request.params.push_back("wallet.backup");
::pwalletMain = &wallet; vpwallets.insert(vpwallets.begin(), &wallet);
::dumpwallet(request); ::dumpwallet(request);
} }
@ -481,7 +481,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
JSONRPCRequest request; JSONRPCRequest request;
request.params.setArray(); request.params.setArray();
request.params.push_back("wallet.backup"); request.params.push_back("wallet.backup");
::pwalletMain = &wallet; vpwallets[0] = &wallet;
::importwallet(request); ::importwallet(request);
BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3); BOOST_CHECK_EQUAL(wallet.mapWallet.size(), 3);
@ -494,7 +494,7 @@ BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup)
} }
SetMockTime(0); SetMockTime(0);
::pwalletMain = pwalletMainBackup; vpwallets.erase(vpwallets.begin());
} }
// Check that GetImmatureCredit() returns a newly calculated value instead of // Check that GetImmatureCredit() returns a newly calculated value instead of

89
src/wallet/wallet.cpp

@ -35,7 +35,7 @@
#include <boost/algorithm/string/replace.hpp> #include <boost/algorithm/string/replace.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
CWallet* pwalletMain = NULL; std::vector<CWalletRef> vpwallets;
/** Transaction fee set by the user */ /** Transaction fee set by the user */
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
@ -440,30 +440,40 @@ bool CWallet::Verify()
if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
return true; return true;
uiInterface.InitMessage(_("Verifying wallet...")); uiInterface.InitMessage(_("Verifying wallet(s)..."));
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
std::string strError; for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) if (boost::filesystem::path(walletFile).filename() != walletFile) {
return InitError(strError); return InitError(_("-wallet parameter must only specify a filename (not a path)"));
} else if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) {
return InitError(_("Invalid characters in -wallet filename"));
}
if (GetBoolArg("-salvagewallet", false)) std::string strError;
{ if (!CWalletDB::VerifyEnvironment(walletFile, GetDataDir().string(), strError)) {
// Recover readable keypairs: return InitError(strError);
CWallet dummyWallet; }
if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter))
if (GetBoolArg("-salvagewallet", false)) {
// Recover readable keypairs:
CWallet dummyWallet;
std::string backup_filename;
if (!CWalletDB::Recover(walletFile, (void *)&dummyWallet, CWalletDB::RecoverKeysOnlyFilter, backup_filename)) {
return false;
}
}
std::string strWarning;
bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError);
if (!strWarning.empty()) {
InitWarning(strWarning);
}
if (!dbV) {
InitError(strError);
return false; return false;
}
} }
std::string strWarning;
bool dbV = CWalletDB::VerifyDatabaseFile(walletFile, GetDataDir().string(), strWarning, strError);
if (!strWarning.empty())
InitWarning(strWarning);
if (!dbV)
{
InitError(strError);
return false;
}
return true; return true;
} }
@ -2867,8 +2877,9 @@ bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry)
bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB *pwalletdb) bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB *pwalletdb)
{ {
if (!pwalletdb->WriteAccountingEntry_Backend(acentry)) if (!pwalletdb->WriteAccountingEntry(++nAccountingEntryNumber, acentry)) {
return false; return false;
}
laccentries.push_back(acentry); laccentries.push_back(acentry);
CAccountingEntry & entry = laccentries.back(); CAccountingEntry & entry = laccentries.back();
@ -3880,7 +3891,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
walletInstance->ScanForWalletTransactions(pindexRescan, true); walletInstance->ScanForWalletTransactions(pindexRescan, true);
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart); LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator()); walletInstance->SetBestChain(chainActive.GetLocator());
CWalletDB::IncrementUpdateCounter(); walletInstance->dbw->IncrementUpdateCounter();
// Restore wallet transaction metadata after -zapwallettxes=1 // Restore wallet transaction metadata after -zapwallettxes=1
if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2") if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2")
@ -3922,24 +3933,17 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
bool CWallet::InitLoadWallet() bool CWallet::InitLoadWallet()
{ {
if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) { if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) {
pwalletMain = NULL;
LogPrintf("Wallet disabled!\n"); LogPrintf("Wallet disabled!\n");
return true; return true;
} }
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT); for (const std::string& walletFile : gArgs.GetArgs("-wallet")) {
CWallet * const pwallet = CreateWalletFromFile(walletFile);
if (boost::filesystem::path(walletFile).filename() != walletFile) { if (!pwallet) {
return InitError(_("-wallet parameter must only specify a filename (not a path)")); return false;
} else if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) { }
return InitError(_("Invalid characters in -wallet filename")); vpwallets.push_back(pwallet);
}
CWallet * const pwallet = CreateWalletFromFile(walletFile);
if (!pwallet) {
return false;
} }
pwalletMain = pwallet;
return true; return true;
} }
@ -3960,6 +3964,9 @@ void CWallet::postInitProcess(CScheduler& scheduler)
bool CWallet::ParameterInteraction() bool CWallet::ParameterInteraction()
{ {
SoftSetArg("-wallet", DEFAULT_WALLET_DAT);
const bool is_multiwallet = gArgs.GetArgs("-wallet").size() > 1;
if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET)) if (GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET))
return true; return true;
@ -3968,15 +3975,27 @@ bool CWallet::ParameterInteraction()
} }
if (GetBoolArg("-salvagewallet", false) && SoftSetBoolArg("-rescan", true)) { if (GetBoolArg("-salvagewallet", false) && SoftSetBoolArg("-rescan", true)) {
if (is_multiwallet) {
return InitError(strprintf("%s is only allowed with a single wallet file", "-salvagewallet"));
}
// Rewrite just private keys: rescan to find transactions // Rewrite just private keys: rescan to find transactions
LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__); LogPrintf("%s: parameter interaction: -salvagewallet=1 -> setting -rescan=1\n", __func__);
} }
// -zapwallettx implies a rescan // -zapwallettx implies a rescan
if (GetBoolArg("-zapwallettxes", false) && SoftSetBoolArg("-rescan", true)) { if (GetBoolArg("-zapwallettxes", false) && SoftSetBoolArg("-rescan", true)) {
if (is_multiwallet) {
return InitError(strprintf("%s is only allowed with a single wallet file", "-zapwallettxes"));
}
LogPrintf("%s: parameter interaction: -zapwallettxes=<mode> -> setting -rescan=1\n", __func__); LogPrintf("%s: parameter interaction: -zapwallettxes=<mode> -> setting -rescan=1\n", __func__);
} }
if (is_multiwallet) {
if (GetBoolArg("-upgradewallet", false)) {
return InitError(strprintf("%s is only allowed with a single wallet file", "-upgradewallet"));
}
}
if (GetBoolArg("-sysperms", false)) if (GetBoolArg("-sysperms", false))
return InitError("-sysperms is not allowed in combination with enabled wallet functionality"); return InitError("-sysperms is not allowed in combination with enabled wallet functionality");
if (GetArg("-prune", 0) && GetBoolArg("-rescan", false)) if (GetArg("-prune", 0) && GetBoolArg("-rescan", false))

5
src/wallet/wallet.h

@ -29,7 +29,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
extern CWallet* pwalletMain; typedef CWallet* CWalletRef;
extern std::vector<CWalletRef> vpwallets;
/** /**
* Settings * Settings
@ -782,6 +783,7 @@ public:
nMasterKeyMaxID = 0; nMasterKeyMaxID = 0;
pwalletdbEncryption = NULL; pwalletdbEncryption = NULL;
nOrderPosNext = 0; nOrderPosNext = 0;
nAccountingEntryNumber = 0;
nNextResend = 0; nNextResend = 0;
nLastResend = 0; nLastResend = 0;
nTimeFirstKey = 0; nTimeFirstKey = 0;
@ -799,6 +801,7 @@ public:
TxItems wtxOrdered; TxItems wtxOrdered;
int64_t nOrderPosNext; int64_t nOrderPosNext;
uint64_t nAccountingEntryNumber;
std::map<uint256, int> mapRequestCount; std::map<uint256, int> mapRequestCount;
std::map<CTxDestination, CAddressBookData> mapAddressBook; std::map<CTxDestination, CAddressBookData> mapAddressBook;

148
src/wallet/walletdb.cpp

@ -21,59 +21,47 @@
#include <boost/foreach.hpp> #include <boost/foreach.hpp>
#include <boost/thread.hpp> #include <boost/thread.hpp>
static uint64_t nAccountingEntryNumber = 0;
static std::atomic<unsigned int> nWalletDBUpdateCounter;
// //
// CWalletDB // CWalletDB
// //
bool CWalletDB::WriteName(const std::string& strAddress, const std::string& strName) bool CWalletDB::WriteName(const std::string& strAddress, const std::string& strName)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("name"), strAddress), strName);
return batch.Write(std::make_pair(std::string("name"), strAddress), strName);
} }
bool CWalletDB::EraseName(const std::string& strAddress) bool CWalletDB::EraseName(const std::string& strAddress)
{ {
// This should only be used for sending addresses, never for receiving addresses, // This should only be used for sending addresses, never for receiving addresses,
// receiving addresses must always have an address book entry if they're not change return. // receiving addresses must always have an address book entry if they're not change return.
nWalletDBUpdateCounter++; return EraseIC(std::make_pair(std::string("name"), strAddress));
return batch.Erase(std::make_pair(std::string("name"), strAddress));
} }
bool CWalletDB::WritePurpose(const std::string& strAddress, const std::string& strPurpose) bool CWalletDB::WritePurpose(const std::string& strAddress, const std::string& strPurpose)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("purpose"), strAddress), strPurpose);
return batch.Write(std::make_pair(std::string("purpose"), strAddress), strPurpose);
} }
bool CWalletDB::ErasePurpose(const std::string& strPurpose) bool CWalletDB::ErasePurpose(const std::string& strPurpose)
{ {
nWalletDBUpdateCounter++; return EraseIC(std::make_pair(std::string("purpose"), strPurpose));
return batch.Erase(std::make_pair(std::string("purpose"), strPurpose));
} }
bool CWalletDB::WriteTx(const CWalletTx& wtx) bool CWalletDB::WriteTx(const CWalletTx& wtx)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
return batch.Write(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
} }
bool CWalletDB::EraseTx(uint256 hash) bool CWalletDB::EraseTx(uint256 hash)
{ {
nWalletDBUpdateCounter++; return EraseIC(std::make_pair(std::string("tx"), hash));
return batch.Erase(std::make_pair(std::string("tx"), hash));
} }
bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta) bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{ {
nWalletDBUpdateCounter++; if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta, false)) {
if (!batch.Write(std::make_pair(std::string("keymeta"), vchPubKey),
keyMeta, false))
return false; return false;
}
// hash pubkey/privkey to accelerate wallet load // hash pubkey/privkey to accelerate wallet load
std::vector<unsigned char> vchKey; std::vector<unsigned char> vchKey;
@ -81,7 +69,7 @@ bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, c
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end()); vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());
return batch.Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); return WriteIC(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
} }
bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey, bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
@ -89,55 +77,53 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
const CKeyMetadata &keyMeta) const CKeyMetadata &keyMeta)
{ {
const bool fEraseUnencryptedKey = true; const bool fEraseUnencryptedKey = true;
nWalletDBUpdateCounter++;
if (!batch.Write(std::make_pair(std::string("keymeta"), vchPubKey), if (!WriteIC(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) {
keyMeta))
return false; return false;
}
if (!batch.Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) if (!WriteIC(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false)) {
return false; return false;
}
if (fEraseUnencryptedKey) if (fEraseUnencryptedKey)
{ {
batch.Erase(std::make_pair(std::string("key"), vchPubKey)); EraseIC(std::make_pair(std::string("key"), vchPubKey));
batch.Erase(std::make_pair(std::string("wkey"), vchPubKey)); EraseIC(std::make_pair(std::string("wkey"), vchPubKey));
} }
return true; return true;
} }
bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey) bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
return batch.Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
} }
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript) bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
return batch.Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
} }
bool CWalletDB::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta) bool CWalletDB::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta)
{ {
nWalletDBUpdateCounter++; if (!WriteIC(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)), keyMeta)) {
if (!batch.Write(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)), keyMeta))
return false; return false;
return batch.Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1'); }
return WriteIC(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
} }
bool CWalletDB::EraseWatchOnly(const CScript &dest) bool CWalletDB::EraseWatchOnly(const CScript &dest)
{ {
nWalletDBUpdateCounter++; if (!EraseIC(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)))) {
if (!batch.Erase(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest))))
return false; return false;
return batch.Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest))); }
return EraseIC(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
} }
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator) bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
{ {
nWalletDBUpdateCounter++; WriteIC(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
batch.Write(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan return WriteIC(std::string("bestblock_nomerkle"), locator);
return batch.Write(std::string("bestblock_nomerkle"), locator);
} }
bool CWalletDB::ReadBestBlock(CBlockLocator& locator) bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
@ -148,14 +134,12 @@ bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext) bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::string("orderposnext"), nOrderPosNext);
return batch.Write(std::string("orderposnext"), nOrderPosNext);
} }
bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey) bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::string("defaultkey"), vchPubKey);
return batch.Write(std::string("defaultkey"), vchPubKey);
} }
bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool) bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
@ -165,19 +149,17 @@ bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool) bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("pool"), nPool), keypool);
return batch.Write(std::make_pair(std::string("pool"), nPool), keypool);
} }
bool CWalletDB::ErasePool(int64_t nPool) bool CWalletDB::ErasePool(int64_t nPool)
{ {
nWalletDBUpdateCounter++; return EraseIC(std::make_pair(std::string("pool"), nPool));
return batch.Erase(std::make_pair(std::string("pool"), nPool));
} }
bool CWalletDB::WriteMinVersion(int nVersion) bool CWalletDB::WriteMinVersion(int nVersion)
{ {
return batch.Write(std::string("minversion"), nVersion); return WriteIC(std::string("minversion"), nVersion);
} }
bool CWalletDB::ReadAccount(const std::string& strAccount, CAccount& account) bool CWalletDB::ReadAccount(const std::string& strAccount, CAccount& account)
@ -188,17 +170,12 @@ bool CWalletDB::ReadAccount(const std::string& strAccount, CAccount& account)
bool CWalletDB::WriteAccount(const std::string& strAccount, const CAccount& account) bool CWalletDB::WriteAccount(const std::string& strAccount, const CAccount& account)
{ {
return batch.Write(std::make_pair(std::string("acc"), strAccount), account); return WriteIC(std::make_pair(std::string("acc"), strAccount), account);
} }
bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry) bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
{ {
return batch.Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry); return WriteIC(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
}
bool CWalletDB::WriteAccountingEntry_Backend(const CAccountingEntry& acentry)
{
return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
} }
CAmount CWalletDB::GetAccountCreditDebit(const std::string& strAccount) CAmount CWalletDB::GetAccountCreditDebit(const std::string& strAccount)
@ -337,8 +314,9 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
ssKey >> strAccount; ssKey >> strAccount;
uint64_t nNumber; uint64_t nNumber;
ssKey >> nNumber; ssKey >> nNumber;
if (nNumber > nAccountingEntryNumber) if (nNumber > pwallet->nAccountingEntryNumber) {
nAccountingEntryNumber = nNumber; pwallet->nAccountingEntryNumber = nNumber;
}
if (!wss.fAnyUnordered) if (!wss.fAnyUnordered)
{ {
@ -784,38 +762,39 @@ void MaybeCompactWalletDB()
return; return;
} }
static unsigned int nLastSeen = CWalletDB::GetUpdateCounter(); for (CWalletRef pwallet : vpwallets) {
static unsigned int nLastFlushed = CWalletDB::GetUpdateCounter(); CWalletDBWrapper& dbh = pwallet->GetDBHandle();
static int64_t nLastWalletUpdate = GetTime();
if (nLastSeen != CWalletDB::GetUpdateCounter()) unsigned int nUpdateCounter = dbh.nUpdateCounter;
{
nLastSeen = CWalletDB::GetUpdateCounter();
nLastWalletUpdate = GetTime();
}
if (nLastFlushed != CWalletDB::GetUpdateCounter() && GetTime() - nLastWalletUpdate >= 2) if (dbh.nLastSeen != nUpdateCounter) {
{ dbh.nLastSeen = nUpdateCounter;
if (CDB::PeriodicFlush(pwalletMain->GetDBHandle())) { dbh.nLastWalletUpdate = GetTime();
nLastFlushed = CWalletDB::GetUpdateCounter(); }
if (dbh.nLastFlushed != nUpdateCounter && GetTime() - dbh.nLastWalletUpdate >= 2) {
if (CDB::PeriodicFlush(dbh)) {
dbh.nLastFlushed = nUpdateCounter;
}
} }
} }
fOneThread = false; fOneThread = false;
} }
// //
// Try to (very carefully!) recover wallet file if there is a problem. // 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)) bool CWalletDB::Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue), std::string& out_backup_filename)
{ {
return CDB::Recover(filename, callbackDataIn, recoverKVcallback); return CDB::Recover(filename, callbackDataIn, recoverKVcallback, out_backup_filename);
} }
bool CWalletDB::Recover(const std::string& filename) bool CWalletDB::Recover(const std::string& filename, std::string& out_backup_filename)
{ {
// recover without a key filter callback // recover without a key filter callback
// results in recovering all record types // results in recovering all record types
return CWalletDB::Recover(filename, NULL, NULL); return CWalletDB::Recover(filename, NULL, NULL, out_backup_filename);
} }
bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue) bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue)
@ -848,36 +827,23 @@ bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path&
bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr) bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr)
{ {
return CDB::VerifyDatabaseFile(walletFile, dataDir, errorStr, warningStr, CWalletDB::Recover); return CDB::VerifyDatabaseFile(walletFile, dataDir, warningStr, errorStr, CWalletDB::Recover);
} }
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value) bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
return batch.Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
} }
bool CWalletDB::EraseDestData(const std::string &address, const std::string &key) bool CWalletDB::EraseDestData(const std::string &address, const std::string &key)
{ {
nWalletDBUpdateCounter++; return EraseIC(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
return batch.Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
} }
bool CWalletDB::WriteHDChain(const CHDChain& chain) bool CWalletDB::WriteHDChain(const CHDChain& chain)
{ {
nWalletDBUpdateCounter++; return WriteIC(std::string("hdchain"), chain);
return batch.Write(std::string("hdchain"), chain);
}
void CWalletDB::IncrementUpdateCounter()
{
nWalletDBUpdateCounter++;
}
unsigned int CWalletDB::GetUpdateCounter()
{
return nWalletDBUpdateCounter;
} }
bool CWalletDB::TxnBegin() bool CWalletDB::TxnBegin()

33
src/wallet/walletdb.h

@ -140,9 +140,31 @@ public:
*/ */
class CWalletDB class CWalletDB
{ {
private:
template <typename K, typename T>
bool WriteIC(const K& key, const T& value, bool fOverwrite = true)
{
if (!batch.Write(key, value, fOverwrite)) {
return false;
}
m_dbw.IncrementUpdateCounter();
return true;
}
template <typename K>
bool EraseIC(const K& key)
{
if (!batch.Erase(key)) {
return false;
}
m_dbw.IncrementUpdateCounter();
return true;
}
public: public:
CWalletDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool _fFlushOnClose = true) : CWalletDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool _fFlushOnClose = true) :
batch(dbw, pszMode, _fFlushOnClose) batch(dbw, pszMode, _fFlushOnClose),
m_dbw(dbw)
{ {
} }
@ -180,7 +202,6 @@ public:
/// This writes directly to the database, and will not update the CWallet's cached accounting entries! /// This writes directly to the database, and will not update the CWallet's cached accounting entries!
/// Use wallet.AddAccountingEntry instead, to write *and* update its caches. /// Use wallet.AddAccountingEntry instead, to write *and* update its caches.
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry); bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry);
bool ReadAccount(const std::string& strAccount, CAccount& account); bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account); bool WriteAccount(const std::string& strAccount, const CAccount& account);
@ -197,9 +218,9 @@ public:
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx); DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut); DBErrors ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut);
/* Try to (very carefully!) recover wallet database (with a possible key type filter) */ /* 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)); static bool Recover(const std::string& filename, 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 */ /* Recover convenience-function to bypass the key filter callback, called when verify fails, recovers everything */
static bool Recover(const std::string& filename); static bool Recover(const std::string& filename, std::string& out_backup_filename);
/* Recover filter (used as callback), will only let keys (cryptographical keys) as KV/key-type pass through */ /* 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); static bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDataStream ssValue);
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */ /* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
@ -212,9 +233,6 @@ public:
//! write the hdchain model (external chain child index counter) //! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain); bool WriteHDChain(const CHDChain& chain);
static void IncrementUpdateCounter();
static unsigned int GetUpdateCounter();
//! Begin a new transaction //! Begin a new transaction
bool TxnBegin(); bool TxnBegin();
//! Commit current transaction //! Commit current transaction
@ -227,6 +245,7 @@ public:
bool WriteVersion(int nVersion); bool WriteVersion(int nVersion);
private: private:
CDB batch; CDB batch;
CWalletDBWrapper& m_dbw;
CWalletDB(const CWalletDB&); CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&); void operator=(const CWalletDB&);

Loading…
Cancel
Save