Merge pull request #5745
50c72f2 [Move Only] Move wallet related things to src/wallet/ (Jonas Schnelli)
This commit is contained in:
462
src/wallet/db.cpp
Normal file
462
src/wallet/db.cpp
Normal file
@@ -0,0 +1,462 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "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;
|
||||
|
||||
|
||||
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(path.string().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();
|
||||
|
||||
path = pathIn;
|
||||
boost::filesystem::path pathLogDir = path / "database";
|
||||
TryCreateDirectory(pathLogDir);
|
||||
boost::filesystem::path pathErrorFile = path / "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(path.string().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, bool fFlushOnCloseIn) : pdb(NULL), activeTxn(NULL)
|
||||
{
|
||||
int ret;
|
||||
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
|
||||
fFlushOnClose = fFlushOnCloseIn;
|
||||
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;
|
||||
|
||||
if (fFlushOnClose)
|
||||
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(path / "database");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
313
src/wallet/db.h
Normal file
313
src/wallet/db.h
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_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;
|
||||
|
||||
extern unsigned int nWalletDBUpdated;
|
||||
|
||||
void ThreadFlushWalletDB(const std::string& strWalletFile);
|
||||
|
||||
|
||||
class CDBEnv
|
||||
{
|
||||
private:
|
||||
bool fDbEnvInit;
|
||||
bool fMockDb;
|
||||
boost::filesystem::path path;
|
||||
|
||||
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;
|
||||
bool fFlushOnClose;
|
||||
|
||||
explicit CDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
|
||||
~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
|
||||
409
src/wallet/rpcdump.cpp
Normal file
409
src/wallet/rpcdump.cpp
Normal file
@@ -0,0 +1,409 @@
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "base58.h"
|
||||
#include "rpcserver.h"
|
||||
#include "init.h"
|
||||
#include "main.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "sync.h"
|
||||
#include "util.h"
|
||||
#include "utiltime.h"
|
||||
#include "wallet.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
#include "json/json_spirit_value.h"
|
||||
|
||||
using namespace json_spirit;
|
||||
using namespace std;
|
||||
|
||||
void EnsureWalletIsUnlocked();
|
||||
|
||||
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;
|
||||
BOOST_FOREACH(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();
|
||||
}
|
||||
|
||||
Value importprivkey(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() < 1 || params.size() > 3)
|
||||
throw runtime_error(
|
||||
"importprivkey \"bitcoinprivkey\" ( \"label\" rescan )\n"
|
||||
"\nAdds a private key (as returned by dumpprivkey) to your wallet.\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"bitcoinprivkey\" (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 Value::null;
|
||||
|
||||
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 Value::null;
|
||||
}
|
||||
|
||||
Value importaddress(const Array& 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 Bitcoin 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 Value::null;
|
||||
|
||||
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 Value::null;
|
||||
}
|
||||
|
||||
Value importwallet(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"importwallet \"filename\"\n"
|
||||
"\nImports keys from a wallet dump file (see dumpwallet).\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 Value::null;
|
||||
}
|
||||
|
||||
Value dumpprivkey(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"dumpprivkey \"bitcoinaddress\"\n"
|
||||
"\nReveals the private key corresponding to 'bitcoinaddress'.\n"
|
||||
"Then the importprivkey can be used with this output\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"bitcoinaddress\" (string, required) The bitcoin 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 Bitcoin 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();
|
||||
}
|
||||
|
||||
|
||||
Value dumpwallet(const Array& params, bool fHelp)
|
||||
{
|
||||
if (fHelp || params.size() != 1)
|
||||
throw runtime_error(
|
||||
"dumpwallet \"filename\"\n"
|
||||
"\nDumps all wallet keys in a human-readable format.\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();
|
||||
|
||||
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 Bitcoin %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();
|
||||
return Value::null;
|
||||
}
|
||||
2097
src/wallet/rpcwallet.cpp
Normal file
2097
src/wallet/rpcwallet.cpp
Normal file
File diff suppressed because it is too large
Load Diff
310
src/wallet/test/wallet_tests.cpp
Normal file
310
src/wallet/test/wallet_tests.cpp
Normal file
@@ -0,0 +1,310 @@
|
||||
// Copyright (c) 2012-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "wallet/wallet.h"
|
||||
|
||||
#include <set>
|
||||
#include <stdint.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "test/test_bitcoin.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
// 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)
|
||||
{
|
||||
BOOST_FOREACH(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()
|
||||
2607
src/wallet/wallet.cpp
Normal file
2607
src/wallet/wallet.cpp
Normal file
File diff suppressed because it is too large
Load Diff
906
src/wallet/wallet.h
Normal file
906
src/wallet/wallet.h
Normal file
@@ -0,0 +1,906 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_WALLET_H
|
||||
#define BITCOIN_WALLET_H
|
||||
|
||||
#include "amount.h"
|
||||
#include "primitives/block.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "crypter.h"
|
||||
#include "key.h"
|
||||
#include "keystore.h"
|
||||
#include "main.h"
|
||||
#include "ui_interface.h"
|
||||
#include "wallet/wallet_ismine.h"
|
||||
#include "wallet/walletdb.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
/**
|
||||
* Settings
|
||||
*/
|
||||
extern CFeeRate payTxFee;
|
||||
extern CAmount maxTxFee;
|
||||
extern unsigned int nTxConfirmTarget;
|
||||
extern bool bSpendZeroConfChange;
|
||||
extern bool fSendFreeTransactions;
|
||||
extern bool fPayAtLeastCustomFee;
|
||||
|
||||
//! -paytxfee default
|
||||
static const CAmount DEFAULT_TRANSACTION_FEE = 0;
|
||||
//! -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB
|
||||
static const CAmount nHighTransactionFeeWarning = 0.01 * COIN;
|
||||
//! -maxtxfee default
|
||||
static const CAmount DEFAULT_TRANSACTION_MAXFEE = 0.1 * COIN;
|
||||
//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis)
|
||||
static const CAmount nHighTransactionMaxFeeWarning = 100 * nHighTransactionFeeWarning;
|
||||
//! Largest (in bytes) free transaction we're willing to create
|
||||
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
|
||||
|
||||
class CAccountingEntry;
|
||||
class CCoinControl;
|
||||
class COutput;
|
||||
class CReserveKey;
|
||||
class CScript;
|
||||
class CWalletTx;
|
||||
|
||||
/** (client) version numbers for particular wallet features */
|
||||
enum WalletFeature
|
||||
{
|
||||
FEATURE_BASE = 10500, // the earliest version new wallets supports (only useful for getinfo's clientversion output)
|
||||
|
||||
FEATURE_WALLETCRYPT = 40000, // wallet encryption
|
||||
FEATURE_COMPRPUBKEY = 60000, // compressed public keys
|
||||
|
||||
FEATURE_LATEST = 60000
|
||||
};
|
||||
|
||||
|
||||
/** A key pool entry */
|
||||
class CKeyPool
|
||||
{
|
||||
public:
|
||||
int64_t nTime;
|
||||
CPubKey vchPubKey;
|
||||
|
||||
CKeyPool();
|
||||
CKeyPool(const CPubKey& vchPubKeyIn);
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
if (!(nType & SER_GETHASH))
|
||||
READWRITE(nVersion);
|
||||
READWRITE(nTime);
|
||||
READWRITE(vchPubKey);
|
||||
}
|
||||
};
|
||||
|
||||
/** Address book data */
|
||||
class CAddressBookData
|
||||
{
|
||||
public:
|
||||
std::string name;
|
||||
std::string purpose;
|
||||
|
||||
CAddressBookData()
|
||||
{
|
||||
purpose = "unknown";
|
||||
}
|
||||
|
||||
typedef std::map<std::string, std::string> StringMap;
|
||||
StringMap destdata;
|
||||
};
|
||||
|
||||
struct CRecipient
|
||||
{
|
||||
CScript scriptPubKey;
|
||||
CAmount nAmount;
|
||||
bool fSubtractFeeFromAmount;
|
||||
};
|
||||
|
||||
typedef std::map<std::string, std::string> mapValue_t;
|
||||
|
||||
|
||||
static void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
|
||||
{
|
||||
if (!mapValue.count("n"))
|
||||
{
|
||||
nOrderPos = -1; // TODO: calculate elsewhere
|
||||
return;
|
||||
}
|
||||
nOrderPos = atoi64(mapValue["n"].c_str());
|
||||
}
|
||||
|
||||
|
||||
static void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
|
||||
{
|
||||
if (nOrderPos == -1)
|
||||
return;
|
||||
mapValue["n"] = i64tostr(nOrderPos);
|
||||
}
|
||||
|
||||
struct COutputEntry
|
||||
{
|
||||
CTxDestination destination;
|
||||
CAmount amount;
|
||||
int vout;
|
||||
};
|
||||
|
||||
/** A transaction with a merkle branch linking it to the block chain. */
|
||||
class CMerkleTx : public CTransaction
|
||||
{
|
||||
private:
|
||||
int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const;
|
||||
|
||||
public:
|
||||
uint256 hashBlock;
|
||||
std::vector<uint256> vMerkleBranch;
|
||||
int nIndex;
|
||||
|
||||
// memory only
|
||||
mutable bool fMerkleVerified;
|
||||
|
||||
|
||||
CMerkleTx()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
CMerkleTx(const CTransaction& txIn) : CTransaction(txIn)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
hashBlock = uint256();
|
||||
nIndex = -1;
|
||||
fMerkleVerified = false;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
READWRITE(*(CTransaction*)this);
|
||||
nVersion = this->nVersion;
|
||||
READWRITE(hashBlock);
|
||||
READWRITE(vMerkleBranch);
|
||||
READWRITE(nIndex);
|
||||
}
|
||||
|
||||
int SetMerkleBranch(const CBlock& block);
|
||||
|
||||
|
||||
/**
|
||||
* Return depth of transaction in blockchain:
|
||||
* -1 : not in blockchain, and not in memory pool (conflicted transaction)
|
||||
* 0 : in memory pool, waiting to be included in a block
|
||||
* >=1 : this many blocks deep in the main chain
|
||||
*/
|
||||
int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
|
||||
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
|
||||
bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
|
||||
int GetBlocksToMaturity() const;
|
||||
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
|
||||
};
|
||||
|
||||
/**
|
||||
* A transaction with a bunch of additional info that only the owner cares about.
|
||||
* It includes any unrecorded transactions needed to link it back to the block chain.
|
||||
*/
|
||||
class CWalletTx : public CMerkleTx
|
||||
{
|
||||
private:
|
||||
const CWallet* pwallet;
|
||||
|
||||
public:
|
||||
mapValue_t mapValue;
|
||||
std::vector<std::pair<std::string, std::string> > vOrderForm;
|
||||
unsigned int fTimeReceivedIsTxTime;
|
||||
unsigned int nTimeReceived; //! time received by this node
|
||||
unsigned int nTimeSmart;
|
||||
char fFromMe;
|
||||
std::string strFromAccount;
|
||||
int64_t nOrderPos; //! position in ordered transaction list
|
||||
|
||||
// memory only
|
||||
mutable bool fDebitCached;
|
||||
mutable bool fCreditCached;
|
||||
mutable bool fImmatureCreditCached;
|
||||
mutable bool fAvailableCreditCached;
|
||||
mutable bool fWatchDebitCached;
|
||||
mutable bool fWatchCreditCached;
|
||||
mutable bool fImmatureWatchCreditCached;
|
||||
mutable bool fAvailableWatchCreditCached;
|
||||
mutable bool fChangeCached;
|
||||
mutable CAmount nDebitCached;
|
||||
mutable CAmount nCreditCached;
|
||||
mutable CAmount nImmatureCreditCached;
|
||||
mutable CAmount nAvailableCreditCached;
|
||||
mutable CAmount nWatchDebitCached;
|
||||
mutable CAmount nWatchCreditCached;
|
||||
mutable CAmount nImmatureWatchCreditCached;
|
||||
mutable CAmount nAvailableWatchCreditCached;
|
||||
mutable CAmount nChangeCached;
|
||||
|
||||
CWalletTx()
|
||||
{
|
||||
Init(NULL);
|
||||
}
|
||||
|
||||
CWalletTx(const CWallet* pwalletIn)
|
||||
{
|
||||
Init(pwalletIn);
|
||||
}
|
||||
|
||||
CWalletTx(const CWallet* pwalletIn, const CMerkleTx& txIn) : CMerkleTx(txIn)
|
||||
{
|
||||
Init(pwalletIn);
|
||||
}
|
||||
|
||||
CWalletTx(const CWallet* pwalletIn, const CTransaction& txIn) : CMerkleTx(txIn)
|
||||
{
|
||||
Init(pwalletIn);
|
||||
}
|
||||
|
||||
void Init(const CWallet* pwalletIn)
|
||||
{
|
||||
pwallet = pwalletIn;
|
||||
mapValue.clear();
|
||||
vOrderForm.clear();
|
||||
fTimeReceivedIsTxTime = false;
|
||||
nTimeReceived = 0;
|
||||
nTimeSmart = 0;
|
||||
fFromMe = false;
|
||||
strFromAccount.clear();
|
||||
fDebitCached = false;
|
||||
fCreditCached = false;
|
||||
fImmatureCreditCached = false;
|
||||
fAvailableCreditCached = false;
|
||||
fWatchDebitCached = false;
|
||||
fWatchCreditCached = false;
|
||||
fImmatureWatchCreditCached = false;
|
||||
fAvailableWatchCreditCached = false;
|
||||
fChangeCached = false;
|
||||
nDebitCached = 0;
|
||||
nCreditCached = 0;
|
||||
nImmatureCreditCached = 0;
|
||||
nAvailableCreditCached = 0;
|
||||
nWatchDebitCached = 0;
|
||||
nWatchCreditCached = 0;
|
||||
nAvailableWatchCreditCached = 0;
|
||||
nImmatureWatchCreditCached = 0;
|
||||
nChangeCached = 0;
|
||||
nOrderPos = -1;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
if (ser_action.ForRead())
|
||||
Init(NULL);
|
||||
char fSpent = false;
|
||||
|
||||
if (!ser_action.ForRead())
|
||||
{
|
||||
mapValue["fromaccount"] = strFromAccount;
|
||||
|
||||
WriteOrderPos(nOrderPos, mapValue);
|
||||
|
||||
if (nTimeSmart)
|
||||
mapValue["timesmart"] = strprintf("%u", nTimeSmart);
|
||||
}
|
||||
|
||||
READWRITE(*(CMerkleTx*)this);
|
||||
std::vector<CMerkleTx> vUnused; //! Used to be vtxPrev
|
||||
READWRITE(vUnused);
|
||||
READWRITE(mapValue);
|
||||
READWRITE(vOrderForm);
|
||||
READWRITE(fTimeReceivedIsTxTime);
|
||||
READWRITE(nTimeReceived);
|
||||
READWRITE(fFromMe);
|
||||
READWRITE(fSpent);
|
||||
|
||||
if (ser_action.ForRead())
|
||||
{
|
||||
strFromAccount = mapValue["fromaccount"];
|
||||
|
||||
ReadOrderPos(nOrderPos, mapValue);
|
||||
|
||||
nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0;
|
||||
}
|
||||
|
||||
mapValue.erase("fromaccount");
|
||||
mapValue.erase("version");
|
||||
mapValue.erase("spent");
|
||||
mapValue.erase("n");
|
||||
mapValue.erase("timesmart");
|
||||
}
|
||||
|
||||
//! make sure balances are recalculated
|
||||
void MarkDirty()
|
||||
{
|
||||
fCreditCached = false;
|
||||
fAvailableCreditCached = false;
|
||||
fWatchDebitCached = false;
|
||||
fWatchCreditCached = false;
|
||||
fAvailableWatchCreditCached = false;
|
||||
fImmatureWatchCreditCached = false;
|
||||
fDebitCached = false;
|
||||
fChangeCached = false;
|
||||
}
|
||||
|
||||
void BindWallet(CWallet *pwalletIn)
|
||||
{
|
||||
pwallet = pwalletIn;
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
//! filter decides which addresses will count towards the debit
|
||||
CAmount GetDebit(const isminefilter& filter) const;
|
||||
CAmount GetCredit(const isminefilter& filter) const;
|
||||
CAmount GetImmatureCredit(bool fUseCache=true) const;
|
||||
CAmount GetAvailableCredit(bool fUseCache=true) const;
|
||||
CAmount GetImmatureWatchOnlyCredit(const bool& fUseCache=true) const;
|
||||
CAmount GetAvailableWatchOnlyCredit(const bool& fUseCache=true) const;
|
||||
CAmount GetChange() const;
|
||||
|
||||
void GetAmounts(std::list<COutputEntry>& listReceived,
|
||||
std::list<COutputEntry>& listSent, CAmount& nFee, std::string& strSentAccount, const isminefilter& filter) const;
|
||||
|
||||
void GetAccountAmounts(const std::string& strAccount, CAmount& nReceived,
|
||||
CAmount& nSent, CAmount& nFee, const isminefilter& filter) const;
|
||||
|
||||
bool IsFromMe(const isminefilter& filter) const
|
||||
{
|
||||
return (GetDebit(filter) > 0);
|
||||
}
|
||||
|
||||
bool IsTrusted() const;
|
||||
|
||||
bool WriteToDisk(CWalletDB *pwalletdb);
|
||||
|
||||
int64_t GetTxTime() const;
|
||||
int GetRequestCount() const;
|
||||
|
||||
void RelayWalletTransaction();
|
||||
|
||||
std::set<uint256> GetConflicts() const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
class COutput
|
||||
{
|
||||
public:
|
||||
const CWalletTx *tx;
|
||||
int i;
|
||||
int nDepth;
|
||||
bool fSpendable;
|
||||
|
||||
COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn)
|
||||
{
|
||||
tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn;
|
||||
}
|
||||
|
||||
std::string ToString() const;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/** Private key that includes an expiration date in case it never gets used. */
|
||||
class CWalletKey
|
||||
{
|
||||
public:
|
||||
CPrivKey vchPrivKey;
|
||||
int64_t nTimeCreated;
|
||||
int64_t nTimeExpires;
|
||||
std::string strComment;
|
||||
//! todo: add something to note what created it (user, getnewaddress, change)
|
||||
//! maybe should have a map<string, string> property map
|
||||
|
||||
CWalletKey(int64_t nExpires=0);
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
if (!(nType & SER_GETHASH))
|
||||
READWRITE(nVersion);
|
||||
READWRITE(vchPrivKey);
|
||||
READWRITE(nTimeCreated);
|
||||
READWRITE(nTimeExpires);
|
||||
READWRITE(LIMITED_STRING(strComment, 65536));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* A CWallet is an extension of a keystore, which also maintains a set of transactions and balances,
|
||||
* and provides the ability to create new transactions.
|
||||
*/
|
||||
class CWallet : public CCryptoKeyStore, public CValidationInterface
|
||||
{
|
||||
private:
|
||||
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
|
||||
|
||||
CWalletDB *pwalletdbEncryption;
|
||||
|
||||
//! the current wallet version: clients below this version are not able to load the wallet
|
||||
int nWalletVersion;
|
||||
|
||||
//! the maximum wallet format version: memory-only variable that specifies to what version this wallet may be upgraded
|
||||
int nWalletMaxVersion;
|
||||
|
||||
int64_t nNextResend;
|
||||
int64_t nLastResend;
|
||||
|
||||
/**
|
||||
* Used to keep track of spent outpoints, and
|
||||
* detect and report conflicts (double-spends or
|
||||
* mutated transactions where the mutant gets mined).
|
||||
*/
|
||||
typedef std::multimap<COutPoint, uint256> TxSpends;
|
||||
TxSpends mapTxSpends;
|
||||
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
|
||||
void AddToSpends(const uint256& wtxid);
|
||||
|
||||
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
|
||||
|
||||
public:
|
||||
/*
|
||||
* Main wallet lock.
|
||||
* This lock protects all the fields added by CWallet
|
||||
* except for:
|
||||
* fFileBacked (immutable after instantiation)
|
||||
* strWalletFile (immutable after instantiation)
|
||||
*/
|
||||
mutable CCriticalSection cs_wallet;
|
||||
|
||||
bool fFileBacked;
|
||||
std::string strWalletFile;
|
||||
|
||||
std::set<int64_t> setKeyPool;
|
||||
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
|
||||
|
||||
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
|
||||
MasterKeyMap mapMasterKeys;
|
||||
unsigned int nMasterKeyMaxID;
|
||||
|
||||
CWallet()
|
||||
{
|
||||
SetNull();
|
||||
}
|
||||
|
||||
CWallet(std::string strWalletFileIn)
|
||||
{
|
||||
SetNull();
|
||||
|
||||
strWalletFile = strWalletFileIn;
|
||||
fFileBacked = true;
|
||||
}
|
||||
|
||||
~CWallet()
|
||||
{
|
||||
delete pwalletdbEncryption;
|
||||
pwalletdbEncryption = NULL;
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
{
|
||||
nWalletVersion = FEATURE_BASE;
|
||||
nWalletMaxVersion = FEATURE_BASE;
|
||||
fFileBacked = false;
|
||||
nMasterKeyMaxID = 0;
|
||||
pwalletdbEncryption = NULL;
|
||||
nOrderPosNext = 0;
|
||||
nNextResend = 0;
|
||||
nLastResend = 0;
|
||||
nTimeFirstKey = 0;
|
||||
}
|
||||
|
||||
std::map<uint256, CWalletTx> mapWallet;
|
||||
|
||||
int64_t nOrderPosNext;
|
||||
std::map<uint256, int> mapRequestCount;
|
||||
|
||||
std::map<CTxDestination, CAddressBookData> mapAddressBook;
|
||||
|
||||
CPubKey vchDefaultKey;
|
||||
|
||||
std::set<COutPoint> setLockedCoins;
|
||||
|
||||
int64_t nTimeFirstKey;
|
||||
|
||||
const CWalletTx* GetWalletTx(const uint256& hash) const;
|
||||
|
||||
//! check whether we are allowed to upgrade (or already support) to the named feature
|
||||
bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
|
||||
|
||||
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL) const;
|
||||
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
|
||||
|
||||
bool IsSpent(const uint256& hash, unsigned int n) const;
|
||||
|
||||
bool IsLockedCoin(uint256 hash, unsigned int n) const;
|
||||
void LockCoin(COutPoint& output);
|
||||
void UnlockCoin(COutPoint& output);
|
||||
void UnlockAllCoins();
|
||||
void ListLockedCoins(std::vector<COutPoint>& vOutpts);
|
||||
|
||||
/**
|
||||
* keystore implementation
|
||||
* Generate a new key
|
||||
*/
|
||||
CPubKey GenerateNewKey();
|
||||
//! Adds a key to the store, and saves it to disk.
|
||||
bool AddKeyPubKey(const CKey& key, const CPubKey &pubkey);
|
||||
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadKey(const CKey& key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); }
|
||||
//! Load metadata (used by LoadWallet)
|
||||
bool LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &metadata);
|
||||
|
||||
bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; }
|
||||
|
||||
//! Adds an encrypted key to the store, and saves it to disk.
|
||||
bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
|
||||
//! Adds an encrypted key to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret);
|
||||
bool AddCScript(const CScript& redeemScript);
|
||||
bool LoadCScript(const CScript& redeemScript);
|
||||
|
||||
//! Adds a destination data tuple to the store, and saves it to disk
|
||||
bool AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
|
||||
//! Erases a destination data tuple in the store and on disk
|
||||
bool EraseDestData(const CTxDestination &dest, const std::string &key);
|
||||
//! Adds a destination data tuple to the store, without saving it to disk
|
||||
bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value);
|
||||
//! Look up a destination data tuple in the store, return true if found false otherwise
|
||||
bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const;
|
||||
|
||||
//! Adds a watch-only address to the store, and saves it to disk.
|
||||
bool AddWatchOnly(const CScript &dest);
|
||||
bool RemoveWatchOnly(const CScript &dest);
|
||||
//! Adds a watch-only address to the store, without saving it to disk (used by LoadWallet)
|
||||
bool LoadWatchOnly(const CScript &dest);
|
||||
|
||||
bool Unlock(const SecureString& strWalletPassphrase);
|
||||
bool ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase);
|
||||
bool EncryptWallet(const SecureString& strWalletPassphrase);
|
||||
|
||||
void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const;
|
||||
|
||||
/**
|
||||
* Increment the next transaction order id
|
||||
* @return next transaction order id
|
||||
*/
|
||||
int64_t IncOrderPosNext(CWalletDB *pwalletdb = NULL);
|
||||
|
||||
typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair;
|
||||
typedef std::multimap<int64_t, TxPair > TxItems;
|
||||
|
||||
/**
|
||||
* Get the wallet's activity log
|
||||
* @return multimap of ordered transactions and accounting entries
|
||||
* @warning Returned pointers are *only* valid within the scope of passed acentries
|
||||
*/
|
||||
TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
|
||||
|
||||
void MarkDirty();
|
||||
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
|
||||
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
|
||||
bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate);
|
||||
void EraseFromWallet(const uint256 &hash);
|
||||
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
|
||||
void ReacceptWalletTransactions();
|
||||
void ResendWalletTransactions();
|
||||
CAmount GetBalance() const;
|
||||
CAmount GetUnconfirmedBalance() const;
|
||||
CAmount GetImmatureBalance() const;
|
||||
CAmount GetWatchOnlyBalance() const;
|
||||
CAmount GetUnconfirmedWatchOnlyBalance() const;
|
||||
CAmount GetImmatureWatchOnlyBalance() const;
|
||||
bool CreateTransaction(const std::vector<CRecipient>& vecSend,
|
||||
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl *coinControl = NULL);
|
||||
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
|
||||
|
||||
static CFeeRate minTxFee;
|
||||
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool);
|
||||
|
||||
bool NewKeyPool();
|
||||
bool TopUpKeyPool(unsigned int kpSize = 0);
|
||||
void ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool);
|
||||
void KeepKey(int64_t nIndex);
|
||||
void ReturnKey(int64_t nIndex);
|
||||
bool GetKeyFromPool(CPubKey &key);
|
||||
int64_t GetOldestKeyPoolTime();
|
||||
void GetAllReserveKeys(std::set<CKeyID>& setAddress) const;
|
||||
|
||||
std::set< std::set<CTxDestination> > GetAddressGroupings();
|
||||
std::map<CTxDestination, CAmount> GetAddressBalances();
|
||||
|
||||
std::set<CTxDestination> GetAccountAddresses(std::string strAccount) const;
|
||||
|
||||
isminetype IsMine(const CTxIn& txin) const;
|
||||
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
|
||||
isminetype IsMine(const CTxOut& txout) const
|
||||
{
|
||||
return ::IsMine(*this, txout.scriptPubKey);
|
||||
}
|
||||
CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const
|
||||
{
|
||||
if (!MoneyRange(txout.nValue))
|
||||
throw std::runtime_error("CWallet::GetCredit(): value out of range");
|
||||
return ((IsMine(txout) & filter) ? txout.nValue : 0);
|
||||
}
|
||||
bool IsChange(const CTxOut& txout) const;
|
||||
CAmount GetChange(const CTxOut& txout) const
|
||||
{
|
||||
if (!MoneyRange(txout.nValue))
|
||||
throw std::runtime_error("CWallet::GetChange(): value out of range");
|
||||
return (IsChange(txout) ? txout.nValue : 0);
|
||||
}
|
||||
bool IsMine(const CTransaction& tx) const
|
||||
{
|
||||
BOOST_FOREACH(const CTxOut& txout, tx.vout)
|
||||
if (IsMine(txout))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
/** should probably be renamed to IsRelevantToMe */
|
||||
bool IsFromMe(const CTransaction& tx) const
|
||||
{
|
||||
return (GetDebit(tx, ISMINE_ALL) > 0);
|
||||
}
|
||||
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const
|
||||
{
|
||||
CAmount nDebit = 0;
|
||||
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
||||
{
|
||||
nDebit += GetDebit(txin, filter);
|
||||
if (!MoneyRange(nDebit))
|
||||
throw std::runtime_error("CWallet::GetDebit(): value out of range");
|
||||
}
|
||||
return nDebit;
|
||||
}
|
||||
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const
|
||||
{
|
||||
CAmount nCredit = 0;
|
||||
BOOST_FOREACH(const CTxOut& txout, tx.vout)
|
||||
{
|
||||
nCredit += GetCredit(txout, filter);
|
||||
if (!MoneyRange(nCredit))
|
||||
throw std::runtime_error("CWallet::GetCredit(): value out of range");
|
||||
}
|
||||
return nCredit;
|
||||
}
|
||||
CAmount GetChange(const CTransaction& tx) const
|
||||
{
|
||||
CAmount nChange = 0;
|
||||
BOOST_FOREACH(const CTxOut& txout, tx.vout)
|
||||
{
|
||||
nChange += GetChange(txout);
|
||||
if (!MoneyRange(nChange))
|
||||
throw std::runtime_error("CWallet::GetChange(): value out of range");
|
||||
}
|
||||
return nChange;
|
||||
}
|
||||
void SetBestChain(const CBlockLocator& loc);
|
||||
|
||||
DBErrors LoadWallet(bool& fFirstRunRet);
|
||||
DBErrors ZapWalletTx(std::vector<CWalletTx>& vWtx);
|
||||
|
||||
bool SetAddressBook(const CTxDestination& address, const std::string& strName, const std::string& purpose);
|
||||
|
||||
bool DelAddressBook(const CTxDestination& address);
|
||||
|
||||
void UpdatedTransaction(const uint256 &hashTx);
|
||||
|
||||
void Inventory(const uint256 &hash)
|
||||
{
|
||||
{
|
||||
LOCK(cs_wallet);
|
||||
std::map<uint256, int>::iterator mi = mapRequestCount.find(hash);
|
||||
if (mi != mapRequestCount.end())
|
||||
(*mi).second++;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int GetKeyPoolSize()
|
||||
{
|
||||
AssertLockHeld(cs_wallet); // setKeyPool
|
||||
return setKeyPool.size();
|
||||
}
|
||||
|
||||
bool SetDefaultKey(const CPubKey &vchPubKey);
|
||||
|
||||
//! signify that a particular wallet feature is now used. this may change nWalletVersion and nWalletMaxVersion if those are lower
|
||||
bool SetMinVersion(enum WalletFeature, CWalletDB* pwalletdbIn = NULL, bool fExplicit = false);
|
||||
|
||||
//! change which version we're allowed to upgrade to (note that this does not immediately imply upgrading to that format)
|
||||
bool SetMaxVersion(int nVersion);
|
||||
|
||||
//! get the current wallet format (the oldest client version guaranteed to understand this wallet)
|
||||
int GetVersion() { LOCK(cs_wallet); return nWalletVersion; }
|
||||
|
||||
//! Get wallet transactions that conflict with given transaction (spend same outputs)
|
||||
std::set<uint256> GetConflicts(const uint256& txid) const;
|
||||
|
||||
/**
|
||||
* Address book entry changed.
|
||||
* @note called with lock cs_wallet held.
|
||||
*/
|
||||
boost::signals2::signal<void (CWallet *wallet, const CTxDestination
|
||||
&address, const std::string &label, bool isMine,
|
||||
const std::string &purpose,
|
||||
ChangeType status)> NotifyAddressBookChanged;
|
||||
|
||||
/**
|
||||
* Wallet transaction added, removed or updated.
|
||||
* @note called with lock cs_wallet held.
|
||||
*/
|
||||
boost::signals2::signal<void (CWallet *wallet, const uint256 &hashTx,
|
||||
ChangeType status)> NotifyTransactionChanged;
|
||||
|
||||
/** Show progress e.g. for rescan */
|
||||
boost::signals2::signal<void (const std::string &title, int nProgress)> ShowProgress;
|
||||
|
||||
/** Watch-only address added */
|
||||
boost::signals2::signal<void (bool fHaveWatchOnly)> NotifyWatchonlyChanged;
|
||||
};
|
||||
|
||||
/** A key allocated from the key pool. */
|
||||
class CReserveKey
|
||||
{
|
||||
protected:
|
||||
CWallet* pwallet;
|
||||
int64_t nIndex;
|
||||
CPubKey vchPubKey;
|
||||
public:
|
||||
CReserveKey(CWallet* pwalletIn)
|
||||
{
|
||||
nIndex = -1;
|
||||
pwallet = pwalletIn;
|
||||
}
|
||||
|
||||
~CReserveKey()
|
||||
{
|
||||
ReturnKey();
|
||||
}
|
||||
|
||||
void ReturnKey();
|
||||
bool GetReservedKey(CPubKey &pubkey);
|
||||
void KeepKey();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Account information.
|
||||
* Stored in wallet with key "acc"+string account name.
|
||||
*/
|
||||
class CAccount
|
||||
{
|
||||
public:
|
||||
CPubKey vchPubKey;
|
||||
|
||||
CAccount()
|
||||
{
|
||||
SetNull();
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
{
|
||||
vchPubKey = CPubKey();
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
if (!(nType & SER_GETHASH))
|
||||
READWRITE(nVersion);
|
||||
READWRITE(vchPubKey);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Internal transfers.
|
||||
* Database key is acentry<account><counter>.
|
||||
*/
|
||||
class CAccountingEntry
|
||||
{
|
||||
public:
|
||||
std::string strAccount;
|
||||
CAmount nCreditDebit;
|
||||
int64_t nTime;
|
||||
std::string strOtherAccount;
|
||||
std::string strComment;
|
||||
mapValue_t mapValue;
|
||||
int64_t nOrderPos; //! position in ordered transaction list
|
||||
uint64_t nEntryNo;
|
||||
|
||||
CAccountingEntry()
|
||||
{
|
||||
SetNull();
|
||||
}
|
||||
|
||||
void SetNull()
|
||||
{
|
||||
nCreditDebit = 0;
|
||||
nTime = 0;
|
||||
strAccount.clear();
|
||||
strOtherAccount.clear();
|
||||
strComment.clear();
|
||||
nOrderPos = -1;
|
||||
nEntryNo = 0;
|
||||
}
|
||||
|
||||
ADD_SERIALIZE_METHODS;
|
||||
|
||||
template <typename Stream, typename Operation>
|
||||
inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
|
||||
if (!(nType & SER_GETHASH))
|
||||
READWRITE(nVersion);
|
||||
//! Note: strAccount is serialized as part of the key, not here.
|
||||
READWRITE(nCreditDebit);
|
||||
READWRITE(nTime);
|
||||
READWRITE(LIMITED_STRING(strOtherAccount, 65536));
|
||||
|
||||
if (!ser_action.ForRead())
|
||||
{
|
||||
WriteOrderPos(nOrderPos, mapValue);
|
||||
|
||||
if (!(mapValue.empty() && _ssExtra.empty()))
|
||||
{
|
||||
CDataStream ss(nType, nVersion);
|
||||
ss.insert(ss.begin(), '\0');
|
||||
ss << mapValue;
|
||||
ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end());
|
||||
strComment.append(ss.str());
|
||||
}
|
||||
}
|
||||
|
||||
READWRITE(LIMITED_STRING(strComment, 65536));
|
||||
|
||||
size_t nSepPos = strComment.find("\0", 0, 1);
|
||||
if (ser_action.ForRead())
|
||||
{
|
||||
mapValue.clear();
|
||||
if (std::string::npos != nSepPos)
|
||||
{
|
||||
CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), nType, nVersion);
|
||||
ss >> mapValue;
|
||||
_ssExtra = std::vector<char>(ss.begin(), ss.end());
|
||||
}
|
||||
ReadOrderPos(nOrderPos, mapValue);
|
||||
}
|
||||
if (std::string::npos != nSepPos)
|
||||
strComment.erase(nSepPos);
|
||||
|
||||
mapValue.erase("n");
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<char> _ssExtra;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_H
|
||||
91
src/wallet/wallet_ismine.cpp
Normal file
91
src/wallet/wallet_ismine.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "wallet_ismine.h"
|
||||
|
||||
#include "key.h"
|
||||
#include "keystore.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
typedef vector<unsigned char> valtype;
|
||||
|
||||
unsigned int HaveKeys(const vector<valtype>& pubkeys, const CKeyStore& keystore)
|
||||
{
|
||||
unsigned int nResult = 0;
|
||||
BOOST_FOREACH(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)
|
||||
{
|
||||
vector<valtype> vSolutions;
|
||||
txnouttype whichType;
|
||||
if (!Solver(scriptPubKey, whichType, vSolutions)) {
|
||||
if (keystore.HaveWatchOnly(scriptPubKey))
|
||||
return ISMINE_WATCH_ONLY;
|
||||
return ISMINE_NO;
|
||||
}
|
||||
|
||||
CKeyID keyID;
|
||||
switch (whichType)
|
||||
{
|
||||
case TX_NONSTANDARD:
|
||||
case TX_NULL_DATA:
|
||||
break;
|
||||
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_SPENDABLE)
|
||||
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;
|
||||
return ISMINE_NO;
|
||||
}
|
||||
29
src/wallet/wallet_ismine.h
Normal file
29
src/wallet/wallet_ismine.h
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_WALLET_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,
|
||||
ISMINE_WATCH_ONLY = 1,
|
||||
ISMINE_SPENDABLE = 2,
|
||||
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
|
||||
986
src/wallet/walletdb.cpp
Normal file
986
src/wallet/walletdb.cpp
Normal file
@@ -0,0 +1,986 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2014 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include "wallet/walletdb.h"
|
||||
|
||||
#include "base58.h"
|
||||
#include "protocol.h"
|
||||
#include "serialize.h"
|
||||
#include "sync.h"
|
||||
#include "util.h"
|
||||
#include "utiltime.h"
|
||||
#include "wallet/wallet.h"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
using namespace std;
|
||||
|
||||
static uint64_t nAccountingEntryNumber = 0;
|
||||
|
||||
//
|
||||
// CWalletDB
|
||||
//
|
||||
|
||||
bool CWalletDB::WriteName(const string& strAddress, const string& strName)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(make_pair(string("name"), strAddress), strName);
|
||||
}
|
||||
|
||||
bool CWalletDB::EraseName(const string& strAddress)
|
||||
{
|
||||
// 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.
|
||||
nWalletDBUpdated++;
|
||||
return Erase(make_pair(string("name"), strAddress));
|
||||
}
|
||||
|
||||
bool CWalletDB::WritePurpose(const string& strAddress, const string& strPurpose)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(make_pair(string("purpose"), strAddress), strPurpose);
|
||||
}
|
||||
|
||||
bool CWalletDB::ErasePurpose(const string& strPurpose)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(make_pair(string("purpose"), strPurpose));
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteTx(uint256 hash, const CWalletTx& wtx)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("tx"), hash), wtx);
|
||||
}
|
||||
|
||||
bool CWalletDB::EraseTx(uint256 hash)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(std::make_pair(std::string("tx"), hash));
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
|
||||
if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
|
||||
keyMeta, false))
|
||||
return false;
|
||||
|
||||
// hash pubkey/privkey to accelerate wallet load
|
||||
std::vector<unsigned char> vchKey;
|
||||
vchKey.reserve(vchPubKey.size() + vchPrivKey.size());
|
||||
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
|
||||
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());
|
||||
|
||||
return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
|
||||
const std::vector<unsigned char>& vchCryptedSecret,
|
||||
const CKeyMetadata &keyMeta)
|
||||
{
|
||||
const bool fEraseUnencryptedKey = true;
|
||||
nWalletDBUpdated++;
|
||||
|
||||
if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
|
||||
keyMeta))
|
||||
return false;
|
||||
|
||||
if (!Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false))
|
||||
return false;
|
||||
if (fEraseUnencryptedKey)
|
||||
{
|
||||
Erase(std::make_pair(std::string("key"), vchPubKey));
|
||||
Erase(std::make_pair(std::string("wkey"), vchPubKey));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("cscript"), hash), redeemScript, false);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteWatchOnly(const CScript &dest)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("watchs"), dest), '1');
|
||||
}
|
||||
|
||||
bool CWalletDB::EraseWatchOnly(const CScript &dest)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(std::make_pair(std::string("watchs"), dest));
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::string("bestblock"), locator);
|
||||
}
|
||||
|
||||
bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
|
||||
{
|
||||
return Read(std::string("bestblock"), locator);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::string("orderposnext"), nOrderPosNext);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::string("defaultkey"), vchPubKey);
|
||||
}
|
||||
|
||||
bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
|
||||
{
|
||||
return Read(std::make_pair(std::string("pool"), nPool), keypool);
|
||||
}
|
||||
|
||||
bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("pool"), nPool), keypool);
|
||||
}
|
||||
|
||||
bool CWalletDB::ErasePool(int64_t nPool)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(std::make_pair(std::string("pool"), nPool));
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteMinVersion(int nVersion)
|
||||
{
|
||||
return Write(std::string("minversion"), nVersion);
|
||||
}
|
||||
|
||||
bool CWalletDB::ReadAccount(const string& strAccount, CAccount& account)
|
||||
{
|
||||
account.SetNull();
|
||||
return Read(make_pair(string("acc"), strAccount), account);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteAccount(const string& strAccount, const CAccount& account)
|
||||
{
|
||||
return Write(make_pair(string("acc"), strAccount), account);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry)
|
||||
{
|
||||
return Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
|
||||
{
|
||||
return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
|
||||
}
|
||||
|
||||
CAmount CWalletDB::GetAccountCreditDebit(const string& strAccount)
|
||||
{
|
||||
list<CAccountingEntry> entries;
|
||||
ListAccountCreditDebit(strAccount, entries);
|
||||
|
||||
CAmount nCreditDebit = 0;
|
||||
BOOST_FOREACH (const CAccountingEntry& entry, entries)
|
||||
nCreditDebit += entry.nCreditDebit;
|
||||
|
||||
return nCreditDebit;
|
||||
}
|
||||
|
||||
void CWalletDB::ListAccountCreditDebit(const string& strAccount, list<CAccountingEntry>& entries)
|
||||
{
|
||||
bool fAllAccounts = (strAccount == "*");
|
||||
|
||||
Dbc* pcursor = GetCursor();
|
||||
if (!pcursor)
|
||||
throw runtime_error("CWalletDB::ListAccountCreditDebit(): cannot create DB cursor");
|
||||
unsigned int fFlags = DB_SET_RANGE;
|
||||
while (true)
|
||||
{
|
||||
// Read next record
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
if (fFlags == DB_SET_RANGE)
|
||||
ssKey << std::make_pair(std::string("acentry"), std::make_pair((fAllAccounts ? string("") : strAccount), uint64_t(0)));
|
||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||
int ret = ReadAtCursor(pcursor, ssKey, ssValue, fFlags);
|
||||
fFlags = DB_NEXT;
|
||||
if (ret == DB_NOTFOUND)
|
||||
break;
|
||||
else if (ret != 0)
|
||||
{
|
||||
pcursor->close();
|
||||
throw runtime_error("CWalletDB::ListAccountCreditDebit(): error scanning DB");
|
||||
}
|
||||
|
||||
// Unserialize
|
||||
string strType;
|
||||
ssKey >> strType;
|
||||
if (strType != "acentry")
|
||||
break;
|
||||
CAccountingEntry acentry;
|
||||
ssKey >> acentry.strAccount;
|
||||
if (!fAllAccounts && acentry.strAccount != strAccount)
|
||||
break;
|
||||
|
||||
ssValue >> acentry;
|
||||
ssKey >> acentry.nEntryNo;
|
||||
entries.push_back(acentry);
|
||||
}
|
||||
|
||||
pcursor->close();
|
||||
}
|
||||
|
||||
DBErrors CWalletDB::ReorderTransactions(CWallet* pwallet)
|
||||
{
|
||||
LOCK(pwallet->cs_wallet);
|
||||
// Old wallets didn't have any defined order for transactions
|
||||
// Probably a bad idea to change the output of this
|
||||
|
||||
// First: get all CWalletTx and CAccountingEntry into a sorted-by-time multimap.
|
||||
typedef pair<CWalletTx*, CAccountingEntry*> TxPair;
|
||||
typedef multimap<int64_t, TxPair > TxItems;
|
||||
TxItems txByTime;
|
||||
|
||||
for (map<uint256, CWalletTx>::iterator it = pwallet->mapWallet.begin(); it != pwallet->mapWallet.end(); ++it)
|
||||
{
|
||||
CWalletTx* wtx = &((*it).second);
|
||||
txByTime.insert(make_pair(wtx->nTimeReceived, TxPair(wtx, (CAccountingEntry*)0)));
|
||||
}
|
||||
list<CAccountingEntry> acentries;
|
||||
ListAccountCreditDebit("", acentries);
|
||||
BOOST_FOREACH(CAccountingEntry& entry, acentries)
|
||||
{
|
||||
txByTime.insert(make_pair(entry.nTime, TxPair((CWalletTx*)0, &entry)));
|
||||
}
|
||||
|
||||
int64_t& nOrderPosNext = pwallet->nOrderPosNext;
|
||||
nOrderPosNext = 0;
|
||||
std::vector<int64_t> nOrderPosOffsets;
|
||||
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it)
|
||||
{
|
||||
CWalletTx *const pwtx = (*it).second.first;
|
||||
CAccountingEntry *const pacentry = (*it).second.second;
|
||||
int64_t& nOrderPos = (pwtx != 0) ? pwtx->nOrderPos : pacentry->nOrderPos;
|
||||
|
||||
if (nOrderPos == -1)
|
||||
{
|
||||
nOrderPos = nOrderPosNext++;
|
||||
nOrderPosOffsets.push_back(nOrderPos);
|
||||
|
||||
if (pwtx)
|
||||
{
|
||||
if (!WriteTx(pwtx->GetHash(), *pwtx))
|
||||
return DB_LOAD_FAIL;
|
||||
}
|
||||
else
|
||||
if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
|
||||
return DB_LOAD_FAIL;
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t nOrderPosOff = 0;
|
||||
BOOST_FOREACH(const int64_t& nOffsetStart, nOrderPosOffsets)
|
||||
{
|
||||
if (nOrderPos >= nOffsetStart)
|
||||
++nOrderPosOff;
|
||||
}
|
||||
nOrderPos += nOrderPosOff;
|
||||
nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
|
||||
|
||||
if (!nOrderPosOff)
|
||||
continue;
|
||||
|
||||
// Since we're changing the order, write it back
|
||||
if (pwtx)
|
||||
{
|
||||
if (!WriteTx(pwtx->GetHash(), *pwtx))
|
||||
return DB_LOAD_FAIL;
|
||||
}
|
||||
else
|
||||
if (!WriteAccountingEntry(pacentry->nEntryNo, *pacentry))
|
||||
return DB_LOAD_FAIL;
|
||||
}
|
||||
}
|
||||
WriteOrderPosNext(nOrderPosNext);
|
||||
|
||||
return DB_LOAD_OK;
|
||||
}
|
||||
|
||||
class CWalletScanState {
|
||||
public:
|
||||
unsigned int nKeys;
|
||||
unsigned int nCKeys;
|
||||
unsigned int nKeyMeta;
|
||||
bool fIsEncrypted;
|
||||
bool fAnyUnordered;
|
||||
int nFileVersion;
|
||||
vector<uint256> vWalletUpgrade;
|
||||
|
||||
CWalletScanState() {
|
||||
nKeys = nCKeys = nKeyMeta = 0;
|
||||
fIsEncrypted = false;
|
||||
fAnyUnordered = false;
|
||||
nFileVersion = 0;
|
||||
}
|
||||
};
|
||||
|
||||
bool
|
||||
ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
|
||||
CWalletScanState &wss, string& strType, string& strErr)
|
||||
{
|
||||
try {
|
||||
// Unserialize
|
||||
// Taking advantage of the fact that pair serialization
|
||||
// is just the two items serialized one after the other
|
||||
ssKey >> strType;
|
||||
if (strType == "name")
|
||||
{
|
||||
string strAddress;
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].name;
|
||||
}
|
||||
else if (strType == "purpose")
|
||||
{
|
||||
string strAddress;
|
||||
ssKey >> strAddress;
|
||||
ssValue >> pwallet->mapAddressBook[CBitcoinAddress(strAddress).Get()].purpose;
|
||||
}
|
||||
else if (strType == "tx")
|
||||
{
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
CWalletTx wtx;
|
||||
ssValue >> wtx;
|
||||
CValidationState state;
|
||||
if (!(CheckTransaction(wtx, state) && (wtx.GetHash() == hash) && state.IsValid()))
|
||||
return false;
|
||||
|
||||
// Undo serialize changes in 31600
|
||||
if (31404 <= wtx.fTimeReceivedIsTxTime && wtx.fTimeReceivedIsTxTime <= 31703)
|
||||
{
|
||||
if (!ssValue.empty())
|
||||
{
|
||||
char fTmp;
|
||||
char fUnused;
|
||||
ssValue >> fTmp >> fUnused >> wtx.strFromAccount;
|
||||
strErr = strprintf("LoadWallet() upgrading tx ver=%d %d '%s' %s",
|
||||
wtx.fTimeReceivedIsTxTime, fTmp, wtx.strFromAccount, hash.ToString());
|
||||
wtx.fTimeReceivedIsTxTime = fTmp;
|
||||
}
|
||||
else
|
||||
{
|
||||
strErr = strprintf("LoadWallet() repairing tx ver=%d %s", wtx.fTimeReceivedIsTxTime, hash.ToString());
|
||||
wtx.fTimeReceivedIsTxTime = 0;
|
||||
}
|
||||
wss.vWalletUpgrade.push_back(hash);
|
||||
}
|
||||
|
||||
if (wtx.nOrderPos == -1)
|
||||
wss.fAnyUnordered = true;
|
||||
|
||||
pwallet->AddToWallet(wtx, true, NULL);
|
||||
}
|
||||
else if (strType == "acentry")
|
||||
{
|
||||
string strAccount;
|
||||
ssKey >> strAccount;
|
||||
uint64_t nNumber;
|
||||
ssKey >> nNumber;
|
||||
if (nNumber > nAccountingEntryNumber)
|
||||
nAccountingEntryNumber = nNumber;
|
||||
|
||||
if (!wss.fAnyUnordered)
|
||||
{
|
||||
CAccountingEntry acentry;
|
||||
ssValue >> acentry;
|
||||
if (acentry.nOrderPos == -1)
|
||||
wss.fAnyUnordered = true;
|
||||
}
|
||||
}
|
||||
else if (strType == "watchs")
|
||||
{
|
||||
CScript script;
|
||||
ssKey >> script;
|
||||
char fYes;
|
||||
ssValue >> fYes;
|
||||
if (fYes == '1')
|
||||
pwallet->LoadWatchOnly(script);
|
||||
|
||||
// Watch-only addresses have no birthday information for now,
|
||||
// so set the wallet birthday to the beginning of time.
|
||||
pwallet->nTimeFirstKey = 1;
|
||||
}
|
||||
else if (strType == "key" || strType == "wkey")
|
||||
{
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
if (!vchPubKey.IsValid())
|
||||
{
|
||||
strErr = "Error reading wallet database: CPubKey corrupt";
|
||||
return false;
|
||||
}
|
||||
CKey key;
|
||||
CPrivKey pkey;
|
||||
uint256 hash;
|
||||
|
||||
if (strType == "key")
|
||||
{
|
||||
wss.nKeys++;
|
||||
ssValue >> pkey;
|
||||
} else {
|
||||
CWalletKey wkey;
|
||||
ssValue >> wkey;
|
||||
pkey = wkey.vchPrivKey;
|
||||
}
|
||||
|
||||
// Old wallets store keys as "key" [pubkey] => [privkey]
|
||||
// ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key
|
||||
// using EC operations as a checksum.
|
||||
// Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while
|
||||
// remaining backwards-compatible.
|
||||
try
|
||||
{
|
||||
ssValue >> hash;
|
||||
}
|
||||
catch (...) {}
|
||||
|
||||
bool fSkipCheck = false;
|
||||
|
||||
if (!hash.IsNull())
|
||||
{
|
||||
// hash pubkey/privkey to accelerate wallet load
|
||||
std::vector<unsigned char> vchKey;
|
||||
vchKey.reserve(vchPubKey.size() + pkey.size());
|
||||
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
|
||||
vchKey.insert(vchKey.end(), pkey.begin(), pkey.end());
|
||||
|
||||
if (Hash(vchKey.begin(), vchKey.end()) != hash)
|
||||
{
|
||||
strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt";
|
||||
return false;
|
||||
}
|
||||
|
||||
fSkipCheck = true;
|
||||
}
|
||||
|
||||
if (!key.Load(pkey, vchPubKey, fSkipCheck))
|
||||
{
|
||||
strErr = "Error reading wallet database: CPrivKey corrupt";
|
||||
return false;
|
||||
}
|
||||
if (!pwallet->LoadKey(key, vchPubKey))
|
||||
{
|
||||
strErr = "Error reading wallet database: LoadKey failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "mkey")
|
||||
{
|
||||
unsigned int nID;
|
||||
ssKey >> nID;
|
||||
CMasterKey kMasterKey;
|
||||
ssValue >> kMasterKey;
|
||||
if(pwallet->mapMasterKeys.count(nID) != 0)
|
||||
{
|
||||
strErr = strprintf("Error reading wallet database: duplicate CMasterKey id %u", nID);
|
||||
return false;
|
||||
}
|
||||
pwallet->mapMasterKeys[nID] = kMasterKey;
|
||||
if (pwallet->nMasterKeyMaxID < nID)
|
||||
pwallet->nMasterKeyMaxID = nID;
|
||||
}
|
||||
else if (strType == "ckey")
|
||||
{
|
||||
vector<unsigned char> vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
vector<unsigned char> vchPrivKey;
|
||||
ssValue >> vchPrivKey;
|
||||
wss.nCKeys++;
|
||||
|
||||
if (!pwallet->LoadCryptedKey(vchPubKey, vchPrivKey))
|
||||
{
|
||||
strErr = "Error reading wallet database: LoadCryptedKey failed";
|
||||
return false;
|
||||
}
|
||||
wss.fIsEncrypted = true;
|
||||
}
|
||||
else if (strType == "keymeta")
|
||||
{
|
||||
CPubKey vchPubKey;
|
||||
ssKey >> vchPubKey;
|
||||
CKeyMetadata keyMeta;
|
||||
ssValue >> keyMeta;
|
||||
wss.nKeyMeta++;
|
||||
|
||||
pwallet->LoadKeyMetadata(vchPubKey, keyMeta);
|
||||
|
||||
// find earliest key creation time, as wallet birthday
|
||||
if (!pwallet->nTimeFirstKey ||
|
||||
(keyMeta.nCreateTime < pwallet->nTimeFirstKey))
|
||||
pwallet->nTimeFirstKey = keyMeta.nCreateTime;
|
||||
}
|
||||
else if (strType == "defaultkey")
|
||||
{
|
||||
ssValue >> pwallet->vchDefaultKey;
|
||||
}
|
||||
else if (strType == "pool")
|
||||
{
|
||||
int64_t nIndex;
|
||||
ssKey >> nIndex;
|
||||
CKeyPool keypool;
|
||||
ssValue >> keypool;
|
||||
pwallet->setKeyPool.insert(nIndex);
|
||||
|
||||
// If no metadata exists yet, create a default with the pool key's
|
||||
// creation time. Note that this may be overwritten by actually
|
||||
// stored metadata for that key later, which is fine.
|
||||
CKeyID keyid = keypool.vchPubKey.GetID();
|
||||
if (pwallet->mapKeyMetadata.count(keyid) == 0)
|
||||
pwallet->mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
|
||||
}
|
||||
else if (strType == "version")
|
||||
{
|
||||
ssValue >> wss.nFileVersion;
|
||||
if (wss.nFileVersion == 10300)
|
||||
wss.nFileVersion = 300;
|
||||
}
|
||||
else if (strType == "cscript")
|
||||
{
|
||||
uint160 hash;
|
||||
ssKey >> hash;
|
||||
CScript script;
|
||||
ssValue >> script;
|
||||
if (!pwallet->LoadCScript(script))
|
||||
{
|
||||
strErr = "Error reading wallet database: LoadCScript failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (strType == "orderposnext")
|
||||
{
|
||||
ssValue >> pwallet->nOrderPosNext;
|
||||
}
|
||||
else if (strType == "destdata")
|
||||
{
|
||||
std::string strAddress, strKey, strValue;
|
||||
ssKey >> strAddress;
|
||||
ssKey >> strKey;
|
||||
ssValue >> strValue;
|
||||
if (!pwallet->LoadDestData(CBitcoinAddress(strAddress).Get(), strKey, strValue))
|
||||
{
|
||||
strErr = "Error reading wallet database: LoadDestData failed";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool IsKeyType(string strType)
|
||||
{
|
||||
return (strType== "key" || strType == "wkey" ||
|
||||
strType == "mkey" || strType == "ckey");
|
||||
}
|
||||
|
||||
DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
|
||||
{
|
||||
pwallet->vchDefaultKey = CPubKey();
|
||||
CWalletScanState wss;
|
||||
bool fNoncriticalErrors = false;
|
||||
DBErrors result = DB_LOAD_OK;
|
||||
|
||||
try {
|
||||
LOCK(pwallet->cs_wallet);
|
||||
int nMinVersion = 0;
|
||||
if (Read((string)"minversion", nMinVersion))
|
||||
{
|
||||
if (nMinVersion > CLIENT_VERSION)
|
||||
return DB_TOO_NEW;
|
||||
pwallet->LoadMinVersion(nMinVersion);
|
||||
}
|
||||
|
||||
// Get cursor
|
||||
Dbc* pcursor = GetCursor();
|
||||
if (!pcursor)
|
||||
{
|
||||
LogPrintf("Error getting wallet database cursor\n");
|
||||
return DB_CORRUPT;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Read next record
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||
int ret = ReadAtCursor(pcursor, ssKey, ssValue);
|
||||
if (ret == DB_NOTFOUND)
|
||||
break;
|
||||
else if (ret != 0)
|
||||
{
|
||||
LogPrintf("Error reading next record from wallet database\n");
|
||||
return DB_CORRUPT;
|
||||
}
|
||||
|
||||
// Try to be tolerant of single corrupt records:
|
||||
string strType, strErr;
|
||||
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr))
|
||||
{
|
||||
// losing keys is considered a catastrophic error, anything else
|
||||
// we assume the user can live with:
|
||||
if (IsKeyType(strType))
|
||||
result = DB_CORRUPT;
|
||||
else
|
||||
{
|
||||
// Leave other errors alone, if we try to fix them we might make things worse.
|
||||
fNoncriticalErrors = true; // ... but do warn the user there is something wrong.
|
||||
if (strType == "tx")
|
||||
// Rescan if there is a bad transaction record:
|
||||
SoftSetBoolArg("-rescan", true);
|
||||
}
|
||||
}
|
||||
if (!strErr.empty())
|
||||
LogPrintf("%s\n", strErr);
|
||||
}
|
||||
pcursor->close();
|
||||
}
|
||||
catch (const boost::thread_interrupted&) {
|
||||
throw;
|
||||
}
|
||||
catch (...) {
|
||||
result = DB_CORRUPT;
|
||||
}
|
||||
|
||||
if (fNoncriticalErrors && result == DB_LOAD_OK)
|
||||
result = DB_NONCRITICAL_ERROR;
|
||||
|
||||
// Any wallet corruption at all: skip any rewriting or
|
||||
// upgrading, we don't want to make it worse.
|
||||
if (result != DB_LOAD_OK)
|
||||
return result;
|
||||
|
||||
LogPrintf("nFileVersion = %d\n", wss.nFileVersion);
|
||||
|
||||
LogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total\n",
|
||||
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys);
|
||||
|
||||
// nTimeFirstKey is only reliable if all keys have metadata
|
||||
if ((wss.nKeys + wss.nCKeys) != wss.nKeyMeta)
|
||||
pwallet->nTimeFirstKey = 1; // 0 would be considered 'no value'
|
||||
|
||||
BOOST_FOREACH(uint256 hash, wss.vWalletUpgrade)
|
||||
WriteTx(hash, pwallet->mapWallet[hash]);
|
||||
|
||||
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
|
||||
if (wss.fIsEncrypted && (wss.nFileVersion == 40000 || wss.nFileVersion == 50000))
|
||||
return DB_NEED_REWRITE;
|
||||
|
||||
if (wss.nFileVersion < CLIENT_VERSION) // Update
|
||||
WriteVersion(CLIENT_VERSION);
|
||||
|
||||
if (wss.fAnyUnordered)
|
||||
result = ReorderTransactions(pwallet);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DBErrors CWalletDB::FindWalletTx(CWallet* pwallet, vector<uint256>& vTxHash, vector<CWalletTx>& vWtx)
|
||||
{
|
||||
pwallet->vchDefaultKey = CPubKey();
|
||||
bool fNoncriticalErrors = false;
|
||||
DBErrors result = DB_LOAD_OK;
|
||||
|
||||
try {
|
||||
LOCK(pwallet->cs_wallet);
|
||||
int nMinVersion = 0;
|
||||
if (Read((string)"minversion", nMinVersion))
|
||||
{
|
||||
if (nMinVersion > CLIENT_VERSION)
|
||||
return DB_TOO_NEW;
|
||||
pwallet->LoadMinVersion(nMinVersion);
|
||||
}
|
||||
|
||||
// Get cursor
|
||||
Dbc* pcursor = GetCursor();
|
||||
if (!pcursor)
|
||||
{
|
||||
LogPrintf("Error getting wallet database cursor\n");
|
||||
return DB_CORRUPT;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Read next record
|
||||
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
||||
int ret = ReadAtCursor(pcursor, ssKey, ssValue);
|
||||
if (ret == DB_NOTFOUND)
|
||||
break;
|
||||
else if (ret != 0)
|
||||
{
|
||||
LogPrintf("Error reading next record from wallet database\n");
|
||||
return DB_CORRUPT;
|
||||
}
|
||||
|
||||
string strType;
|
||||
ssKey >> strType;
|
||||
if (strType == "tx") {
|
||||
uint256 hash;
|
||||
ssKey >> hash;
|
||||
|
||||
CWalletTx wtx;
|
||||
ssValue >> wtx;
|
||||
|
||||
vTxHash.push_back(hash);
|
||||
vWtx.push_back(wtx);
|
||||
}
|
||||
}
|
||||
pcursor->close();
|
||||
}
|
||||
catch (const boost::thread_interrupted&) {
|
||||
throw;
|
||||
}
|
||||
catch (...) {
|
||||
result = DB_CORRUPT;
|
||||
}
|
||||
|
||||
if (fNoncriticalErrors && result == DB_LOAD_OK)
|
||||
result = DB_NONCRITICAL_ERROR;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DBErrors CWalletDB::ZapWalletTx(CWallet* pwallet, vector<CWalletTx>& vWtx)
|
||||
{
|
||||
// build list of wallet TXs
|
||||
vector<uint256> vTxHash;
|
||||
DBErrors err = FindWalletTx(pwallet, vTxHash, vWtx);
|
||||
if (err != DB_LOAD_OK)
|
||||
return err;
|
||||
|
||||
// erase each wallet TX
|
||||
BOOST_FOREACH (uint256& hash, vTxHash) {
|
||||
if (!EraseTx(hash))
|
||||
return DB_CORRUPT;
|
||||
}
|
||||
|
||||
return DB_LOAD_OK;
|
||||
}
|
||||
|
||||
void ThreadFlushWalletDB(const string& strFile)
|
||||
{
|
||||
// Make this thread recognisable as the wallet flushing thread
|
||||
RenameThread("bitcoin-wallet");
|
||||
|
||||
static bool fOneThread;
|
||||
if (fOneThread)
|
||||
return;
|
||||
fOneThread = true;
|
||||
if (!GetBoolArg("-flushwallet", true))
|
||||
return;
|
||||
|
||||
unsigned int nLastSeen = nWalletDBUpdated;
|
||||
unsigned int nLastFlushed = nWalletDBUpdated;
|
||||
int64_t nLastWalletUpdate = GetTime();
|
||||
while (true)
|
||||
{
|
||||
MilliSleep(500);
|
||||
|
||||
if (nLastSeen != nWalletDBUpdated)
|
||||
{
|
||||
nLastSeen = nWalletDBUpdated;
|
||||
nLastWalletUpdate = GetTime();
|
||||
}
|
||||
|
||||
if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2)
|
||||
{
|
||||
TRY_LOCK(bitdb.cs_db,lockDb);
|
||||
if (lockDb)
|
||||
{
|
||||
// Don't do this if any databases are in use
|
||||
int nRefCount = 0;
|
||||
map<string, int>::iterator mi = bitdb.mapFileUseCount.begin();
|
||||
while (mi != bitdb.mapFileUseCount.end())
|
||||
{
|
||||
nRefCount += (*mi).second;
|
||||
mi++;
|
||||
}
|
||||
|
||||
if (nRefCount == 0)
|
||||
{
|
||||
boost::this_thread::interruption_point();
|
||||
map<string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
|
||||
if (mi != bitdb.mapFileUseCount.end())
|
||||
{
|
||||
LogPrint("db", "Flushing wallet.dat\n");
|
||||
nLastFlushed = nWalletDBUpdated;
|
||||
int64_t nStart = GetTimeMillis();
|
||||
|
||||
// Flush wallet.dat so it's self contained
|
||||
bitdb.CloseDb(strFile);
|
||||
bitdb.CheckpointLSN(strFile);
|
||||
|
||||
bitdb.mapFileUseCount.erase(mi++);
|
||||
LogPrint("db", "Flushed wallet.dat %dms\n", GetTimeMillis() - nStart);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BackupWallet(const CWallet& wallet, const string& strDest)
|
||||
{
|
||||
if (!wallet.fFileBacked)
|
||||
return false;
|
||||
while (true)
|
||||
{
|
||||
{
|
||||
LOCK(bitdb.cs_db);
|
||||
if (!bitdb.mapFileUseCount.count(wallet.strWalletFile) || bitdb.mapFileUseCount[wallet.strWalletFile] == 0)
|
||||
{
|
||||
// Flush log data to the dat file
|
||||
bitdb.CloseDb(wallet.strWalletFile);
|
||||
bitdb.CheckpointLSN(wallet.strWalletFile);
|
||||
bitdb.mapFileUseCount.erase(wallet.strWalletFile);
|
||||
|
||||
// Copy wallet.dat
|
||||
boost::filesystem::path pathSrc = GetDataDir() / wallet.strWalletFile;
|
||||
boost::filesystem::path pathDest(strDest);
|
||||
if (boost::filesystem::is_directory(pathDest))
|
||||
pathDest /= wallet.strWalletFile;
|
||||
|
||||
try {
|
||||
#if BOOST_VERSION >= 104000
|
||||
boost::filesystem::copy_file(pathSrc, pathDest, boost::filesystem::copy_option::overwrite_if_exists);
|
||||
#else
|
||||
boost::filesystem::copy_file(pathSrc, pathDest);
|
||||
#endif
|
||||
LogPrintf("copied wallet.dat to %s\n", pathDest.string());
|
||||
return true;
|
||||
} catch (const boost::filesystem::filesystem_error& e) {
|
||||
LogPrintf("error copying wallet.dat to %s - %s\n", pathDest.string(), e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
MilliSleep(100);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// Try to (very carefully!) recover wallet.dat if there is a problem.
|
||||
//
|
||||
bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename, bool fOnlyKeys)
|
||||
{
|
||||
// Recovery procedure:
|
||||
// move wallet.dat to wallet.timestamp.bak
|
||||
// Call Salvage with fAggressive=true to
|
||||
// get as much data as possible.
|
||||
// Rewrite salvaged data to wallet.dat
|
||||
// Set -rescan so any missing transactions will be
|
||||
// found.
|
||||
int64_t now = GetTime();
|
||||
std::string newFilename = strprintf("wallet.%d.bak", now);
|
||||
|
||||
int result = dbenv.dbenv->dbrename(NULL, filename.c_str(), NULL,
|
||||
newFilename.c_str(), DB_AUTO_COMMIT);
|
||||
if (result == 0)
|
||||
LogPrintf("Renamed %s to %s\n", filename, newFilename);
|
||||
else
|
||||
{
|
||||
LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<CDBEnv::KeyValPair> salvagedData;
|
||||
bool allOK = dbenv.Salvage(newFilename, true, salvagedData);
|
||||
if (salvagedData.empty())
|
||||
{
|
||||
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
|
||||
return false;
|
||||
}
|
||||
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
|
||||
|
||||
bool fSuccess = allOK;
|
||||
boost::scoped_ptr<Db> pdbCopy(new Db(dbenv.dbenv, 0));
|
||||
int ret = pdbCopy->open(NULL, // Txn pointer
|
||||
filename.c_str(), // Filename
|
||||
"main", // Logical db name
|
||||
DB_BTREE, // Database type
|
||||
DB_CREATE, // Flags
|
||||
0);
|
||||
if (ret > 0)
|
||||
{
|
||||
LogPrintf("Cannot create database file %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
CWallet dummyWallet;
|
||||
CWalletScanState wss;
|
||||
|
||||
DbTxn* ptxn = dbenv.TxnBegin();
|
||||
BOOST_FOREACH(CDBEnv::KeyValPair& row, salvagedData)
|
||||
{
|
||||
if (fOnlyKeys)
|
||||
{
|
||||
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
|
||||
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
|
||||
string strType, strErr;
|
||||
bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
|
||||
wss, strType, strErr);
|
||||
if (!IsKeyType(strType))
|
||||
continue;
|
||||
if (!fReadOK)
|
||||
{
|
||||
LogPrintf("WARNING: CWalletDB::Recover skipping %s: %s\n", strType, strErr);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Dbt datKey(&row.first[0], row.first.size());
|
||||
Dbt datValue(&row.second[0], row.second.size());
|
||||
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
|
||||
if (ret2 > 0)
|
||||
fSuccess = false;
|
||||
}
|
||||
ptxn->commit(0);
|
||||
pdbCopy->close(0);
|
||||
|
||||
return fSuccess;
|
||||
}
|
||||
|
||||
bool CWalletDB::Recover(CDBEnv& dbenv, std::string filename)
|
||||
{
|
||||
return CWalletDB::Recover(dbenv, filename, false);
|
||||
}
|
||||
|
||||
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
|
||||
}
|
||||
|
||||
bool CWalletDB::EraseDestData(const std::string &address, const std::string &key)
|
||||
{
|
||||
nWalletDBUpdated++;
|
||||
return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
|
||||
}
|
||||
142
src/wallet/walletdb.h
Normal file
142
src/wallet/walletdb.h
Normal file
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2009-2010 Satoshi Nakamoto
|
||||
// Copyright (c) 2009-2013 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_WALLETDB_H
|
||||
#define BITCOIN_WALLETDB_H
|
||||
|
||||
#include "amount.h"
|
||||
#include "wallet/db.h"
|
||||
#include "key.h"
|
||||
#include "keystore.h"
|
||||
|
||||
#include <list>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class CAccount;
|
||||
class CAccountingEntry;
|
||||
struct CBlockLocator;
|
||||
class CKeyPool;
|
||||
class CMasterKey;
|
||||
class CScript;
|
||||
class CWallet;
|
||||
class CWalletTx;
|
||||
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+", bool fFlushOnClose = true) : CDB(strFilename, pszMode, fFlushOnClose)
|
||||
{
|
||||
}
|
||||
|
||||
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 WriteCScript(const uint160& hash, const CScript& redeemScript);
|
||||
|
||||
bool WriteWatchOnly(const CScript &script);
|
||||
bool EraseWatchOnly(const CScript &script);
|
||||
|
||||
bool WriteBestBlock(const CBlockLocator& locator);
|
||||
bool ReadBestBlock(CBlockLocator& locator);
|
||||
|
||||
bool WriteOrderPosNext(int64_t nOrderPosNext);
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
bool WriteAccountingEntry(const CAccountingEntry& acentry);
|
||||
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);
|
||||
|
||||
private:
|
||||
CWalletDB(const CWalletDB&);
|
||||
void operator=(const CWalletDB&);
|
||||
|
||||
bool WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccountingEntry& acentry);
|
||||
};
|
||||
|
||||
bool BackupWallet(const CWallet& wallet, const std::string& strDest);
|
||||
|
||||
#endif // BITCOIN_WALLETDB_H
|
||||
Reference in New Issue
Block a user