This commit is contained in:
2022-02-03 23:45:47 -08:00
parent 42c2062cc4
commit 184ece190c
1438 changed files with 404064 additions and 0 deletions
+461
View File
@@ -0,0 +1,461 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "db.h"
#include "addrman.h"
#include "hash.h"
#include "protocol.h"
#include "util.h"
#include "utilstrencodings.h"
#include <stdint.h>
#ifndef WIN32
#include <sys/stat.h>
#endif
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <boost/version.hpp>
using namespace std;
using namespace boost;
unsigned int nWalletDBUpdated;
//
// CDB
//
CDBEnv bitdb;
void CDBEnv::EnvShutdown()
{
if (!fDbEnvInit)
return;
fDbEnvInit = false;
int ret = dbenv->close(0);
if (ret != 0)
LogPrintf("CDBEnv::EnvShutdown : Error %d shutting down database environment: %s\n", ret, DbEnv::strerror(ret));
if (!fMockDb)
DbEnv(0).remove(strPath.c_str(), 0);
}
void CDBEnv::Reset()
{
delete dbenv;
dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS);
fDbEnvInit = false;
fMockDb = false;
}
CDBEnv::CDBEnv() : dbenv(NULL)
{
Reset();
}
CDBEnv::~CDBEnv()
{
EnvShutdown();
delete dbenv;
dbenv = NULL;
}
void CDBEnv::Close()
{
EnvShutdown();
}
bool CDBEnv::Open(const boost::filesystem::path& pathIn)
{
if (fDbEnvInit)
return true;
boost::this_thread::interruption_point();
strPath = pathIn.string();
boost::filesystem::path pathLogDir = pathIn / "database";
TryCreateDirectory(pathLogDir);
boost::filesystem::path pathErrorFile = pathIn / "db.log";
LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
unsigned int nEnvFlags = 0;
if (GetBoolArg("-privdb", true))
nEnvFlags |= DB_PRIVATE;
dbenv->set_lg_dir(pathLogDir.string().c_str());
dbenv->set_cachesize(0, 0x100000, 1); // 1 MiB should be enough for just the wallet
dbenv->set_lg_bsize(0x10000);
dbenv->set_lg_max(1048576);
dbenv->set_lk_max_locks(40000);
dbenv->set_lk_max_objects(40000);
dbenv->set_errfile(fopen(pathErrorFile.string().c_str(), "a")); /// debug
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
int ret = dbenv->open(strPath.c_str(),
DB_CREATE |
DB_INIT_LOCK |
DB_INIT_LOG |
DB_INIT_MPOOL |
DB_INIT_TXN |
DB_THREAD |
DB_RECOVER |
nEnvFlags,
S_IRUSR | S_IWUSR);
if (ret != 0)
return error("CDBEnv::Open : Error %d opening database environment: %s\n", ret, DbEnv::strerror(ret));
fDbEnvInit = true;
fMockDb = false;
return true;
}
void CDBEnv::MakeMock()
{
if (fDbEnvInit)
throw runtime_error("CDBEnv::MakeMock : Already initialized");
boost::this_thread::interruption_point();
LogPrint("db", "CDBEnv::MakeMock\n");
dbenv->set_cachesize(1, 0, 1);
dbenv->set_lg_bsize(10485760 * 4);
dbenv->set_lg_max(10485760);
dbenv->set_lk_max_locks(10000);
dbenv->set_lk_max_objects(10000);
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
int ret = dbenv->open(NULL,
DB_CREATE |
DB_INIT_LOCK |
DB_INIT_LOG |
DB_INIT_MPOOL |
DB_INIT_TXN |
DB_THREAD |
DB_PRIVATE,
S_IRUSR | S_IWUSR);
if (ret > 0)
throw runtime_error(strprintf("CDBEnv::MakeMock : Error %d opening database environment.", ret));
fDbEnvInit = true;
fMockDb = true;
}
CDBEnv::VerifyResult CDBEnv::Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile))
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
Db db(dbenv, 0);
int result = db.verify(strFile.c_str(), NULL, NULL, 0);
if (result == 0)
return VERIFY_OK;
else if (recoverFunc == NULL)
return RECOVER_FAIL;
// Try to recover:
bool fRecovered = (*recoverFunc)(*this, strFile);
return (fRecovered ? RECOVER_OK : RECOVER_FAIL);
}
bool CDBEnv::Salvage(std::string strFile, bool fAggressive, std::vector<CDBEnv::KeyValPair>& vResult)
{
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
u_int32_t flags = DB_SALVAGE;
if (fAggressive)
flags |= DB_AGGRESSIVE;
stringstream strDump;
Db db(dbenv, 0);
int result = db.verify(strFile.c_str(), NULL, &strDump, flags);
if (result == DB_VERIFY_BAD) {
LogPrintf("CDBEnv::Salvage : Database salvage found errors, all data may not be recoverable.\n");
if (!fAggressive) {
LogPrintf("CDBEnv::Salvage : Rerun with aggressive mode to ignore errors and continue.\n");
return false;
}
}
if (result != 0 && result != DB_VERIFY_BAD) {
LogPrintf("CDBEnv::Salvage : Database salvage failed with result %d.\n", result);
return false;
}
// Format of bdb dump is ascii lines:
// header lines...
// HEADER=END
// hexadecimal key
// hexadecimal value
// ... repeated
// DATA=END
string strLine;
while (!strDump.eof() && strLine != "HEADER=END")
getline(strDump, strLine); // Skip past header
std::string keyHex, valueHex;
while (!strDump.eof() && keyHex != "DATA=END") {
getline(strDump, keyHex);
if (keyHex != "DATA=END") {
getline(strDump, valueHex);
vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
}
}
return (result == 0);
}
void CDBEnv::CheckpointLSN(const std::string& strFile)
{
dbenv->txn_checkpoint(0, 0, 0);
if (fMockDb)
return;
dbenv->lsn_reset(strFile.c_str(), 0);
}
CDB::CDB(const std::string& strFilename, const char* pszMode) : pdb(NULL), activeTxn(NULL)
{
int ret;
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
if (strFilename.empty())
return;
bool fCreate = strchr(pszMode, 'c') != NULL;
unsigned int nFlags = DB_THREAD;
if (fCreate)
nFlags |= DB_CREATE;
{
LOCK(bitdb.cs_db);
if (!bitdb.Open(GetDataDir()))
throw runtime_error("CDB : Failed to open database environment.");
strFile = strFilename;
++bitdb.mapFileUseCount[strFile];
pdb = bitdb.mapDb[strFile];
if (pdb == NULL) {
pdb = new Db(bitdb.dbenv, 0);
bool fMockDb = bitdb.IsMock();
if (fMockDb) {
DbMpoolFile* mpf = pdb->get_mpf();
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
if (ret != 0)
throw runtime_error(strprintf("CDB : Failed to configure for no temp file backing for database %s", strFile));
}
ret = pdb->open(NULL, // Txn pointer
fMockDb ? NULL : strFile.c_str(), // Filename
fMockDb ? strFile.c_str() : "main", // Logical db name
DB_BTREE, // Database type
nFlags, // Flags
0);
if (ret != 0) {
delete pdb;
pdb = NULL;
--bitdb.mapFileUseCount[strFile];
strFile = "";
throw runtime_error(strprintf("CDB : Error %d, can't open database %s", ret, strFile));
}
if (fCreate && !Exists(string("version"))) {
bool fTmp = fReadOnly;
fReadOnly = false;
WriteVersion(CLIENT_VERSION);
fReadOnly = fTmp;
}
bitdb.mapDb[strFile] = pdb;
}
}
}
void CDB::Flush()
{
if (activeTxn)
return;
// Flush database activity from memory pool to disk log
unsigned int nMinutes = 0;
if (fReadOnly)
nMinutes = 1;
bitdb.dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100) * 1024 : 0, nMinutes, 0);
}
void CDB::Close()
{
if (!pdb)
return;
if (activeTxn)
activeTxn->abort();
activeTxn = NULL;
pdb = NULL;
Flush();
{
LOCK(bitdb.cs_db);
--bitdb.mapFileUseCount[strFile];
}
}
void CDBEnv::CloseDb(const string& strFile)
{
{
LOCK(cs_db);
if (mapDb[strFile] != NULL) {
// Close the database handle
Db* pdb = mapDb[strFile];
pdb->close(0);
delete pdb;
mapDb[strFile] = NULL;
}
}
}
bool CDBEnv::RemoveDb(const string& strFile)
{
this->CloseDb(strFile);
LOCK(cs_db);
int rc = dbenv->dbremove(NULL, strFile.c_str(), NULL, DB_AUTO_COMMIT);
return (rc == 0);
}
bool CDB::Rewrite(const string& strFile, const char* pszSkip)
{
while (true) {
{
LOCK(bitdb.cs_db);
if (!bitdb.mapFileUseCount.count(strFile) || bitdb.mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
bitdb.CloseDb(strFile);
bitdb.CheckpointLSN(strFile);
bitdb.mapFileUseCount.erase(strFile);
bool fSuccess = true;
LogPrintf("CDB::Rewrite : Rewriting %s...\n", strFile);
string strFileRes = strFile + ".rewrite";
{ // surround usage of db with extra {}
CDB db(strFile.c_str(), "r");
Db* pdbCopy = new Db(bitdb.dbenv, 0);
int ret = pdbCopy->open(NULL, // Txn pointer
strFileRes.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
DB_CREATE, // Flags
0);
if (ret > 0) {
LogPrintf("CDB::Rewrite : Can't create database file %s\n", strFileRes);
fSuccess = false;
}
Dbc* pcursor = db.GetCursor();
if (pcursor)
while (fSuccess) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
int ret = db.ReadAtCursor(pcursor, ssKey, ssValue, DB_NEXT);
if (ret == DB_NOTFOUND) {
pcursor->close();
break;
} else if (ret != 0) {
pcursor->close();
fSuccess = false;
break;
}
if (pszSkip &&
strncmp(&ssKey[0], pszSkip, std::min(ssKey.size(), strlen(pszSkip))) == 0)
continue;
if (strncmp(&ssKey[0], "\x07version", 8) == 0) {
// Update version:
ssValue.clear();
ssValue << CLIENT_VERSION;
}
Dbt datKey(&ssKey[0], ssKey.size());
Dbt datValue(&ssValue[0], ssValue.size());
int ret2 = pdbCopy->put(NULL, &datKey, &datValue, DB_NOOVERWRITE);
if (ret2 > 0)
fSuccess = false;
}
if (fSuccess) {
db.Close();
bitdb.CloseDb(strFile);
if (pdbCopy->close(0))
fSuccess = false;
delete pdbCopy;
}
}
if (fSuccess) {
Db dbA(bitdb.dbenv, 0);
if (dbA.remove(strFile.c_str(), NULL, 0))
fSuccess = false;
Db dbB(bitdb.dbenv, 0);
if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
fSuccess = false;
}
if (!fSuccess)
LogPrintf("CDB::Rewrite : Failed to rewrite database file %s\n", strFileRes);
return fSuccess;
}
}
MilliSleep(100);
}
return false;
}
void CDBEnv::Flush(bool fShutdown)
{
int64_t nStart = GetTimeMillis();
// Flush log data to the actual data file on all files that are not in use
LogPrint("db", "CDBEnv::Flush : Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit)
return;
{
LOCK(cs_db);
map<string, int>::iterator mi = mapFileUseCount.begin();
while (mi != mapFileUseCount.end()) {
string strFile = (*mi).first;
int nRefCount = (*mi).second;
LogPrint("db", "CDBEnv::Flush : Flushing %s (refcount = %d)...\n", strFile, nRefCount);
if (nRefCount == 0) {
// Move log data to the dat file
CloseDb(strFile);
LogPrint("db", "CDBEnv::Flush: %s checkpoint\n", strFile);
dbenv->txn_checkpoint(0, 0, 0);
LogPrint("db", "CDBEnv::Flush: %s detach\n", strFile);
if (!fMockDb)
dbenv->lsn_reset(strFile.c_str(), 0);
LogPrint("db", "CDBEnv::Flush: %s closed\n", strFile);
mapFileUseCount.erase(mi++);
} else
mi++;
}
LogPrint("db", "CDBEnv::Flush : Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
if (fShutdown) {
char** listp;
if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb)
boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
}
}
}
}
+316
View File
@@ -0,0 +1,316 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_DB_H
#define BITCOIN_DB_H
#include "clientversion.h"
#include "serialize.h"
#include "streams.h"
#include "sync.h"
#include "version.h"
#include <map>
#include <string>
#include <vector>
#include <boost/filesystem/path.hpp>
#include <db_cxx.h>
class CDiskBlockIndex;
class COutPoint;
struct CBlockLocator;
extern unsigned int nWalletDBUpdated;
void ThreadFlushWalletDB(const std::string& strWalletFile);
class CDBEnv
{
private:
bool fDbEnvInit;
bool fMockDb;
// Don't change into boost::filesystem::path, as that can result in
// shutdown problems/crashes caused by a static initialized internal pointer.
std::string strPath;
void EnvShutdown();
public:
mutable CCriticalSection cs_db;
DbEnv *dbenv;
std::map<std::string, int> mapFileUseCount;
std::map<std::string, Db*> mapDb;
CDBEnv();
~CDBEnv();
void Reset();
void MakeMock();
bool IsMock() { return fMockDb; }
/**
* Verify that database file strFile is OK. If it is not,
* call the callback to try to recover.
* This must be called BEFORE strFile is opened.
* Returns true if strFile is OK.
*/
enum VerifyResult { VERIFY_OK,
RECOVER_OK,
RECOVER_FAIL };
VerifyResult Verify(std::string strFile, bool (*recoverFunc)(CDBEnv& dbenv, std::string strFile));
/**
* Salvage data from a file that Verify says is bad.
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
* Appends binary key/value pairs to vResult, returns true if successful.
* NOTE: reads the entire database into memory, so cannot be used
* for huge databases.
*/
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(std::string strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
bool Open(const boost::filesystem::path& path);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
void CloseDb(const std::string& strFile);
bool RemoveDb(const std::string& strFile);
DbTxn* TxnBegin(int flags = DB_TXN_WRITE_NOSYNC)
{
DbTxn* ptxn = NULL;
int ret = dbenv->txn_begin(NULL, &ptxn, flags);
if (!ptxn || ret != 0)
return NULL;
return ptxn;
}
};
extern CDBEnv bitdb;
/** RAII class that provides access to a Berkeley database */
class CDB
{
protected:
Db* pdb;
std::string strFile;
DbTxn* activeTxn;
bool fReadOnly;
explicit CDB(const std::string& strFilename, const char* pszMode = "r+");
~CDB() { Close(); }
public:
void Flush();
void Close();
private:
CDB(const CDB&);
void operator=(const CDB&);
protected:
template <typename K, typename T>
bool Read(const K& key, T& value)
{
if (!pdb)
return false;
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
Dbt datKey(&ssKey[0], ssKey.size());
// Read
Dbt datValue;
datValue.set_flags(DB_DBT_MALLOC);
int ret = pdb->get(activeTxn, &datKey, &datValue, 0);
memset(datKey.get_data(), 0, datKey.get_size());
if (datValue.get_data() == NULL)
return false;
// Unserialize value
try {
CDataStream ssValue((char*)datValue.get_data(), (char*)datValue.get_data() + datValue.get_size(), SER_DISK, CLIENT_VERSION);
ssValue >> value;
} catch (const std::exception&) {
return false;
}
// Clear and free memory
memset(datValue.get_data(), 0, datValue.get_size());
free(datValue.get_data());
return (ret == 0);
}
template <typename K, typename T>
bool Write(const K& key, const T& value, bool fOverwrite = true)
{
if (!pdb)
return false;
if (fReadOnly)
assert(!"Write called on database in read-only mode");
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
Dbt datKey(&ssKey[0], ssKey.size());
// Value
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue.reserve(10000);
ssValue << value;
Dbt datValue(&ssValue[0], ssValue.size());
// Write
int ret = pdb->put(activeTxn, &datKey, &datValue, (fOverwrite ? 0 : DB_NOOVERWRITE));
// Clear memory in case it was a private key
memset(datKey.get_data(), 0, datKey.get_size());
memset(datValue.get_data(), 0, datValue.get_size());
return (ret == 0);
}
template <typename K>
bool Erase(const K& key)
{
if (!pdb)
return false;
if (fReadOnly)
assert(!"Erase called on database in read-only mode");
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
Dbt datKey(&ssKey[0], ssKey.size());
// Erase
int ret = pdb->del(activeTxn, &datKey, 0);
// Clear memory
memset(datKey.get_data(), 0, datKey.get_size());
return (ret == 0 || ret == DB_NOTFOUND);
}
template <typename K>
bool Exists(const K& key)
{
if (!pdb)
return false;
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
Dbt datKey(&ssKey[0], ssKey.size());
// Exists
int ret = pdb->exists(activeTxn, &datKey, 0);
// Clear memory
memset(datKey.get_data(), 0, datKey.get_size());
return (ret == 0);
}
Dbc* GetCursor()
{
if (!pdb)
return NULL;
Dbc* pcursor = NULL;
int ret = pdb->cursor(NULL, &pcursor, 0);
if (ret != 0)
return NULL;
return pcursor;
}
int ReadAtCursor(Dbc* pcursor, CDataStream& ssKey, CDataStream& ssValue, unsigned int fFlags = DB_NEXT)
{
// Read at cursor
Dbt datKey;
if (fFlags == DB_SET || fFlags == DB_SET_RANGE || fFlags == DB_GET_BOTH || fFlags == DB_GET_BOTH_RANGE) {
datKey.set_data(&ssKey[0]);
datKey.set_size(ssKey.size());
}
Dbt datValue;
if (fFlags == DB_GET_BOTH || fFlags == DB_GET_BOTH_RANGE) {
datValue.set_data(&ssValue[0]);
datValue.set_size(ssValue.size());
}
datKey.set_flags(DB_DBT_MALLOC);
datValue.set_flags(DB_DBT_MALLOC);
int ret = pcursor->get(&datKey, &datValue, fFlags);
if (ret != 0)
return ret;
else if (datKey.get_data() == NULL || datValue.get_data() == NULL)
return 99999;
// Convert to streams
ssKey.SetType(SER_DISK);
ssKey.clear();
ssKey.write((char*)datKey.get_data(), datKey.get_size());
ssValue.SetType(SER_DISK);
ssValue.clear();
ssValue.write((char*)datValue.get_data(), datValue.get_size());
// Clear and free memory
memset(datKey.get_data(), 0, datKey.get_size());
memset(datValue.get_data(), 0, datValue.get_size());
free(datKey.get_data());
free(datValue.get_data());
return 0;
}
public:
bool TxnBegin()
{
if (!pdb || activeTxn)
return false;
DbTxn* ptxn = bitdb.TxnBegin();
if (!ptxn)
return false;
activeTxn = ptxn;
return true;
}
bool TxnCommit()
{
if (!pdb || !activeTxn)
return false;
int ret = activeTxn->commit(0);
activeTxn = NULL;
return (ret == 0);
}
bool TxnAbort()
{
if (!pdb || !activeTxn)
return false;
int ret = activeTxn->abort();
activeTxn = NULL;
return (ret == 0);
}
bool ReadVersion(int& nVersion)
{
nVersion = 0;
return Read(std::string("version"), nVersion);
}
bool WriteVersion(int nVersion)
{
return Write(std::string("version"), nVersion);
}
bool static Rewrite(const std::string& strFile, const char* pszSkip = NULL);
};
#endif // BITCOIN_DB_H
+546
View File
@@ -0,0 +1,546 @@
// Copyright (c) 2009-2014 The Bitcoin developers
// Copyright (c) 2014-2015 The Dash developers
// Copyright (c) 2015-2019 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "bip38.h"
#include "init.h"
#include "main.h"
#include "rpc/server.h"
#include "script/script.h"
#include "script/standard.h"
#include "sync.h"
#include "util.h"
#include "utilstrencodings.h"
#include "utiltime.h"
#include "wallet.h"
#include <fstream>
#include <secp256k1.h>
#include <stdint.h>
#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <openssl/aes.h>
#include <openssl/sha.h>
#include <univalue.h>
using namespace std;
void EnsureWalletIsUnlocked(bool fAllowAnonOnly);
std::string static EncodeDumpTime(int64_t nTime)
{
return DateTimeStrFormat("%Y-%m-%dT%H:%M:%SZ", nTime);
}
int64_t static DecodeDumpTime(const std::string& str)
{
static const boost::posix_time::ptime epoch = boost::posix_time::from_time_t(0);
static const std::locale loc(std::locale::classic(),
new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
std::istringstream iss(str);
iss.imbue(loc);
boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
iss >> ptime;
if (ptime.is_not_a_date_time())
return 0;
return (ptime - epoch).total_seconds();
}
std::string static EncodeDumpString(const std::string& str)
{
std::stringstream ret;
for (unsigned char c : str) {
if (c <= 32 || c >= 128 || c == '%') {
ret << '%' << HexStr(&c, &c + 1);
} else {
ret << c;
}
}
return ret.str();
}
std::string DecodeDumpString(const std::string& str)
{
std::stringstream ret;
for (unsigned int pos = 0; pos < str.length(); pos++) {
unsigned char c = str[pos];
if (c == '%' && pos + 2 < str.length()) {
c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) |
((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15));
pos += 2;
}
ret << c;
}
return ret.str();
}
UniValue importprivkey(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error(
"importprivkey \"agrarianprivkey\" ( \"label\" rescan )\n"
"\nAdds a private key (as returned by dumpprivkey) to your wallet.\n" +
HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"agrarianprivkey\" (string, required) The private key (see dumpprivkey)\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"\nNote: This call can take minutes to complete if rescan is true.\n"
"\nExamples:\n"
"\nDump a private key\n" +
HelpExampleCli("dumpprivkey", "\"myaddress\"") +
"\nImport the private key with rescan\n" +
HelpExampleCli("importprivkey", "\"mykey\"") +
"\nImport using a label and without rescan\n" +
HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false"));
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
string strSecret = params[0].get_str();
string strLabel = "";
if (params.size() > 1)
strLabel = params[1].get_str();
// Whether to perform rescan after import
bool fRescan = true;
if (params.size() > 2)
fRescan = params[2].get_bool();
CBitcoinSecret vchSecret;
bool fGood = vchSecret.SetString(strSecret);
if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
CKey key = vchSecret.GetKey();
if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range");
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID vchAddress = pubkey.GetID();
{
pwalletMain->MarkDirty();
pwalletMain->SetAddressBook(vchAddress, strLabel, "receive");
// Don't throw error in case a key is already there
if (pwalletMain->HaveKey(vchAddress))
return NullUniValue;
pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1;
if (!pwalletMain->AddKeyPubKey(key, pubkey))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
// whenever a key is imported, we need to scan the whole chain
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
}
}
return NullUniValue;
}
UniValue importaddress(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 3)
throw runtime_error(
"importaddress \"address\" ( \"label\" rescan )\n"
"\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend.\n"
"\nArguments:\n"
"1. \"address\" (string, required) The address\n"
"2. \"label\" (string, optional, default=\"\") An optional label\n"
"3. rescan (boolean, optional, default=true) Rescan the wallet for transactions\n"
"\nNote: This call can take minutes to complete if rescan is true.\n"
"\nExamples:\n"
"\nImport an address with rescan\n" +
HelpExampleCli("importaddress", "\"myaddress\"") +
"\nImport using a label without rescan\n" +
HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false"));
LOCK2(cs_main, pwalletMain->cs_wallet);
CScript script;
CBitcoinAddress address(params[0].get_str());
if (address.IsValid()) {
script = GetScriptForDestination(address.Get());
} else if (IsHex(params[0].get_str())) {
std::vector<unsigned char> data(ParseHex(params[0].get_str()));
script = CScript(data.begin(), data.end());
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Agrarian address or script");
}
string strLabel = "";
if (params.size() > 1)
strLabel = params[1].get_str();
// Whether to perform rescan after import
bool fRescan = true;
if (params.size() > 2)
fRescan = params[2].get_bool();
{
if (::IsMine(*pwalletMain, script) == ISMINE_SPENDABLE)
throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script");
// add to address book or update label
if (address.IsValid())
pwalletMain->SetAddressBook(address.Get(), strLabel, "receive");
// Don't throw error in case an address is already there
if (pwalletMain->HaveWatchOnly(script))
return NullUniValue;
pwalletMain->MarkDirty();
if (!pwalletMain->AddWatchOnly(script))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
if (fRescan) {
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
pwalletMain->ReacceptWalletTransactions();
}
}
return NullUniValue;
}
UniValue importwallet(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
throw runtime_error(
"importwallet \"filename\"\n"
"\nImports keys from a wallet dump file (see dumpwallet).\n" +
HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The wallet file\n"
"\nExamples:\n"
"\nDump the wallet\n" +
HelpExampleCli("dumpwallet", "\"test\"") +
"\nImport the wallet\n" +
HelpExampleCli("importwallet", "\"test\"") +
"\nImport using the json rpc call\n" +
HelpExampleRpc("importwallet", "\"test\""));
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
ifstream file;
file.open(params[0].get_str().c_str(), std::ios::in | std::ios::ate);
if (!file.is_open())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
int64_t nTimeBegin = chainActive.Tip()->GetBlockTime();
bool fGood = true;
int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
file.seekg(0, file.beg);
pwalletMain->ShowProgress(_("Importing..."), 0); // show progress dialog in GUI
while (file.good()) {
pwalletMain->ShowProgress("", std::max(1, std::min(99, (int)(((double)file.tellg() / (double)nFilesize) * 100))));
std::string line;
std::getline(file, line);
if (line.empty() || line[0] == '#')
continue;
std::vector<std::string> vstr;
boost::split(vstr, line, boost::is_any_of(" "));
if (vstr.size() < 2)
continue;
CBitcoinSecret vchSecret;
if (!vchSecret.SetString(vstr[0]))
continue;
CKey key = vchSecret.GetKey();
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
CKeyID keyid = pubkey.GetID();
if (pwalletMain->HaveKey(keyid)) {
LogPrintf("Skipping import of %s (key already present)\n", CBitcoinAddress(keyid).ToString());
continue;
}
int64_t nTime = DecodeDumpTime(vstr[1]);
std::string strLabel;
bool fLabel = true;
for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
if (boost::algorithm::starts_with(vstr[nStr], "#"))
break;
if (vstr[nStr] == "change=1")
fLabel = false;
if (vstr[nStr] == "reserve=1")
fLabel = false;
if (boost::algorithm::starts_with(vstr[nStr], "label=")) {
strLabel = DecodeDumpString(vstr[nStr].substr(6));
fLabel = true;
}
}
LogPrintf("Importing %s...\n", CBitcoinAddress(keyid).ToString());
if (!pwalletMain->AddKeyPubKey(key, pubkey)) {
fGood = false;
continue;
}
pwalletMain->mapKeyMetadata[keyid].nCreateTime = nTime;
if (fLabel)
pwalletMain->SetAddressBook(keyid, strLabel, "receive");
nTimeBegin = std::min(nTimeBegin, nTime);
}
file.close();
pwalletMain->ShowProgress("", 100); // hide progress dialog in GUI
CBlockIndex* pindex = chainActive.Tip();
while (pindex && pindex->pprev && pindex->GetBlockTime() > nTimeBegin - 7200)
pindex = pindex->pprev;
if (!pwalletMain->nTimeFirstKey || nTimeBegin < pwalletMain->nTimeFirstKey)
pwalletMain->nTimeFirstKey = nTimeBegin;
LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1);
pwalletMain->ScanForWalletTransactions(pindex);
pwalletMain->MarkDirty();
if (!fGood)
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys to wallet");
return NullUniValue;
}
UniValue dumpprivkey(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
throw runtime_error(
"dumpprivkey \"agrarianaddress\"\n"
"\nReveals the private key corresponding to 'agrarianaddress'.\n"
"Then the importprivkey can be used with this output\n" +
HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"agrarianaddress\" (string, required) The agrarian address for the private key\n"
"\nResult:\n"
"\"key\" (string) The private key\n"
"\nExamples:\n" +
HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\""));
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
string strAddress = params[0].get_str();
CBitcoinAddress address;
if (!address.SetString(strAddress))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Agrarian address");
CKeyID keyID;
if (!address.GetKeyID(keyID))
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
CKey vchSecret;
if (!pwalletMain->GetKey(keyID, vchSecret))
throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
return CBitcoinSecret(vchSecret).ToString();
}
UniValue dumpwallet(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 1)
throw runtime_error(
"dumpwallet \"filename\"\n"
"\nDumps all wallet keys in a human-readable format.\n" +
HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"filename\" (string, required) The filename\n"
"\nExamples:\n" +
HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\""));
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
boost::filesystem::path filepath = params[0].get_str().c_str();
filepath = boost::filesystem::absolute(filepath);
ofstream file;
file.open(params[0].get_str().c_str());
if (!file.is_open())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
std::map<CKeyID, int64_t> mapKeyBirth;
std::set<CKeyID> setKeyPool;
pwalletMain->GetKeyBirthTimes(mapKeyBirth);
pwalletMain->GetAllReserveKeys(setKeyPool);
// sort time/key pairs
std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
for (std::map<CKeyID, int64_t>::const_iterator it = mapKeyBirth.begin(); it != mapKeyBirth.end(); it++) {
vKeyBirth.push_back(std::make_pair(it->second, it->first));
}
mapKeyBirth.clear();
std::sort(vKeyBirth.begin(), vKeyBirth.end());
// produce output
file << strprintf("# Wallet dump created by Agrarian %s (%s)\n", CLIENT_BUILD, CLIENT_DATE);
file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()));
file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString());
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
file << "\n";
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID& keyid = it->second;
std::string strTime = EncodeDumpTime(it->first);
std::string strAddr = CBitcoinAddress(keyid).ToString();
CKey key;
if (pwalletMain->GetKey(keyid, key)) {
if (pwalletMain->mapAddressBook.count(keyid)) {
file << strprintf("%s %s label=%s # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, EncodeDumpString(pwalletMain->mapAddressBook[keyid].name), strAddr);
} else if (setKeyPool.count(keyid)) {
file << strprintf("%s %s reserve=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr);
} else {
file << strprintf("%s %s change=1 # addr=%s\n", CBitcoinSecret(key).ToString(), strTime, strAddr);
}
}
}
file << "\n";
file << "# End of dump\n";
file.close();
UniValue reply(UniValue::VOBJ);
reply.push_back(Pair("filename", filepath.string()));
return reply;
}
UniValue bip38encrypt(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 2)
throw runtime_error(
"bip38encrypt \"agrarianaddress\" \"passphrase\"\n"
"\nEncrypts a private key corresponding to 'agrarianaddress'.\n" +
HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"agrarianaddress\" (string, required) The agrarian address for the private key (you must hold the key already)\n"
"2. \"passphrase\" (string, required) The passphrase you want the private key to be encrypted with - Valid special chars: !#$%&'()*+,-./:;<=>?`{|}~ \n"
"\nResult:\n"
"\"key\" (string) The encrypted private key\n"
"\nExamples:\n" +
HelpExampleCli("bip38encrypt", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" \"mypasphrase\"") +
HelpExampleRpc("bip38encrypt", "\"DMJRSsuU9zfyrvxVaAEFQqK4MxZg6vgeS6\" \"mypasphrase\""));
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
string strAddress = params[0].get_str();
string strPassphrase = params[1].get_str();
CBitcoinAddress address;
if (!address.SetString(strAddress))
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Agrarian address");
CKeyID keyID;
if (!address.GetKeyID(keyID))
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
CKey vchSecret;
if (!pwalletMain->GetKey(keyID, vchSecret))
throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
uint256 privKey = vchSecret.GetPrivKey_256();
string encryptedOut = BIP38_Encrypt(strAddress, strPassphrase, privKey, vchSecret.IsCompressed());
UniValue result(UniValue::VOBJ);
result.push_back(Pair("Addess", strAddress));
result.push_back(Pair("Encrypted Key", encryptedOut));
return result;
}
UniValue bip38decrypt(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() != 2)
throw runtime_error(
"bip38decrypt \"agrarianaddress\" \"passphrase\"\n"
"\nDecrypts and then imports password protected private key.\n" +
HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"encryptedkey\" (string, required) The encrypted private key\n"
"2. \"passphrase\" (string, required) The passphrase you want the private key to be encrypted with\n"
"\nResult:\n"
"\"key\" (string) The decrypted private key\n"
"\nExamples:\n" +
HelpExampleCli("bip38decrypt", "\"encryptedkey\" \"mypassphrase\"") +
HelpExampleRpc("bip38decrypt", "\"encryptedkey\" \"mypassphrase\""));
LOCK2(cs_main, pwalletMain->cs_wallet);
EnsureWalletIsUnlocked();
/** Collect private key and passphrase **/
string strKey = params[0].get_str();
string strPassphrase = params[1].get_str();
uint256 privKey;
bool fCompressed;
if (!BIP38_Decrypt(strPassphrase, strKey, privKey, fCompressed))
throw JSONRPCError(RPC_WALLET_ERROR, "Failed To Decrypt");
UniValue result(UniValue::VOBJ);
result.push_back(Pair("privatekey", HexStr(privKey)));
CKey key;
key.Set(privKey.begin(), privKey.end(), fCompressed);
if (!key.IsValid())
throw JSONRPCError(RPC_WALLET_ERROR, "Private Key Not Valid");
CPubKey pubkey = key.GetPubKey();
pubkey.IsCompressed();
assert(key.VerifyPubKey(pubkey));
result.push_back(Pair("Address", CBitcoinAddress(pubkey.GetID()).ToString()));
CKeyID vchAddress = pubkey.GetID();
{
pwalletMain->MarkDirty();
pwalletMain->SetAddressBook(vchAddress, "", "receive");
// Don't throw error in case a key is already there
if (pwalletMain->HaveKey(vchAddress))
throw JSONRPCError(RPC_WALLET_ERROR, "Key already held by wallet");
pwalletMain->mapKeyMetadata[vchAddress].nCreateTime = 1;
if (!pwalletMain->AddKeyPubKey(key, pubkey))
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
// whenever a key is imported, we need to scan the whole chain
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'
pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true);
}
return result;
}
File diff suppressed because it is too large Load Diff
+308
View File
@@ -0,0 +1,308 @@
// Copyright (c) 2012-2014 The Bitcoin Core developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "wallet/wallet.h"
#include <set>
#include <stdint.h>
#include <utility>
#include <vector>
#include <boost/test/unit_test.hpp>
#include "test_agrarian.h"
// how many times to run all the tests to have a chance to catch errors that only show up with particular random shuffles
#define RUN_TESTS 100
// some tests fail 1% of the time due to bad luck.
// we repeat those tests this many times and only complain if all iterations of the test fail
#define RANDOM_REPEATS 5
using namespace std;
typedef set<pair<const CWalletTx*,unsigned int> > CoinSet;
BOOST_FIXTURE_TEST_SUITE(wallet_tests, TestingSetup)
static CWallet wallet;
static vector<COutput> vCoins;
static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0)
{
static int nextLockTime = 0;
CMutableTransaction tx;
tx.nLockTime = nextLockTime++; // so all transactions get different hashes
tx.vout.resize(nInput+1);
tx.vout[nInput].nValue = nValue;
if (fIsFromMe) {
// IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(),
// so stop vin being empty, and cache a non-zero Debit to fake out IsFromMe()
tx.vin.resize(1);
}
CWalletTx* wtx = new CWalletTx(&wallet, tx);
if (fIsFromMe)
{
wtx->fDebitCached = true;
wtx->nDebitCached = 1;
}
COutput output(wtx, nInput, nAge, true);
vCoins.push_back(output);
}
static void empty_wallet(void)
{
for (COutput output : vCoins)
delete output.tx;
vCoins.clear();
}
static bool equal_sets(CoinSet a, CoinSet b)
{
pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin());
return ret.first == a.end() && ret.second == b.end();
}
BOOST_AUTO_TEST_CASE(coin_selection_tests)
{
CoinSet setCoinsRet, setCoinsRet2;
CAmount nValueRet;
LOCK(wallet.cs_wallet);
// test multiple times to allow for differences in the shuffle order
for (int i = 0; i < RUN_TESTS; i++)
{
empty_wallet();
// with an empty wallet we can't even pay one cent
BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
add_coin(1*CENT, 4); // add a new 1 cent coin
// with a new 1 cent coin, we still can't find a mature 1 cent
BOOST_CHECK(!wallet.SelectCoinsMinConf( 1 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
// but we can find a new 1 cent
BOOST_CHECK( wallet.SelectCoinsMinConf( 1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
add_coin(2*CENT); // add a mature 2 cent coin
// we can't make 3 cents of mature coins
BOOST_CHECK(!wallet.SelectCoinsMinConf( 3 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
// we can make 3 cents of new coins
BOOST_CHECK( wallet.SelectCoinsMinConf( 3 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 3 * CENT);
add_coin(5*CENT); // add a mature 5 cent coin,
add_coin(10*CENT, 3, true); // a new 10 cent coin sent from one of our own addresses
add_coin(20*CENT); // and a mature 20 cent coin
// now we have new: 1+10=11 (of which 10 was self-sent), and mature: 2+5+20=27. total = 38
// we can't make 38 cents only if we disallow new coins:
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
// we can't even make 37 cents if we don't allow new coins even if they're from us
BOOST_CHECK(!wallet.SelectCoinsMinConf(38 * CENT, 6, 6, vCoins, setCoinsRet, nValueRet));
// but we can make 37 cents if we accept new coins from ourself
BOOST_CHECK( wallet.SelectCoinsMinConf(37 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 37 * CENT);
// and we can make 38 cents if we accept all new coins
BOOST_CHECK( wallet.SelectCoinsMinConf(38 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 38 * CENT);
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
BOOST_CHECK( wallet.SelectCoinsMinConf( 7 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 7 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// when we try making 8 cents, the smaller coins (1,2,5) are exactly enough.
BOOST_CHECK( wallet.SelectCoinsMinConf( 8 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK(nValueRet == 8 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// when we try making 9 cents, no subset of smaller coins is enough, and we get the next bigger coin (10)
BOOST_CHECK( wallet.SelectCoinsMinConf( 9 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 10 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// now clear out the wallet and start again to test choosing between subsets of smaller coins and the next biggest coin
empty_wallet();
add_coin( 6*CENT);
add_coin( 7*CENT);
add_coin( 8*CENT);
add_coin(20*CENT);
add_coin(30*CENT); // now we have 6+7+8+20+30 = 71 cents total
// check that we have 71 and not 72
BOOST_CHECK( wallet.SelectCoinsMinConf(71 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK(!wallet.SelectCoinsMinConf(72 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
// now try making 16 cents. the best smaller coins can do is 6+7+8 = 21; not as good at the next biggest coin, 20
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); // we should get 20 in one coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
add_coin( 5*CENT); // now we have 5+6+7+8+20+30 = 75 cents total
// now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, better than the next biggest coin, 20
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 3 coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
add_coin( 18*CENT); // now we have 5+6+7+8+18+20+30
// and now if we try making 16 cents again, the smaller coins can make 5+6+7 = 18 cents, the same as the next biggest coin, 18
BOOST_CHECK( wallet.SelectCoinsMinConf(16 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // we should get 18 in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // because in the event of a tie, the biggest coin wins
// now try making 11 cents. we should get 5+6
BOOST_CHECK( wallet.SelectCoinsMinConf(11 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 11 * CENT);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// check that the smallest bigger coin is used
add_coin( 1*COIN);
add_coin( 2*COIN);
add_coin( 3*COIN);
add_coin( 4*COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents
BOOST_CHECK( wallet.SelectCoinsMinConf(95 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); // we should get 1 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
BOOST_CHECK( wallet.SelectCoinsMinConf(195 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance
empty_wallet();
add_coin(0.1*CENT);
add_coin(0.2*CENT);
add_coin(0.3*CENT);
add_coin(0.4*CENT);
add_coin(0.5*CENT);
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents
// we'll get sub-cent change whatever happens, so can expect 1.0 exactly
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
// but if we add a bigger coin, making it possible to avoid sub-cent change, things change:
add_coin(1111*CENT);
// try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
// if we add more sub-cent coins:
add_coin(0.6*CENT);
add_coin(0.7*CENT);
// and try again to make 1.0 cents, we can still make 1.0 cents
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
// run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
empty_wallet();
for (int i = 0; i < 20; i++)
add_coin(50000 * COIN);
BOOST_CHECK( wallet.SelectCoinsMinConf(500000 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
// if there's not enough in the smaller coins to make at least 1 cent change (0.5+0.6+0.7 < 1.0+1.0),
// we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
empty_wallet();
add_coin(0.5 * CENT);
add_coin(0.6 * CENT);
add_coin(0.7 * CENT);
add_coin(1111 * CENT);
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
empty_wallet();
add_coin(0.4 * CENT);
add_coin(0.6 * CENT);
add_coin(0.8 * CENT);
add_coin(1111 * CENT);
BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
// test avoiding sub-cent change
empty_wallet();
add_coin(0.0005 * COIN);
add_coin(0.01 * COIN);
add_coin(1 * COIN);
// trying to make 1.0001 from these three coins
BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
// but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change
BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
// test randomness
{
empty_wallet();
for (int i2 = 0; i2 < 100; i2++)
add_coin(COIN);
// picking 50 from 100 coins doesn't depend on the shuffle,
// but does depend on randomness in the stochastic approximation code
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
BOOST_CHECK(wallet.SelectCoinsMinConf(50 * COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2));
int fails = 0;
for (int i = 0; i < RANDOM_REPEATS; i++)
{
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
// run the test RANDOM_REPEATS times and only complain if all of them fail
BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet , nValueRet));
BOOST_CHECK(wallet.SelectCoinsMinConf(COIN, 1, 6, vCoins, setCoinsRet2, nValueRet));
if (equal_sets(setCoinsRet, setCoinsRet2))
fails++;
}
BOOST_CHECK_NE(fails, RANDOM_REPEATS);
// add 75 cents in small change. not enough to make 90 cents,
// then try making 90 cents. there are multiple competing "smallest bigger" coins,
// one of which should be picked at random
add_coin( 5*CENT); add_coin(10*CENT); add_coin(15*CENT); add_coin(20*CENT); add_coin(25*CENT);
fails = 0;
for (int i = 0; i < RANDOM_REPEATS; i++)
{
// selecting 1 from 100 identical coins depends on the shuffle; this test will fail 1% of the time
// run the test RANDOM_REPEATS times and only complain if all of them fail
BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet , nValueRet));
BOOST_CHECK(wallet.SelectCoinsMinConf(90*CENT, 1, 6, vCoins, setCoinsRet2, nValueRet));
if (equal_sets(setCoinsRet, setCoinsRet2))
fails++;
}
BOOST_CHECK_NE(fails, RANDOM_REPEATS);
}
}
empty_wallet();
}
BOOST_AUTO_TEST_SUITE_END()
File diff suppressed because it is too large Load Diff
+1221
View File
File diff suppressed because it is too large Load Diff
+100
View File
@@ -0,0 +1,100 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin developers
// Copyright (c) 2016-2019 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "wallet_ismine.h"
#include "key.h"
#include "keystore.h"
#include "script/script.h"
#include "script/standard.h"
#include "util.h"
using namespace std;
typedef vector<unsigned char> valtype;
unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore)
{
unsigned int nResult = 0;
for (const valtype& pubkey : pubkeys) {
CKeyID keyID = CPubKey(pubkey).GetID();
if(keystore.HaveKey(keyID))
++nResult;
}
return nResult;
}
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest)
{
CScript script = GetScriptForDestination(dest);
return IsMine(keystore, script);
}
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey)
{
if(keystore.HaveWatchOnly(scriptPubKey))
return ISMINE_WATCH_ONLY;
if(keystore.HaveMultiSig(scriptPubKey))
return ISMINE_MULTISIG;
vector<valtype> vSolutions;
txnouttype whichType;
if(!Solver(scriptPubKey, whichType, vSolutions)) {
if(keystore.HaveWatchOnly(scriptPubKey))
return ISMINE_WATCH_ONLY;
if(keystore.HaveMultiSig(scriptPubKey))
return ISMINE_MULTISIG;
return ISMINE_NO;
}
CKeyID keyID;
switch (whichType) {
case TX_NONSTANDARD:
case TX_NULL_DATA:
break;
case TX_ZEROCOINMINT:
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
if(keystore.HaveKey(keyID))
return ISMINE_SPENDABLE;
break;
case TX_PUBKEYHASH:
keyID = CKeyID(uint160(vSolutions[0]));
if(keystore.HaveKey(keyID))
return ISMINE_SPENDABLE;
break;
case TX_SCRIPTHASH: {
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
CScript subscript;
if(keystore.GetCScript(scriptID, subscript)) {
isminetype ret = IsMine(keystore, subscript);
if(ret != ISMINE_NO)
return ret;
}
break;
}
case TX_MULTISIG: {
// Only consider transactions "mine" if we own ALL the
// keys involved. multi-signature transactions that are
// partially owned (somebody else has a key that can spend
// them) enable spend-out-from-under-you attacks, especially
// in shared-wallet situations.
vector<valtype> keys(vSolutions.begin() + 1, vSolutions.begin() + vSolutions.size() - 1);
if(HaveKeys(keys, keystore) == keys.size())
return ISMINE_SPENDABLE;
break;
}
}
if(keystore.HaveWatchOnly(scriptPubKey))
return ISMINE_WATCH_ONLY;
if(keystore.HaveMultiSig(scriptPubKey))
return ISMINE_MULTISIG;
return ISMINE_NO;
}
+32
View File
@@ -0,0 +1,32 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin developers
// Copyright (c) 2017-2019 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_ISMINE_H
#define BITCOIN_WALLET_ISMINE_H
#include "key.h"
#include "script/standard.h"
class CKeyStore;
class CScript;
/** IsMine() return codes */
enum isminetype {
ISMINE_NO = 0,
//! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys
ISMINE_WATCH_ONLY = 1,
//! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys
ISMINE_MULTISIG = 2,
ISMINE_SPENDABLE = 4,
ISMINE_ALL = ISMINE_WATCH_ONLY | ISMINE_SPENDABLE
};
/** used for bitflags of isminetype */
typedef uint8_t isminefilter;
isminetype IsMine(const CKeyStore& keystore, const CScript& scriptPubKey);
isminetype IsMine(const CKeyStore& keystore, const CTxDestination& dest);
#endif // BITCOIN_WALLET_ISMINE_H
File diff suppressed because it is too large Load Diff
+212
View File
@@ -0,0 +1,212 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2013 The Bitcoin developers
// Copyright (c) 2016-2019 The PIVX developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLETDB_H
#define BITCOIN_WALLETDB_H
#include "amount.h"
#include "wallet/db.h"
#include "key.h"
#include "keystore.h"
#include "zagr/zerocoin.h"
#include "libzerocoin/Accumulator.h"
#include "libzerocoin/Denominations.h"
#include "zagr/zagrtracker.h"
#include <list>
#include <stdint.h>
#include <string>
#include <utility>
#include <vector>
class CAccount;
class CAccountingEntry;
class CBitcoinAddress;
struct CBlockLocator;
class CKeyPool;
class CMasterKey;
class CScript;
class CWallet;
class CWalletTx;
class CDeterministicMint;
class CZerocoinMint;
class CZerocoinSpend;
class uint160;
class uint256;
/** Error statuses for the wallet database */
enum DBErrors {
DB_LOAD_OK,
DB_CORRUPT,
DB_NONCRITICAL_ERROR,
DB_TOO_NEW,
DB_LOAD_FAIL,
DB_NEED_REWRITE
};
class CKeyMetadata
{
public:
static const int CURRENT_VERSION = 1;
int nVersion;
int64_t nCreateTime; // 0 means unknown
CKeyMetadata()
{
SetNull();
}
CKeyMetadata(int64_t nCreateTime_)
{
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = nCreateTime_;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion)
{
READWRITE(this->nVersion);
nVersion = this->nVersion;
READWRITE(nCreateTime);
}
void SetNull()
{
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
}
};
/** Access to the wallet database (wallet.dat) */
class CWalletDB : public CDB
{
public:
CWalletDB(const std::string& strFilename, const char* pszMode = "r+") : CDB(strFilename, pszMode)
{
}
bool WriteName(const std::string& strAddress, const std::string& strName);
bool EraseName(const std::string& strAddress);
bool WritePurpose(const std::string& strAddress, const std::string& purpose);
bool ErasePurpose(const std::string& strAddress);
bool WriteTx(uint256 hash, const CWalletTx& wtx);
bool EraseTx(uint256 hash);
bool WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta);
bool WriteCryptedKey(const CPubKey& vchPubKey, const std::vector<unsigned char>& vchCryptedSecret, const CKeyMetadata& keyMeta);
bool WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey);
bool WriteAutoConvertKey(const CBitcoinAddress& btcAddress);
void LoadAutoConvertKeys(std::set<CBitcoinAddress>& setAddresses);
bool WriteCScript(const uint160& hash, const CScript& redeemScript);
bool WriteWatchOnly(const CScript& script);
bool EraseWatchOnly(const CScript& script);
bool WriteMultiSig(const CScript& script);
bool EraseMultiSig(const CScript& script);
bool WriteBestBlock(const CBlockLocator& locator);
bool ReadBestBlock(CBlockLocator& locator);
bool WriteOrderPosNext(int64_t nOrderPosNext);
// presstab
bool WriteStakeSplitThreshold(uint64_t nStakeSplitThreshold);
bool WriteMultiSend(std::vector<std::pair<std::string, int> > vMultiSend);
bool EraseMultiSend(std::vector<std::pair<std::string, int> > vMultiSend);
bool WriteMSettings(bool fMultiSendStake, bool fMultiSendMasternode, int nLastMultiSendHeight);
bool WriteMSDisabledAddresses(std::vector<std::string> vDisabledAddresses);
bool EraseMSDisabledAddresses(std::vector<std::string> vDisabledAddresses);
bool WriteAutoCombineSettings(bool fEnable, CAmount nCombineThreshold);
bool WriteDefaultKey(const CPubKey& vchPubKey);
bool ReadPool(int64_t nPool, CKeyPool& keypool);
bool WritePool(int64_t nPool, const CKeyPool& keypool);
bool ErasePool(int64_t nPool);
bool WriteMinVersion(int nVersion);
/// 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.
bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry);
bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account);
/// Write destination data key,value tuple to database
bool WriteDestData(const std::string& address, const std::string& key, const std::string& value);
/// Erase destination data tuple from wallet database
bool EraseDestData(const std::string& address, const std::string& key);
CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);
DBErrors ReorderTransactions(CWallet* pwallet);
DBErrors LoadWallet(CWallet* pwallet);
DBErrors FindWalletTx(CWallet* pwallet, std::vector<uint256>& vTxHash, std::vector<CWalletTx>& vWtx);
DBErrors ZapWalletTx(CWallet* pwallet, std::vector<CWalletTx>& vWtx);
static bool Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys);
static bool Recover(CDBEnv& dbenv, std::string filename);
bool WriteDeterministicMint(const CDeterministicMint& dMint);
bool ReadDeterministicMint(const uint256& hashPubcoin, CDeterministicMint& dMint);
bool EraseDeterministicMint(const uint256& hashPubcoin);
bool WriteZerocoinMint(const CZerocoinMint& zerocoinMint);
bool EraseZerocoinMint(const CZerocoinMint& zerocoinMint);
bool ReadZerocoinMint(const CBigNum &bnPubcoinValue, CZerocoinMint& zerocoinMint);
bool ReadZerocoinMint(const uint256& hashPubcoin, CZerocoinMint& mint);
bool ArchiveMintOrphan(const CZerocoinMint& zerocoinMint);
bool ArchiveDeterministicOrphan(const CDeterministicMint& dMint);
bool UnarchiveZerocoinMint(const uint256& hashPubcoin, CZerocoinMint& mint);
bool UnarchiveDeterministicMint(const uint256& hashPubcoin, CDeterministicMint& dMint);
std::list<CZerocoinMint> ListMintedCoins();
std::list<CDeterministicMint> ListDeterministicMints();
std::list<CZerocoinSpend> ListSpentCoins();
std::list<CBigNum> ListSpentCoinsSerial();
std::list<CZerocoinMint> ListArchivedZerocoins();
std::list<CDeterministicMint> ListArchivedDeterministicMints();
bool WriteZerocoinSpendSerialEntry(const CZerocoinSpend& zerocoinSpend);
bool EraseZerocoinSpendSerialEntry(const CBigNum& serialEntry);
bool ReadZerocoinSpendSerialEntry(const CBigNum& bnSerial);
bool WriteCurrentSeedHash(const uint256& hashSeed);
bool ReadCurrentSeedHash(uint256& hashSeed);
bool WriteZAGRSeed(const uint256& hashSeed, const vector<unsigned char>& seed);
bool ReadZAGRSeed(const uint256& hashSeed, vector<unsigned char>& seed);
bool ReadZAGRSeed_deprecated(uint256& seed);
bool EraseZAGRSeed();
bool EraseZAGRSeed_deprecated();
bool WriteZAGRCount(const uint32_t& nCount);
bool ReadZAGRCount(uint32_t& nCount);
std::map<uint256, std::vector<pair<uint256, uint32_t> > > MapMintPool();
bool WriteMintPoolPair(const uint256& hashMasterSeed, const uint256& hashPubcoin, const uint32_t& nCount);
void LoadPrecomputes(std::list<std::pair<uint256, CoinWitnessCacheData> >& itemList, std::map<uint256, list<std::pair<uint256, CoinWitnessCacheData> >::iterator>& itemMap);
void LoadPrecomputes(set<uint256> setHashes);
void EraseAllPrecomputes();
bool WritePrecompute(const uint256& hash, const CoinWitnessCacheData& data);
bool ReadPrecompute(const uint256& hash, CoinWitnessCacheData& data);
bool ErasePrecompute(const uint256& hash);
private:
CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&);
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
};
void NotifyBacked(const CWallet& wallet, bool fSuccess, string strMessage);
bool BackupWallet(const CWallet& wallet, const boost::filesystem::path& strDest, bool fEnableCustom = true);
bool AttemptBackupWallet(const CWallet& wallet, const boost::filesystem::path& pathSrc, const boost::filesystem::path& pathDest);
#endif // BITCOIN_WALLETDB_H