Page MenuHomePhabricator

No OneTemporary

diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 36946b7dcc..53cfcf0961 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,462 +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))
+CDBEnv::VerifyResult CDBEnv::Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const 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)
+bool CDBEnv::Salvage(const 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");
}
}
}
}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 71133f9699..0c2c139d89 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,313 +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));
+ VerifyResult Verify(const std::string& strFile, bool (*recoverFunc)(CDBEnv& dbenv, const 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 Salvage(const 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
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 44b416d496..167639b53d 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,2660 +1,2660 @@
// 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/wallet.h"
#include "base58.h"
#include "checkpoints.h"
#include "coincontrol.h"
#include "net.h"
#include "script/script.h"
#include "script/sign.h"
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
#include <assert.h>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
using namespace std;
/**
* Settings
*/
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
unsigned int nTxConfirmTarget = 1;
bool bSpendZeroConfChange = true;
bool fSendFreeTransactions = false;
bool fPayAtLeastCustomFee = true;
/**
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
* Override with -mintxfee
*/
CFeeRate CWallet::minTxFee = CFeeRate(1000);
/** @defgroup mapWallet
*
* @{
*/
struct CompareValueOnly
{
bool operator()(const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t1,
const pair<CAmount, pair<const CWalletTx*, unsigned int> >& t2) const
{
return t1.first < t2.first;
}
};
std::string COutput::ToString() const
{
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->vout[i].nValue));
}
const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
{
LOCK(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(hash);
if (it == mapWallet.end())
return NULL;
return &(it->second);
}
CPubKey CWallet::GenerateNewKey()
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); // default to compressed public keys if we want 0.6.0 wallets
CKey secret;
secret.MakeNewKey(fCompressed);
// Compressed public keys were introduced in version 0.6.0
if (fCompressed)
SetMinVersion(FEATURE_COMPRPUBKEY);
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
// Create new metadata
int64_t nCreationTime = GetTime();
mapKeyMetadata[pubkey.GetID()] = CKeyMetadata(nCreationTime);
if (!nTimeFirstKey || nCreationTime < nTimeFirstKey)
nTimeFirstKey = nCreationTime;
if (!AddKeyPubKey(secret, pubkey))
throw std::runtime_error("CWallet::GenerateNewKey(): AddKey failed");
return pubkey;
}
bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey))
return false;
// check if we need to remove from watch-only
CScript script;
script = GetScriptForDestination(pubkey.GetID());
if (HaveWatchOnly(script))
RemoveWatchOnly(script);
if (!fFileBacked)
return true;
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteKey(pubkey,
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
return true;
}
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
const vector<unsigned char> &vchCryptedSecret)
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
if (!fFileBacked)
return true;
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
return pwalletdbEncryption->WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
else
return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
return false;
}
bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapKeyMetadata
if (meta.nCreateTime && (!nTimeFirstKey || meta.nCreateTime < nTimeFirstKey))
nTimeFirstKey = meta.nCreateTime;
mapKeyMetadata[pubkey.GetID()] = meta;
return true;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
}
bool CWallet::LoadCScript(const CScript& redeemScript)
{
/* A sanity check was added in pull #3843 to avoid adding redeemScripts
* that never can be redeemed. However, old wallets may still contain
* these. Do not add them to the wallet and warn. */
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE)
{
std::string strAddr = CBitcoinAddress(CScriptID(redeemScript)).ToString();
LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i which exceeds maximum size %i thus can never be redeemed. Do not use address %s.\n",
__func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr);
return true;
}
return CCryptoKeyStore::AddCScript(redeemScript);
}
bool CWallet::AddWatchOnly(const CScript &dest)
{
if (!CCryptoKeyStore::AddWatchOnly(dest))
return false;
nTimeFirstKey = 1; // No birthday information for watch-only keys.
NotifyWatchonlyChanged(true);
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteWatchOnly(dest);
}
bool CWallet::RemoveWatchOnly(const CScript &dest)
{
AssertLockHeld(cs_wallet);
if (!CCryptoKeyStore::RemoveWatchOnly(dest))
return false;
if (!HaveWatchOnly())
NotifyWatchonlyChanged(false);
if (fFileBacked)
if (!CWalletDB(strWalletFile).EraseWatchOnly(dest))
return false;
return true;
}
bool CWallet::LoadWatchOnly(const CScript &dest)
{
return CCryptoKeyStore::AddWatchOnly(dest);
}
bool CWallet::Unlock(const SecureString& strWalletPassphrase)
{
CCrypter crypter;
CKeyingMaterial vMasterKey;
{
LOCK(cs_wallet);
BOOST_FOREACH(const MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
continue; // try another master key
if (CCryptoKeyStore::Unlock(vMasterKey))
return true;
}
}
return false;
}
bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase, const SecureString& strNewWalletPassphrase)
{
bool fWasLocked = IsLocked();
{
LOCK(cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial vMasterKey;
BOOST_FOREACH(MasterKeyMap::value_type& pMasterKey, mapMasterKeys)
{
if(!crypter.SetKeyFromPassphrase(strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, vMasterKey))
return false;
if (CCryptoKeyStore::Unlock(vMasterKey))
{
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + pMasterKey.second.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (pMasterKey.second.nDeriveIterations < 25000)
pMasterKey.second.nDeriveIterations = 25000;
LogPrintf("Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod))
return false;
if (!crypter.Encrypt(vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
Lock();
return true;
}
}
}
return false;
}
void CWallet::SetBestChain(const CBlockLocator& loc)
{
CWalletDB walletdb(strWalletFile);
walletdb.WriteBestBlock(loc);
}
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn, bool fExplicit)
{
LOCK(cs_wallet); // nWalletVersion
if (nWalletVersion >= nVersion)
return true;
// when doing an explicit upgrade, if we pass the max version permitted, upgrade all the way
if (fExplicit && nVersion > nWalletMaxVersion)
nVersion = FEATURE_LATEST;
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion)
nWalletMaxVersion = nVersion;
if (fFileBacked)
{
CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
if (nWalletVersion > 40000)
pwalletdb->WriteMinVersion(nWalletVersion);
if (!pwalletdbIn)
delete pwalletdb;
}
return true;
}
bool CWallet::SetMaxVersion(int nVersion)
{
LOCK(cs_wallet); // nWalletVersion, nWalletMaxVersion
// cannot downgrade below current version
if (nWalletVersion > nVersion)
return false;
nWalletMaxVersion = nVersion;
return true;
}
set<uint256> CWallet::GetConflicts(const uint256& txid) const
{
set<uint256> result;
AssertLockHeld(cs_wallet);
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end())
return result;
const CWalletTx& wtx = it->second;
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
BOOST_FOREACH(const CTxIn& txin, wtx.vin)
{
if (mapTxSpends.count(txin.prevout) <= 1)
continue; // No conflict if zero or one spends
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
result.insert(it->second);
}
return result;
}
void CWallet::Flush(bool shutdown)
{
bitdb.Flush(shutdown);
}
-bool CWallet::Verify(const string walletFile, string& warningString, string& errorString)
+bool CWallet::Verify(const string& walletFile, string& warningString, string& errorString)
{
if (!bitdb.Open(GetDataDir()))
{
// try moving the database env out of the way
boost::filesystem::path pathDatabase = GetDataDir() / "database";
boost::filesystem::path pathDatabaseBak = GetDataDir() / strprintf("database.%d.bak", GetTime());
try {
boost::filesystem::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
} catch (const boost::filesystem::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}
// try again
if (!bitdb.Open(GetDataDir())) {
// if it still fails, it probably means we can't even create the database env
string msg = strprintf(_("Error initializing wallet database environment %s!"), GetDataDir());
errorString += msg;
return true;
}
}
if (GetBoolArg("-salvagewallet", false))
{
// Recover readable keypairs:
if (!CWalletDB::Recover(bitdb, walletFile, true))
return false;
}
if (boost::filesystem::exists(GetDataDir() / walletFile))
{
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, CWalletDB::Recover);
if (r == CDBEnv::RECOVER_OK)
{
warningString += strprintf(_("Warning: wallet.dat corrupt, data salvaged!"
" Original wallet.dat saved as wallet.{timestamp}.bak in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."), GetDataDir());
}
if (r == CDBEnv::RECOVER_FAIL)
errorString += _("wallet.dat corrupt, salvage failed");
}
return true;
}
void CWallet::SyncMetaData(pair<TxSpends::iterator, TxSpends::iterator> range)
{
// We want all the wallet transactions in range to have the same metadata as
// the oldest (smallest nOrderPos).
// So: find smallest nOrderPos:
int nMinOrderPos = std::numeric_limits<int>::max();
const CWalletTx* copyFrom = NULL;
for (TxSpends::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
int n = mapWallet[hash].nOrderPos;
if (n < nMinOrderPos)
{
nMinOrderPos = n;
copyFrom = &mapWallet[hash];
}
}
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it)
{
const uint256& hash = it->second;
CWalletTx* copyTo = &mapWallet[hash];
if (copyFrom == copyTo) continue;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose
// nTimeReceived not copied on purpose
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
// nOrderPos not copied on purpose
// cached members not copied on purpose
}
}
/**
* Outpoint is spent if any non-conflicted transaction
* spends it:
*/
bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
{
const COutPoint outpoint(hash, n);
pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
range = mapTxSpends.equal_range(outpoint);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it)
{
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0)
return true; // Spent
}
return false;
}
void CWallet::AddToSpends(const COutPoint& outpoint, const uint256& wtxid)
{
mapTxSpends.insert(make_pair(outpoint, wtxid));
pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
}
void CWallet::AddToSpends(const uint256& wtxid)
{
assert(mapWallet.count(wtxid));
CWalletTx& thisTx = mapWallet[wtxid];
if (thisTx.IsCoinBase()) // Coinbases don't spend anything!
return;
BOOST_FOREACH(const CTxIn& txin, thisTx.vin)
AddToSpends(txin.prevout, wtxid);
}
bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
if (IsCrypted())
return false;
CKeyingMaterial vMasterKey;
RandAddSeedPerfmon();
vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetRandBytes(&vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
RandAddSeedPerfmon();
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = 2500000 / ((double)(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + kMasterKey.nDeriveIterations * 100 / ((double)(GetTimeMillis() - nStartTime))) / 2;
if (kMasterKey.nDeriveIterations < 25000)
kMasterKey.nDeriveIterations = 25000;
LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod))
return false;
if (!crypter.Encrypt(vMasterKey, kMasterKey.vchCryptedKey))
return false;
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
if (fFileBacked)
{
assert(!pwalletdbEncryption);
pwalletdbEncryption = new CWalletDB(strWalletFile);
if (!pwalletdbEncryption->TxnBegin()) {
delete pwalletdbEncryption;
pwalletdbEncryption = NULL;
return false;
}
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
}
if (!EncryptKeys(vMasterKey))
{
if (fFileBacked) {
pwalletdbEncryption->TxnAbort();
delete pwalletdbEncryption;
}
// We now probably have half of our keys encrypted in memory, and half not...
// die and let the user reload their unencrypted wallet.
assert(false);
}
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
if (fFileBacked)
{
if (!pwalletdbEncryption->TxnCommit()) {
delete pwalletdbEncryption;
// We now have keys encrypted in memory, but not on disk...
// die to avoid confusion and let the user reload their unencrypted wallet.
assert(false);
}
delete pwalletdbEncryption;
pwalletdbEncryption = NULL;
}
Lock();
Unlock(strWalletPassphrase);
NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might keep
// bits of the unencrypted private key in slack space in the database file.
CDB::Rewrite(strWalletFile);
}
NotifyStatusChanged(this);
return true;
}
int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
{
AssertLockHeld(cs_wallet); // nOrderPosNext
int64_t nRet = nOrderPosNext++;
if (pwalletdb) {
pwalletdb->WriteOrderPosNext(nOrderPosNext);
} else {
CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount)
{
AssertLockHeld(cs_wallet); // mapWallet
CWalletDB walletdb(strWalletFile);
// First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
TxItems txOrdered;
// Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry
// would make this much faster for applications that do this a lot.
for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
CWalletTx* wtx = &((*it).second);
txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0)));
}
acentries.clear();
walletdb.ListAccountCreditDebit(strAccount, acentries);
BOOST_FOREACH(CAccountingEntry& entry, acentries)
{
txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
}
return txOrdered;
}
void CWallet::MarkDirty()
{
{
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
item.second.MarkDirty();
}
}
bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb)
{
uint256 hash = wtxIn.GetHash();
if (fFromLoadWallet)
{
mapWallet[hash] = wtxIn;
mapWallet[hash].BindWallet(this);
AddToSpends(hash);
}
else
{
LOCK(cs_wallet);
// Inserts only if not already there, returns tx inserted or tx found
pair<map<uint256, CWalletTx>::iterator, bool> ret = mapWallet.insert(make_pair(hash, wtxIn));
CWalletTx& wtx = (*ret.first).second;
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
if (fInsertedNew)
{
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(pwalletdb);
wtx.nTimeSmart = wtx.nTimeReceived;
if (!wtxIn.hashBlock.IsNull())
{
if (mapBlockIndex.count(wtxIn.hashBlock))
{
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
{
// Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
std::list<CAccountingEntry> acentries;
TxItems txOrdered = OrderedTxItems(acentries);
for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
CWalletTx *const pwtx = (*it).second.first;
if (pwtx == &wtx)
continue;
CAccountingEntry *const pacentry = (*it).second.second;
int64_t nSmartTime;
if (pwtx)
{
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime)
nSmartTime = pwtx->nTimeReceived;
}
else
nSmartTime = pacentry->nTime;
if (nSmartTime <= latestTolerated)
{
latestEntry = nSmartTime;
if (nSmartTime > latestNow)
latestNow = nSmartTime;
break;
}
}
}
int64_t blocktime = mapBlockIndex[wtxIn.hashBlock]->GetBlockTime();
wtx.nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
}
else
LogPrintf("AddToWallet(): found %s in block %s not in index\n",
wtxIn.GetHash().ToString(),
wtxIn.hashBlock.ToString());
}
AddToSpends(hash);
}
bool fUpdated = false;
if (!fInsertedNew)
{
// Merge
if (!wtxIn.hashBlock.IsNull() && wtxIn.hashBlock != wtx.hashBlock)
{
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.vMerkleBranch != wtx.vMerkleBranch || wtxIn.nIndex != wtx.nIndex))
{
wtx.vMerkleBranch = wtxIn.vMerkleBranch;
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe)
{
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
}
//// debug print
LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetHash().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if (fInsertedNew || fUpdated)
if (!wtx.WriteToDisk(pwalletdb))
return false;
// Break debit/credit balance caches:
wtx.MarkDirty();
// Notify UI of new or updated transaction
NotifyTransactionChanged(this, hash, fInsertedNew ? CT_NEW : CT_UPDATED);
// notify an external script when a wallet transaction comes in or is updated
std::string strCmd = GetArg("-walletnotify", "");
if ( !strCmd.empty())
{
boost::replace_all(strCmd, "%s", wtxIn.GetHash().GetHex());
boost::thread t(runCommand, strCmd); // thread runs free
}
}
return true;
}
/**
* Add a transaction to the wallet, or update it.
* pblock is optional, but should be provided if the transaction is known to be in a block.
* If fUpdate is true, existing transactions will be updated.
*/
bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pblock, bool fUpdate)
{
{
AssertLockHeld(cs_wallet);
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
{
CWalletTx wtx(this,tx);
// Get merkle branch if transaction was found in a block
if (pblock)
wtx.SetMerkleBranch(*pblock);
// Do not flush the wallet here for performance reasons
// this is safe, as in case of a crash, we rescan the necessary blocks on startup through our SetBestChain-mechanism
CWalletDB walletdb(strWalletFile, "r+", false);
return AddToWallet(wtx, false, &walletdb);
}
}
return false;
}
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
{
LOCK2(cs_main, cs_wallet);
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be
// recomputed, also:
BOOST_FOREACH(const CTxIn& txin, tx.vin)
{
if (mapWallet.count(txin.prevout.hash))
mapWallet[txin.prevout.hash].MarkDirty();
}
}
void CWallet::EraseFromWallet(const uint256 &hash)
{
if (!fFileBacked)
return;
{
LOCK(cs_wallet);
if (mapWallet.erase(hash))
CWalletDB(strWalletFile).EraseTx(hash);
}
return;
}
isminetype CWallet::IsMine(const CTxIn &txin) const
{
{
LOCK(cs_wallet);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
return IsMine(prev.vout[txin.prevout.n]);
}
}
return ISMINE_NO;
}
CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const
{
{
LOCK(cs_wallet);
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.vout.size())
if (IsMine(prev.vout[txin.prevout.n]) & filter)
return prev.vout[txin.prevout.n].nValue;
}
}
return 0;
}
bool CWallet::IsChange(const CTxOut& txout) const
{
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book
// is change. That assumption is likely to break when we implement multisignature
// wallets that return change back into a multi-signature-protected address;
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
if (::IsMine(*this, txout.scriptPubKey))
{
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
return true;
LOCK(cs_wallet);
if (!mapAddressBook.count(address))
return true;
}
return false;
}
int64_t CWalletTx::GetTxTime() const
{
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
int CWalletTx::GetRequestCount() const
{
// Returns -1 if it wasn't being tracked
int nRequests = -1;
{
LOCK(pwallet->cs_wallet);
if (IsCoinBase())
{
// Generated block
if (!hashBlock.IsNull())
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
nRequests = (*mi).second;
}
}
else
{
// Did anyone request this transaction?
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(GetHash());
if (mi != pwallet->mapRequestCount.end())
{
nRequests = (*mi).second;
// How about the block it's in?
if (nRequests == 0 && !hashBlock.IsNull())
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
nRequests = (*mi).second;
else
nRequests = 1; // If it's in someone else's block it must have got out
}
}
}
}
return nRequests;
}
void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
list<COutputEntry>& listSent, CAmount& nFee, string& strSentAccount, const isminefilter& filter) const
{
nFee = 0;
listReceived.clear();
listSent.clear();
strSentAccount = strFromAccount;
// Compute fee:
CAmount nDebit = GetDebit(filter);
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
CAmount nValueOut = GetValueOut();
nFee = nDebit - nValueOut;
}
// Sent/received.
for (unsigned int i = 0; i < vout.size(); ++i)
{
const CTxOut& txout = vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > 0)
{
// Don't report 'change' txouts
if (pwallet->IsChange(txout))
continue;
}
else if (!(fIsMine & filter))
continue;
// In either case, we need to get the destination address
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
{
LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString());
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0)
listSent.push_back(output);
// If we are receiving the output, add it as a "received" entry
if (fIsMine & filter)
listReceived.push_back(output);
}
}
void CWalletTx::GetAccountAmounts(const string& strAccount, CAmount& nReceived,
CAmount& nSent, CAmount& nFee, const isminefilter& filter) const
{
nReceived = nSent = nFee = 0;
CAmount allFee;
string strSentAccount;
list<COutputEntry> listReceived;
list<COutputEntry> listSent;
GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
if (strAccount == strSentAccount)
{
BOOST_FOREACH(const COutputEntry& s, listSent)
nSent += s.amount;
nFee = allFee;
}
{
LOCK(pwallet->cs_wallet);
BOOST_FOREACH(const COutputEntry& r, listReceived)
{
if (pwallet->mapAddressBook.count(r.destination))
{
map<CTxDestination, CAddressBookData>::const_iterator mi = pwallet->mapAddressBook.find(r.destination);
if (mi != pwallet->mapAddressBook.end() && (*mi).second.name == strAccount)
nReceived += r.amount;
}
else if (strAccount.empty())
{
nReceived += r.amount;
}
}
}
}
bool CWalletTx::WriteToDisk(CWalletDB *pwalletdb)
{
return pwalletdb->WriteTx(GetHash(), *this);
}
/**
* Scan the block chain (starting in pindexStart) for transactions
* from or to us. If fUpdate is true, found transactions that already
* exist in the wallet will be updated.
*/
int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
{
int ret = 0;
int64_t nNow = GetTime();
CBlockIndex* pindex = pindexStart;
{
LOCK2(cs_main, cs_wallet);
// no need to read and scan block, if block was created before
// our wallet birthday (as adjusted for block time variability)
while (pindex && nTimeFirstKey && (pindex->GetBlockTime() < (nTimeFirstKey - 7200)))
pindex = chainActive.Next(pindex);
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
double dProgressStart = Checkpoints::GuessVerificationProgress(pindex, false);
double dProgressTip = Checkpoints::GuessVerificationProgress(chainActive.Tip(), false);
while (pindex)
{
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
CBlock block;
ReadBlockFromDisk(block, pindex);
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
ret++;
}
pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {
nNow = GetTime();
LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, Checkpoints::GuessVerificationProgress(pindex));
}
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
}
return ret;
}
void CWallet::ReacceptWalletTransactions()
{
LOCK2(cs_main, cs_wallet);
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
const uint256& wtxid = item.first;
CWalletTx& wtx = item.second;
assert(wtx.GetHash() == wtxid);
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && nDepth < 0)
{
// Try to add to memory pool
LOCK(mempool.cs);
wtx.AcceptToMemoryPool(false);
}
}
}
void CWalletTx::RelayWalletTransaction()
{
if (!IsCoinBase())
{
if (GetDepthInMainChain() == 0) {
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
RelayTransaction((CTransaction)*this);
}
}
}
set<uint256> CWalletTx::GetConflicts() const
{
set<uint256> result;
if (pwallet != NULL)
{
uint256 myHash = GetHash();
result = pwallet->GetConflicts(myHash);
result.erase(myHash);
}
return result;
}
CAmount CWalletTx::GetDebit(const isminefilter& filter) const
{
if (vin.empty())
return 0;
CAmount debit = 0;
if(filter & ISMINE_SPENDABLE)
{
if (fDebitCached)
debit += nDebitCached;
else
{
nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
fDebitCached = true;
debit += nDebitCached;
}
}
if(filter & ISMINE_WATCH_ONLY)
{
if(fWatchDebitCached)
debit += nWatchDebitCached;
else
{
nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
fWatchDebitCached = true;
debit += nWatchDebitCached;
}
}
return debit;
}
CAmount CWalletTx::GetCredit(const isminefilter& filter) const
{
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
int64_t credit = 0;
if (filter & ISMINE_SPENDABLE)
{
// GetBalance can assume transactions in mapWallet won't change
if (fCreditCached)
credit += nCreditCached;
else
{
nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fCreditCached = true;
credit += nCreditCached;
}
}
if (filter & ISMINE_WATCH_ONLY)
{
if (fWatchCreditCached)
credit += nWatchCreditCached;
else
{
nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fWatchCreditCached = true;
credit += nWatchCreditCached;
}
}
return credit;
}
CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
{
if (fUseCache && fImmatureCreditCached)
return nImmatureCreditCached;
nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fImmatureCreditCached = true;
return nImmatureCreditCached;
}
return 0;
}
CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
{
if (pwallet == 0)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
if (fUseCache && fAvailableCreditCached)
return nAvailableCreditCached;
CAmount nCredit = 0;
uint256 hashTx = GetHash();
for (unsigned int i = 0; i < vout.size(); i++)
{
if (!pwallet->IsSpent(hashTx, i))
{
const CTxOut &txout = vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
}
nAvailableCreditCached = nCredit;
fAvailableCreditCached = true;
return nCredit;
}
CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const
{
if (IsCoinBase() && GetBlocksToMaturity() > 0 && IsInMainChain())
{
if (fUseCache && fImmatureWatchCreditCached)
return nImmatureWatchCreditCached;
nImmatureWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fImmatureWatchCreditCached = true;
return nImmatureWatchCreditCached;
}
return 0;
}
CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const
{
if (pwallet == 0)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
if (fUseCache && fAvailableWatchCreditCached)
return nAvailableWatchCreditCached;
CAmount nCredit = 0;
for (unsigned int i = 0; i < vout.size(); i++)
{
if (!pwallet->IsSpent(GetHash(), i))
{
const CTxOut &txout = vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
if (!MoneyRange(nCredit))
throw std::runtime_error("CWalletTx::GetAvailableCredit() : value out of range");
}
}
nAvailableWatchCreditCached = nCredit;
fAvailableWatchCreditCached = true;
return nCredit;
}
CAmount CWalletTx::GetChange() const
{
if (fChangeCached)
return nChangeCached;
nChangeCached = pwallet->GetChange(*this);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::IsTrusted() const
{
// Quick answer in most cases
if (!IsFinalTx(*this))
return false;
int nDepth = GetDepthInMainChain();
if (nDepth >= 1)
return true;
if (nDepth < 0)
return false;
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
return false;
// Trusted if all inputs are from us and are in the mempool:
BOOST_FOREACH(const CTxIn& txin, vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = pwallet->GetWalletTx(txin.prevout.hash);
if (parent == NULL)
return false;
const CTxOut& parentOut = parent->vout[txin.prevout.n];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE)
return false;
}
return true;
}
void CWallet::ResendWalletTransactions()
{
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
if (GetTime() < nNextResend)
return;
bool fFirst = (nNextResend == 0);
nNextResend = GetTime() + GetRand(30 * 60);
if (fFirst)
return;
// Only do it if there's been a new block since last time
if (nTimeBestReceived < nLastResend)
return;
nLastResend = GetTime();
// Rebroadcast any of our txes that aren't in a block yet
LogPrintf("ResendWalletTransactions()\n");
{
LOCK(cs_wallet);
// Sort them in chronological order
multimap<unsigned int, CWalletTx*> mapSorted;
BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
{
CWalletTx& wtx = item.second;
// Don't rebroadcast until it's had plenty of time that
// it should have gotten in already by now.
if (nTimeBestReceived - (int64_t)wtx.nTimeReceived > 5 * 60)
mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
}
BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
{
CWalletTx& wtx = *item.second;
wtx.RelayWalletTransaction();
}
}
}
/** @} */ // end of mapWallet
/** @defgroup Actions
*
* @{
*/
CAmount CWallet::GetBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
CAmount CWallet::GetUnconfirmedBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
CAmount CWallet::GetImmatureBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
nTotal += pcoin->GetImmatureCredit();
}
}
return nTotal;
}
CAmount CWallet::GetWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
if (!IsFinalTx(*pcoin) || (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0))
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
CAmount CWallet::GetImmatureWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const CWalletTx* pcoin = &(*it).second;
nTotal += pcoin->GetImmatureWatchOnlyCredit();
}
}
return nTotal;
}
/**
* populate vCoins with vector of available COutputs.
*/
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl) const
{
vCoins.clear();
{
LOCK2(cs_main, cs_wallet);
for (map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
{
const uint256& wtxid = it->first;
const CWalletTx* pcoin = &(*it).second;
if (!IsFinalTx(*pcoin))
continue;
if (fOnlyConfirmed && !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0)
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++) {
isminetype mine = IsMine(pcoin->vout[i]);
if (!(IsSpent(wtxid, i)) && mine != ISMINE_NO &&
!IsLockedCoin((*it).first, i) && pcoin->vout[i].nValue > 0 &&
(!coinControl || !coinControl->HasSelected() || coinControl->IsSelected((*it).first, i)))
vCoins.push_back(COutput(pcoin, i, nDepth, (mine & ISMINE_SPENDABLE) != ISMINE_NO));
}
}
}
}
static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > >vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
vector<char> vfIncluded;
vfBest.assign(vValue.size(), true);
nBest = nTotalLower;
seed_insecure_rand();
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
{
vfIncluded.assign(vValue.size(), false);
CAmount nTotal = 0;
bool fReachedTarget = false;
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
{
for (unsigned int i = 0; i < vValue.size(); i++)
{
//The solver here uses a randomized algorithm,
//the randomness serves no real security purpose but is just
//needed to prevent degenerate behavior and it is important
//that the rng is fast. We do not use a constant random sequence,
//because there may be some privacy improvement by making
//the selection random.
if (nPass == 0 ? insecure_rand()&1 : !vfIncluded[i])
{
nTotal += vValue[i].first;
vfIncluded[i] = true;
if (nTotal >= nTargetValue)
{
fReachedTarget = true;
if (nTotal < nBest)
{
nBest = nTotal;
vfBest = vfIncluded;
}
nTotal -= vValue[i].first;
vfIncluded[i] = false;
}
}
}
}
}
}
bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,
set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
{
setCoinsRet.clear();
nValueRet = 0;
// List of values less than target
pair<CAmount, pair<const CWalletTx*,unsigned int> > coinLowestLarger;
coinLowestLarger.first = std::numeric_limits<CAmount>::max();
coinLowestLarger.second.first = NULL;
vector<pair<CAmount, pair<const CWalletTx*,unsigned int> > > vValue;
CAmount nTotalLower = 0;
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
BOOST_FOREACH(const COutput &output, vCoins)
{
if (!output.fSpendable)
continue;
const CWalletTx *pcoin = output.tx;
if (output.nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs))
continue;
int i = output.i;
CAmount n = pcoin->vout[i].nValue;
pair<CAmount,pair<const CWalletTx*,unsigned int> > coin = make_pair(n,make_pair(pcoin, i));
if (n == nTargetValue)
{
setCoinsRet.insert(coin.second);
nValueRet += coin.first;
return true;
}
else if (n < nTargetValue + CENT)
{
vValue.push_back(coin);
nTotalLower += n;
}
else if (n < coinLowestLarger.first)
{
coinLowestLarger = coin;
}
}
if (nTotalLower == nTargetValue)
{
for (unsigned int i = 0; i < vValue.size(); ++i)
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
return true;
}
if (nTotalLower < nTargetValue)
{
if (coinLowestLarger.second.first == NULL)
return false;
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
return true;
}
// Solve subset sum by stochastic approximation
sort(vValue.rbegin(), vValue.rend(), CompareValueOnly());
vector<char> vfBest;
CAmount nBest;
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT)
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000);
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
// or the next bigger coin is closer), return the bigger coin
if (coinLowestLarger.second.first &&
((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest))
{
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
}
else {
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
{
setCoinsRet.insert(vValue[i].second);
nValueRet += vValue[i].first;
}
LogPrint("selectcoins", "SelectCoins() best subset: ");
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
LogPrint("selectcoins", "%s ", FormatMoney(vValue[i].first));
LogPrint("selectcoins", "total %s\n", FormatMoney(nBest));
}
return true;
}
bool CWallet::SelectCoins(const CAmount& nTargetValue, set<pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
{
vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
if (coinControl && coinControl->HasSelected())
{
BOOST_FOREACH(const COutput& out, vCoins)
{
if(!out.fSpendable)
continue;
nValueRet += out.tx->vout[out.i].nValue;
setCoinsRet.insert(make_pair(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
return (SelectCoinsMinConf(nTargetValue, 1, 6, vCoins, setCoinsRet, nValueRet) ||
SelectCoinsMinConf(nTargetValue, 1, 1, vCoins, setCoinsRet, nValueRet) ||
(bSpendZeroConfChange && SelectCoinsMinConf(nTargetValue, 0, 1, vCoins, setCoinsRet, nValueRet)));
}
bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend,
CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, const CCoinControl* coinControl)
{
CAmount nValue = 0;
unsigned int nSubtractFeeFromAmount = 0;
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
if (nValue < 0 || recipient.nAmount < 0)
{
strFailReason = _("Transaction amounts must be positive");
return false;
}
nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount)
nSubtractFeeFromAmount++;
}
if (vecSend.empty() || nValue < 0)
{
strFailReason = _("Transaction amounts must be positive");
return false;
}
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.BindWallet(this);
CMutableTransaction txNew;
// Discourage fee sniping.
//
// However because of a off-by-one-error in previous versions we need to
// neuter it by setting nLockTime to at least one less than nBestHeight.
// Secondly currently propagation of transactions created for block heights
// corresponding to blocks that were just mined may be iffy - transactions
// aren't re-accepted into the mempool - we additionally neuter the code by
// going ten blocks back. Doesn't yet do anything for sniping, but does act
// to shake out wallet bugs like not showing nLockTime'd transactions at
// all.
txNew.nLockTime = std::max(0, chainActive.Height() - 10);
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
if (GetRandInt(10) == 0)
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
{
LOCK2(cs_main, cs_wallet);
{
nFeeRet = 0;
while (true)
{
txNew.vin.clear();
txNew.vout.clear();
wtxNew.fFromMe = true;
nChangePosRet = -1;
bool fFirst = true;
CAmount nTotalValue = nValue;
if (nSubtractFeeFromAmount == 0)
nTotalValue += nFeeRet;
double dPriority = 0;
// vouts to the payees
BOOST_FOREACH (const CRecipient& recipient, vecSend)
{
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
if (recipient.fSubtractFeeFromAmount)
{
txout.nValue -= nFeeRet / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient
if (fFirst) // first receiver pays the remainder not divisible by output count
{
fFirst = false;
txout.nValue -= nFeeRet % nSubtractFeeFromAmount;
}
}
if (txout.IsDust(::minRelayTxFee))
{
if (recipient.fSubtractFeeFromAmount && nFeeRet > 0)
{
if (txout.nValue < 0)
strFailReason = _("The transaction amount is too small to pay the fee");
else
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
}
else
strFailReason = _("Transaction amount too small");
return false;
}
txNew.vout.push_back(txout);
}
// Choose coins to use
set<pair<const CWalletTx*,unsigned int> > setCoins;
CAmount nValueIn = 0;
if (!SelectCoins(nTotalValue, setCoins, nValueIn, coinControl))
{
strFailReason = _("Insufficient funds");
return false;
}
BOOST_FOREACH(PAIRTYPE(const CWalletTx*, unsigned int) pcoin, setCoins)
{
CAmount nCredit = pcoin.first->vout[pcoin.second].nValue;
//The coin age after the next block (depth+1) is used instead of the current,
//reflecting an assumption the user would accept a bit more delay for
//a chance at a free transaction.
//But mempool inputs might still be in the mempool, so their age stays 0
int age = pcoin.first->GetDepthInMainChain();
if (age != 0)
age += 1;
dPriority += (double)nCredit * age;
}
CAmount nChange = nValueIn - nValue;
if (nSubtractFeeFromAmount == 0)
nChange -= nFeeRet;
if (nChange > 0)
{
// Fill a vout to ourself
// TODO: pass in scriptChange instead of reservekey so
// change transaction isn't always pay-to-bitcoin-address
CScript scriptChange;
// coin control: send change to custom address
if (coinControl && !boost::get<CNoDestination>(&coinControl->destChange))
scriptChange = GetScriptForDestination(coinControl->destChange);
// no coin control: send change to newly generated address
else
{
// Note: We use a new key here to keep it from being obvious which side is the change.
// The drawback is that by not reusing a previous key, the change may be lost if a
// backup is restored, if the backup doesn't have the new private key for the change.
// If we reused the old key, it would be possible to add code to look for and
// rediscover unknown transactions that were written with keys of ours to recover
// post-backup change.
// Reserve a new key pair from key pool
CPubKey vchPubKey;
bool ret;
ret = reservekey.GetReservedKey(vchPubKey);
assert(ret); // should never fail, as we just unlocked
scriptChange = GetScriptForDestination(vchPubKey.GetID());
}
CTxOut newTxOut(nChange, scriptChange);
// We do not move dust-change to fees, because the sender would end up paying more than requested.
// This would be against the purpose of the all-inclusive feature.
// So instead we raise the change and deduct from the recipient.
if (nSubtractFeeFromAmount > 0 && newTxOut.IsDust(::minRelayTxFee))
{
CAmount nDust = newTxOut.GetDustThreshold(::minRelayTxFee) - newTxOut.nValue;
newTxOut.nValue += nDust; // raise change until no more dust
for (unsigned int i = 0; i < vecSend.size(); i++) // subtract from first recipient
{
if (vecSend[i].fSubtractFeeFromAmount)
{
txNew.vout[i].nValue -= nDust;
if (txNew.vout[i].IsDust(::minRelayTxFee))
{
strFailReason = _("The transaction amount is too small to send after the fee has been deducted");
return false;
}
break;
}
}
}
// Never create dust outputs; if we would, just
// add the dust to the fee.
if (newTxOut.IsDust(::minRelayTxFee))
{
nFeeRet += nChange;
reservekey.ReturnKey();
}
else
{
// Insert change txn at random position:
nChangePosRet = GetRandInt(txNew.vout.size()+1);
vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosRet;
txNew.vout.insert(position, newTxOut);
}
}
else
reservekey.ReturnKey();
// Fill vin
//
// Note how the sequence number is set to max()-1 so that the
// nLockTime set above actually works.
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
std::numeric_limits<unsigned int>::max()-1));
// Sign
int nIn = 0;
BOOST_FOREACH(const PAIRTYPE(const CWalletTx*,unsigned int)& coin, setCoins)
if (!SignSignature(*this, *coin.first, txNew, nIn++))
{
strFailReason = _("Signing transaction failed");
return false;
}
// Embed the constructed transaction data in wtxNew.
*static_cast<CTransaction*>(&wtxNew) = CTransaction(txNew);
// Limit size
unsigned int nBytes = ::GetSerializeSize(*(CTransaction*)&wtxNew, SER_NETWORK, PROTOCOL_VERSION);
if (nBytes >= MAX_STANDARD_TX_SIZE)
{
strFailReason = _("Transaction too large");
return false;
}
dPriority = wtxNew.ComputePriority(dPriority, nBytes);
// Can we complete this as a free transaction?
if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
{
// Not enough fee: enough priority?
double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
// Not enough mempool history to estimate: use hard-coded AllowFree.
if (dPriorityNeeded <= 0 && AllowFree(dPriority))
break;
// Small enough, and priority high enough, to send for free
if (dPriorityNeeded > 0 && dPriority >= dPriorityNeeded)
break;
}
CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
if (nFeeNeeded < ::minRelayTxFee.GetFee(nBytes))
{
strFailReason = _("Transaction too large for fee policy");
return false;
}
if (nFeeRet >= nFeeNeeded)
break; // Done, enough fee included.
// Include more fee and try again.
nFeeRet = nFeeNeeded;
continue;
}
}
}
return true;
}
/**
* Call after CreateTransaction unless you want to abort
*/
bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
{
{
LOCK2(cs_main, cs_wallet);
LogPrintf("CommitTransaction:\n%s", wtxNew.ToString());
{
// This is only to keep the database open to defeat the auto-flush for the
// duration of this scope. This is the only place where this optimization
// maybe makes sense; please don't do it anywhere else.
CWalletDB* pwalletdb = fFileBacked ? new CWalletDB(strWalletFile,"r+") : NULL;
// Take key pair from key pool so it won't be used again
reservekey.KeepKey();
// Add tx to wallet, because if it has change it's also ours,
// otherwise just for transaction history.
AddToWallet(wtxNew, false, pwalletdb);
// Notify that old coins are spent
set<CWalletTx*> setCoins;
BOOST_FOREACH(const CTxIn& txin, wtxNew.vin)
{
CWalletTx &coin = mapWallet[txin.prevout.hash];
coin.BindWallet(this);
NotifyTransactionChanged(this, coin.GetHash(), CT_UPDATED);
}
if (fFileBacked)
delete pwalletdb;
}
// Track how many getdata requests our transaction gets
mapRequestCount[wtxNew.GetHash()] = 0;
// Broadcast
if (!wtxNew.AcceptToMemoryPool(false))
{
// This must not fail. The transaction has already been signed and recorded.
LogPrintf("CommitTransaction(): Error: Transaction not valid");
return false;
}
wtxNew.RelayWalletTransaction();
}
return true;
}
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
{
// payTxFee is user-set "I want to pay this much"
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
// user selected total at least (default=true)
if (fPayAtLeastCustomFee && nFeeNeeded > 0 && nFeeNeeded < payTxFee.GetFeePerK())
nFeeNeeded = payTxFee.GetFeePerK();
// User didn't set: use -txconfirmtarget to estimate...
if (nFeeNeeded == 0)
nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes);
// ... unless we don't have enough mempool data, in which case fall
// back to a hard-coded fee
if (nFeeNeeded == 0)
nFeeNeeded = minTxFee.GetFee(nTxBytes);
// prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee
if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes))
nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes);
// But always obey the maximum
if (nFeeNeeded > maxTxFee)
nFeeNeeded = maxTxFee;
return nFeeNeeded;
}
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
if (!fFileBacked)
return DB_LOAD_OK;
fFirstRunRet = false;
DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// the requires a new key.
}
}
if (nLoadWalletRet != DB_LOAD_OK)
return nLoadWalletRet;
fFirstRunRet = !vchDefaultKey.IsValid();
uiInterface.LoadWallet(this);
return DB_LOAD_OK;
}
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
if (!fFileBacked)
return DB_LOAD_OK;
DBErrors nZapWalletTxRet = CWalletDB(strWalletFile,"cr+").ZapWalletTx(this, vWtx);
if (nZapWalletTxRet == DB_NEED_REWRITE)
{
if (CDB::Rewrite(strWalletFile, "\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nZapWalletTxRet != DB_LOAD_OK)
return nZapWalletTxRet;
return DB_LOAD_OK;
}
bool CWallet::SetAddressBook(const CTxDestination& address, const string& strName, const string& strPurpose)
{
bool fUpdated = false;
{
LOCK(cs_wallet); // mapAddressBook
std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
if (!strPurpose.empty()) /* update purpose only if requested */
mapAddressBook[address].purpose = strPurpose;
}
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
if (!fFileBacked)
return false;
if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose))
return false;
return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
{
{
LOCK(cs_wallet); // mapAddressBook
if(fFileBacked)
{
// Delete destdata tuples associated with address
std::string strAddress = CBitcoinAddress(address).ToString();
BOOST_FOREACH(const PAIRTYPE(string, string) &item, mapAddressBook[address].destdata)
{
CWalletDB(strWalletFile).EraseDestData(strAddress, item.first);
}
}
mapAddressBook.erase(address);
}
NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED);
if (!fFileBacked)
return false;
CWalletDB(strWalletFile).ErasePurpose(CBitcoinAddress(address).ToString());
return CWalletDB(strWalletFile).EraseName(CBitcoinAddress(address).ToString());
}
bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
{
if (fFileBacked)
{
if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey))
return false;
}
vchDefaultKey = vchPubKey;
return true;
}
/**
* Mark old keypool keys as used,
* and generate all new keys
*/
bool CWallet::NewKeyPool()
{
{
LOCK(cs_wallet);
CWalletDB walletdb(strWalletFile);
BOOST_FOREACH(int64_t nIndex, setKeyPool)
walletdb.ErasePool(nIndex);
setKeyPool.clear();
if (IsLocked())
return false;
int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0);
for (int i = 0; i < nKeys; i++)
{
int64_t nIndex = i+1;
walletdb.WritePool(nIndex, CKeyPool(GenerateNewKey()));
setKeyPool.insert(nIndex);
}
LogPrintf("CWallet::NewKeyPool wrote %d new keys\n", nKeys);
}
return true;
}
bool CWallet::TopUpKeyPool(unsigned int kpSize)
{
{
LOCK(cs_wallet);
if (IsLocked())
return false;
CWalletDB walletdb(strWalletFile);
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0)
nTargetSize = kpSize;
else
nTargetSize = max(GetArg("-keypool", 100), (int64_t) 0);
while (setKeyPool.size() < (nTargetSize + 1))
{
int64_t nEnd = 1;
if (!setKeyPool.empty())
nEnd = *(--setKeyPool.end()) + 1;
if (!walletdb.WritePool(nEnd, CKeyPool(GenerateNewKey())))
throw runtime_error("TopUpKeyPool(): writing generated key failed");
setKeyPool.insert(nEnd);
LogPrintf("keypool added key %d, size=%u\n", nEnd, setKeyPool.size());
}
}
return true;
}
void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool)
{
nIndex = -1;
keypool.vchPubKey = CPubKey();
{
LOCK(cs_wallet);
if (!IsLocked())
TopUpKeyPool();
// Get the oldest key
if(setKeyPool.empty())
return;
CWalletDB walletdb(strWalletFile);
nIndex = *(setKeyPool.begin());
setKeyPool.erase(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool))
throw runtime_error("ReserveKeyFromKeyPool(): read failed");
if (!HaveKey(keypool.vchPubKey.GetID()))
throw runtime_error("ReserveKeyFromKeyPool(): unknown key in key pool");
assert(keypool.vchPubKey.IsValid());
LogPrintf("keypool reserve %d\n", nIndex);
}
}
void CWallet::KeepKey(int64_t nIndex)
{
// Remove from key pool
if (fFileBacked)
{
CWalletDB walletdb(strWalletFile);
walletdb.ErasePool(nIndex);
}
LogPrintf("keypool keep %d\n", nIndex);
}
void CWallet::ReturnKey(int64_t nIndex)
{
// Return to key pool
{
LOCK(cs_wallet);
setKeyPool.insert(nIndex);
}
LogPrintf("keypool return %d\n", nIndex);
}
bool CWallet::GetKeyFromPool(CPubKey& result)
{
int64_t nIndex = 0;
CKeyPool keypool;
{
LOCK(cs_wallet);
ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex == -1)
{
if (IsLocked()) return false;
result = GenerateNewKey();
return true;
}
KeepKey(nIndex);
result = keypool.vchPubKey;
}
return true;
}
int64_t CWallet::GetOldestKeyPoolTime()
{
int64_t nIndex = 0;
CKeyPool keypool;
ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex == -1)
return GetTime();
ReturnKey(nIndex);
return keypool.nTime;
}
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances()
{
map<CTxDestination, CAmount> balances;
{
LOCK(cs_wallet);
BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
{
CWalletTx *pcoin = &walletEntry.second;
if (!IsFinalTx(*pcoin) || !pcoin->IsTrusted())
continue;
if (pcoin->IsCoinBase() && pcoin->GetBlocksToMaturity() > 0)
continue;
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1))
continue;
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
{
CTxDestination addr;
if (!IsMine(pcoin->vout[i]))
continue;
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, addr))
continue;
CAmount n = IsSpent(walletEntry.first, i) ? 0 : pcoin->vout[i].nValue;
if (!balances.count(addr))
balances[addr] = 0;
balances[addr] += n;
}
}
}
return balances;
}
set< set<CTxDestination> > CWallet::GetAddressGroupings()
{
AssertLockHeld(cs_wallet); // mapWallet
set< set<CTxDestination> > groupings;
set<CTxDestination> grouping;
BOOST_FOREACH(PAIRTYPE(uint256, CWalletTx) walletEntry, mapWallet)
{
CWalletTx *pcoin = &walletEntry.second;
if (pcoin->vin.size() > 0)
{
bool any_mine = false;
// group all input addresses with each other
BOOST_FOREACH(CTxIn txin, pcoin->vin)
{
CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */
continue;
if(!ExtractDestination(mapWallet[txin.prevout.hash].vout[txin.prevout.n].scriptPubKey, address))
continue;
grouping.insert(address);
any_mine = true;
}
// group change with input addresses
if (any_mine)
{
BOOST_FOREACH(CTxOut txout, pcoin->vout)
if (IsChange(txout))
{
CTxDestination txoutAddr;
if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
continue;
grouping.insert(txoutAddr);
}
}
if (grouping.size() > 0)
{
groupings.insert(grouping);
grouping.clear();
}
}
// group lone addrs by themselves
for (unsigned int i = 0; i < pcoin->vout.size(); i++)
if (IsMine(pcoin->vout[i]))
{
CTxDestination address;
if(!ExtractDestination(pcoin->vout[i].scriptPubKey, address))
continue;
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
set< set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
map< CTxDestination, set<CTxDestination>* > setmap; // map addresses to the unique group containing it
BOOST_FOREACH(set<CTxDestination> grouping, groupings)
{
// make a set of all the groups hit by this new group
set< set<CTxDestination>* > hits;
map< CTxDestination, set<CTxDestination>* >::iterator it;
BOOST_FOREACH(CTxDestination address, grouping)
if ((it = setmap.find(address)) != setmap.end())
hits.insert((*it).second);
// merge all hit groups into a new single group and delete old groups
set<CTxDestination>* merged = new set<CTxDestination>(grouping);
BOOST_FOREACH(set<CTxDestination>* hit, hits)
{
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// update setmap
BOOST_FOREACH(CTxDestination element, *merged)
setmap[element] = merged;
}
set< set<CTxDestination> > ret;
BOOST_FOREACH(set<CTxDestination>* uniqueGrouping, uniqueGroupings)
{
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
set<CTxDestination> CWallet::GetAccountAddresses(string strAccount) const
{
LOCK(cs_wallet);
set<CTxDestination> result;
BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& item, mapAddressBook)
{
const CTxDestination& address = item.first;
const string& strName = item.second.name;
if (strName == strAccount)
result.insert(address);
}
return result;
}
bool CReserveKey::GetReservedKey(CPubKey& pubkey)
{
if (nIndex == -1)
{
CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool);
if (nIndex != -1)
vchPubKey = keypool.vchPubKey;
else {
return false;
}
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
return true;
}
void CReserveKey::KeepKey()
{
if (nIndex != -1)
pwallet->KeepKey(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
}
void CReserveKey::ReturnKey()
{
if (nIndex != -1)
pwallet->ReturnKey(nIndex);
nIndex = -1;
vchPubKey = CPubKey();
}
void CWallet::GetAllReserveKeys(set<CKeyID>& setAddress) const
{
setAddress.clear();
CWalletDB walletdb(strWalletFile);
LOCK2(cs_main, cs_wallet);
BOOST_FOREACH(const int64_t& id, setKeyPool)
{
CKeyPool keypool;
if (!walletdb.ReadPool(id, keypool))
throw runtime_error("GetAllReserveKeyHashes(): read failed");
assert(keypool.vchPubKey.IsValid());
CKeyID keyID = keypool.vchPubKey.GetID();
if (!HaveKey(keyID))
throw runtime_error("GetAllReserveKeyHashes(): unknown key in key pool");
setAddress.insert(keyID);
}
}
void CWallet::UpdatedTransaction(const uint256 &hashTx)
{
{
LOCK(cs_wallet);
// Only notify UI if this transaction is in this wallet
map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
if (mi != mapWallet.end())
NotifyTransactionChanged(this, hashTx, CT_UPDATED);
}
}
void CWallet::LockCoin(COutPoint& output)
{
AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.insert(output);
}
void CWallet::UnlockCoin(COutPoint& output)
{
AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.erase(output);
}
void CWallet::UnlockAllCoins()
{
AssertLockHeld(cs_wallet); // setLockedCoins
setLockedCoins.clear();
}
bool CWallet::IsLockedCoin(uint256 hash, unsigned int n) const
{
AssertLockHeld(cs_wallet); // setLockedCoins
COutPoint outpt(hash, n);
return (setLockedCoins.count(outpt) > 0);
}
void CWallet::ListLockedCoins(std::vector<COutPoint>& vOutpts)
{
AssertLockHeld(cs_wallet); // setLockedCoins
for (std::set<COutPoint>::iterator it = setLockedCoins.begin();
it != setLockedCoins.end(); it++) {
COutPoint outpt = (*it);
vOutpts.push_back(outpt);
}
}
/** @} */ // end of Actions
class CAffectedKeysVisitor : public boost::static_visitor<void> {
private:
const CKeyStore &keystore;
std::vector<CKeyID> &vKeys;
public:
CAffectedKeysVisitor(const CKeyStore &keystoreIn, std::vector<CKeyID> &vKeysIn) : keystore(keystoreIn), vKeys(vKeysIn) {}
void Process(const CScript &script) {
txnouttype type;
std::vector<CTxDestination> vDest;
int nRequired;
if (ExtractDestinations(script, type, vDest, nRequired)) {
BOOST_FOREACH(const CTxDestination &dest, vDest)
boost::apply_visitor(*this, dest);
}
}
void operator()(const CKeyID &keyId) {
if (keystore.HaveKey(keyId))
vKeys.push_back(keyId);
}
void operator()(const CScriptID &scriptId) {
CScript script;
if (keystore.GetCScript(scriptId, script))
Process(script);
}
void operator()(const CNoDestination &none) {}
};
void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
AssertLockHeld(cs_wallet); // mapKeyMetadata
mapKeyBirth.clear();
// get birth times for keys with metadata
for (std::map<CKeyID, CKeyMetadata>::const_iterator it = mapKeyMetadata.begin(); it != mapKeyMetadata.end(); it++)
if (it->second.nCreateTime)
mapKeyBirth[it->first] = it->second.nCreateTime;
// map in which we'll infer heights of other keys
CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganised; use a 144-block safety margin
std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock;
std::set<CKeyID> setKeys;
GetKeys(setKeys);
BOOST_FOREACH(const CKeyID &keyid, setKeys) {
if (mapKeyBirth.count(keyid) == 0)
mapKeyFirstBlock[keyid] = pindexMax;
}
setKeys.clear();
// if there are no such keys, we're done
if (mapKeyFirstBlock.empty())
return;
// find first block that affects those keys, if there are any left
std::vector<CKeyID> vAffected;
for (std::map<uint256, CWalletTx>::const_iterator it = mapWallet.begin(); it != mapWallet.end(); it++) {
// iterate over all wallet transactions...
const CWalletTx &wtx = (*it).second;
BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
// ... which are already in a block
int nHeight = blit->second->nHeight;
BOOST_FOREACH(const CTxOut &txout, wtx.vout) {
// iterate over all their outputs
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
BOOST_FOREACH(const CKeyID &keyid, vAffected) {
// ... and all their affected keys
std::map<CKeyID, CBlockIndex*>::iterator rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() && nHeight < rit->second->nHeight)
rit->second = blit->second;
}
vAffected.clear();
}
}
}
// Extract block timestamps for those keys
for (std::map<CKeyID, CBlockIndex*>::const_iterator it = mapKeyFirstBlock.begin(); it != mapKeyFirstBlock.end(); it++)
mapKeyBirth[it->first] = it->second->GetBlockTime() - 7200; // block times can be 2h off
}
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
if (boost::get<CNoDestination>(&dest))
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).WriteDestData(CBitcoinAddress(dest).ToString(), key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key)
{
if (!mapAddressBook[dest].destdata.erase(key))
return false;
if (!fFileBacked)
return true;
return CWalletDB(strWalletFile).EraseDestData(CBitcoinAddress(dest).ToString(), key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
{
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return true;
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const
{
std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest);
if(i != mapAddressBook.end())
{
CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key);
if(j != i->second.destdata.end())
{
if(value)
*value = j->second;
return true;
}
}
return false;
}
CKeyPool::CKeyPool()
{
nTime = GetTime();
}
CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn)
{
nTime = GetTime();
vchPubKey = vchPubKeyIn;
}
CWalletKey::CWalletKey(int64_t nExpires)
{
nTimeCreated = (nExpires ? GetTime() : 0);
nTimeExpires = nExpires;
}
int CMerkleTx::SetMerkleBranch(const CBlock& block)
{
AssertLockHeld(cs_main);
CBlock blockTmp;
// Update the tx's hashBlock
hashBlock = block.GetHash();
// Locate the transaction
for (nIndex = 0; nIndex < (int)block.vtx.size(); nIndex++)
if (block.vtx[nIndex] == *(CTransaction*)this)
break;
if (nIndex == (int)block.vtx.size())
{
vMerkleBranch.clear();
nIndex = -1;
LogPrintf("ERROR: SetMerkleBranch(): couldn't find tx in block\n");
return 0;
}
// Fill in merkle branch
vMerkleBranch = block.GetMerkleBranch(nIndex);
// Is the tx in a block that's in the main chain
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi == mapBlockIndex.end())
return 0;
const CBlockIndex* pindex = (*mi).second;
if (!pindex || !chainActive.Contains(pindex))
return 0;
return chainActive.Height() - pindex->nHeight + 1;
}
int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
{
if (hashBlock.IsNull() || nIndex == -1)
return 0;
AssertLockHeld(cs_main);
// Find the block it claims to be in
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi == mapBlockIndex.end())
return 0;
CBlockIndex* pindex = (*mi).second;
if (!pindex || !chainActive.Contains(pindex))
return 0;
// Make sure the merkle branch connects to this block
if (!fMerkleVerified)
{
if (CBlock::CheckMerkleBranch(GetHash(), vMerkleBranch, nIndex) != pindex->hashMerkleRoot)
return 0;
fMerkleVerified = true;
}
pindexRet = pindex;
return chainActive.Height() - pindex->nHeight + 1;
}
int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
{
AssertLockHeld(cs_main);
int nResult = GetDepthInMainChainINTERNAL(pindexRet);
if (nResult == 0 && !mempool.exists(GetHash()))
return -1; // Not in chain, not in mempool
return nResult;
}
int CMerkleTx::GetBlocksToMaturity() const
{
if (!IsCoinBase())
return 0;
return max(0, (COINBASE_MATURITY+1) - GetDepthInMainChain());
}
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
{
CValidationState state;
return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee);
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 1a9feff9f3..f00fb0caff 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,912 +1,912 @@
// 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;
//! Flush wallet (bitdb flush)
void Flush(bool shutdown=false);
//! Verify the wallet database and perform salvage if required
- static bool Verify(const std::string walletFile, std::string& warningString, std::string& errorString);
+ static bool Verify(const std::string& walletFile, std::string& warningString, std::string& errorString);
/**
* 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
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 880026d187..d7f70e435e 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,986 +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)
+bool CWalletDB::Recover(CDBEnv& dbenv, const 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)
+bool CWalletDB::Recover(CDBEnv& dbenv, const 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)));
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 6aa0fa40df..a1c38b9d3d 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,142 +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);
+ static bool Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKeys);
+ static bool Recover(CDBEnv& dbenv, const 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

File Metadata

Mime Type
text/x-diff
Expires
Sun, Apr 27, 10:17 (19 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5573221
Default Alt Text
(179 KB)

Event Timeline