aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/db.cpp181
-rw-r--r--src/wallet/db.h71
-rw-r--r--src/wallet/feebumper.cpp284
-rw-r--r--src/wallet/feebumper.h56
-rw-r--r--src/wallet/rpcdump.cpp27
-rw-r--r--src/wallet/rpcwallet.cpp310
-rw-r--r--src/wallet/test/wallet_test_fixture.cpp3
-rw-r--r--src/wallet/test/wallet_tests.cpp13
-rw-r--r--src/wallet/wallet.cpp577
-rw-r--r--src/wallet/wallet.h120
-rw-r--r--src/wallet/walletdb.cpp121
-rw-r--r--src/wallet/walletdb.h43
12 files changed, 1072 insertions, 734 deletions
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 0801bd7300..25f6bdd9d9 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -6,6 +6,7 @@
#include "db.h"
#include "addrman.h"
+#include "fs.h"
#include "hash.h"
#include "protocol.h"
#include "util.h"
@@ -17,7 +18,6 @@
#include <sys/stat.h>
#endif
-#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
#include <boost/version.hpp>
@@ -66,7 +66,7 @@ void CDBEnv::Close()
EnvShutdown();
}
-bool CDBEnv::Open(const boost::filesystem::path& pathIn)
+bool CDBEnv::Open(const fs::path& pathIn)
{
if (fDbEnvInit)
return true;
@@ -74,9 +74,9 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
boost::this_thread::interruption_point();
strPath = pathIn.string();
- boost::filesystem::path pathLogDir = pathIn / "database";
+ fs::path pathLogDir = pathIn / "database";
TryCreateDirectory(pathLogDir);
- boost::filesystem::path pathErrorFile = pathIn / "db.log";
+ fs::path pathErrorFile = pathIn / "db.log";
LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
unsigned int nEnvFlags = 0;
@@ -89,7 +89,7 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
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_errfile(fsbridge::fopen(pathErrorFile, "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);
@@ -118,7 +118,7 @@ void CDBEnv::MakeMock()
boost::this_thread::interruption_point();
- LogPrint("db", "CDBEnv::MakeMock\n");
+ LogPrint(BCLog::DB, "CDBEnv::MakeMock\n");
dbenv->set_cachesize(1, 0, 1);
dbenv->set_lg_bsize(10485760 * 4);
@@ -227,13 +227,13 @@ bool CDB::Recover(const std::string& filename, void *callbackDataIn, bool (*reco
return fSuccess;
}
-bool CDB::VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr)
+bool CDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr)
{
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
LogPrintf("Using wallet %s\n", walletFile);
// Wallet file must be a plain filename without a directory
- if (walletFile != boost::filesystem::basename(walletFile) + boost::filesystem::extension(walletFile))
+ if (walletFile != fs::basename(walletFile) + fs::extension(walletFile))
{
errorStr = strprintf(_("Wallet %s resides outside data directory %s"), walletFile, dataDir.string());
return false;
@@ -242,12 +242,12 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const boost::filesyst
if (!bitdb.Open(dataDir))
{
// try moving the database env out of the way
- boost::filesystem::path pathDatabase = dataDir / "database";
- boost::filesystem::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime());
+ fs::path pathDatabase = dataDir / "database";
+ fs::path pathDatabaseBak = dataDir / strprintf("database.%d.bak", GetTime());
try {
- boost::filesystem::rename(pathDatabase, pathDatabaseBak);
+ fs::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(), pathDatabaseBak.string());
- } catch (const boost::filesystem::filesystem_error&) {
+ } catch (const fs::filesystem_error&) {
// failure is ok (well, not really, but it's not worse than what we started with)
}
@@ -261,9 +261,9 @@ bool CDB::VerifyEnvironment(const std::string& walletFile, const boost::filesyst
return true;
}
-bool CDB::VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile))
+bool CDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile))
{
- if (boost::filesystem::exists(dataDir / walletFile))
+ if (fs::exists(dataDir / walletFile))
{
CDBEnv::VerifyResult r = bitdb.Verify(walletFile, recoverFunc);
if (r == CDBEnv::RECOVER_OK)
@@ -359,13 +359,16 @@ void CDBEnv::CheckpointLSN(const std::string& strFile)
}
-CDB::CDB(const std::string& strFilename, const char* pszMode, bool fFlushOnCloseIn) : pdb(NULL), activeTxn(NULL)
+CDB::CDB(CWalletDBWrapper& dbw, const char* pszMode, bool fFlushOnCloseIn) : pdb(NULL), activeTxn(NULL)
{
int ret;
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
fFlushOnClose = fFlushOnCloseIn;
- if (strFilename.empty())
+ env = dbw.env;
+ if (dbw.IsDummy()) {
return;
+ }
+ const std::string &strFilename = dbw.strFile;
bool fCreate = strchr(pszMode, 'c') != NULL;
unsigned int nFlags = DB_THREAD;
@@ -373,17 +376,17 @@ CDB::CDB(const std::string& strFilename, const char* pszMode, bool fFlushOnClose
nFlags |= DB_CREATE;
{
- LOCK(bitdb.cs_db);
- if (!bitdb.Open(GetDataDir()))
+ LOCK(env->cs_db);
+ if (!env->Open(GetDataDir()))
throw std::runtime_error("CDB: Failed to open database environment.");
strFile = strFilename;
- ++bitdb.mapFileUseCount[strFile];
- pdb = bitdb.mapDb[strFile];
+ ++env->mapFileUseCount[strFile];
+ pdb = env->mapDb[strFile];
if (pdb == NULL) {
- pdb = new Db(bitdb.dbenv, 0);
+ pdb = new Db(env->dbenv, 0);
- bool fMockDb = bitdb.IsMock();
+ bool fMockDb = env->IsMock();
if (fMockDb) {
DbMpoolFile* mpf = pdb->get_mpf();
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
@@ -401,7 +404,7 @@ CDB::CDB(const std::string& strFilename, const char* pszMode, bool fFlushOnClose
if (ret != 0) {
delete pdb;
pdb = NULL;
- --bitdb.mapFileUseCount[strFile];
+ --env->mapFileUseCount[strFile];
strFile = "";
throw std::runtime_error(strprintf("CDB: Error %d, can't open database %s", ret, strFilename));
}
@@ -413,7 +416,7 @@ CDB::CDB(const std::string& strFilename, const char* pszMode, bool fFlushOnClose
fReadOnly = fTmp;
}
- bitdb.mapDb[strFile] = pdb;
+ env->mapDb[strFile] = pdb;
}
}
}
@@ -428,7 +431,7 @@ void CDB::Flush()
if (fReadOnly)
nMinutes = 1;
- bitdb.dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
+ env->dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
}
void CDB::Close()
@@ -444,8 +447,8 @@ void CDB::Close()
Flush();
{
- LOCK(bitdb.cs_db);
- --bitdb.mapFileUseCount[strFile];
+ LOCK(env->cs_db);
+ --env->mapFileUseCount[strFile];
}
}
@@ -463,32 +466,28 @@ void CDBEnv::CloseDb(const std::string& strFile)
}
}
-bool CDBEnv::RemoveDb(const std::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 std::string& strFile, const char* pszSkip)
+bool CDB::Rewrite(CWalletDBWrapper& dbw, const char* pszSkip)
{
+ if (dbw.IsDummy()) {
+ return true;
+ }
+ CDBEnv *env = dbw.env;
+ const std::string& strFile = dbw.strFile;
while (true) {
{
- LOCK(bitdb.cs_db);
- if (!bitdb.mapFileUseCount.count(strFile) || bitdb.mapFileUseCount[strFile] == 0) {
+ LOCK(env->cs_db);
+ if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
- bitdb.CloseDb(strFile);
- bitdb.CheckpointLSN(strFile);
- bitdb.mapFileUseCount.erase(strFile);
+ env->CloseDb(strFile);
+ env->CheckpointLSN(strFile);
+ env->mapFileUseCount.erase(strFile);
bool fSuccess = true;
LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile);
std::string strFileRes = strFile + ".rewrite";
{ // surround usage of db with extra {}
- CDB db(strFile.c_str(), "r");
- Db* pdbCopy = new Db(bitdb.dbenv, 0);
+ CDB db(dbw, "r");
+ Db* pdbCopy = new Db(env->dbenv, 0);
int ret = pdbCopy->open(NULL, // Txn pointer
strFileRes.c_str(), // Filename
@@ -531,17 +530,17 @@ bool CDB::Rewrite(const std::string& strFile, const char* pszSkip)
}
if (fSuccess) {
db.Close();
- bitdb.CloseDb(strFile);
+ env->CloseDb(strFile);
if (pdbCopy->close(0))
fSuccess = false;
delete pdbCopy;
}
}
if (fSuccess) {
- Db dbA(bitdb.dbenv, 0);
+ Db dbA(env->dbenv, 0);
if (dbA.remove(strFile.c_str(), NULL, 0))
fSuccess = false;
- Db dbB(bitdb.dbenv, 0);
+ Db dbB(env->dbenv, 0);
if (dbB.rename(strFileRes.c_str(), NULL, strFile.c_str(), 0))
fSuccess = false;
}
@@ -560,7 +559,7 @@ 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");
+ LogPrint(BCLog::DB, "CDBEnv::Flush: Flush(%s)%s\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit)
return;
{
@@ -569,43 +568,48 @@ void CDBEnv::Flush(bool fShutdown)
while (mi != mapFileUseCount.end()) {
std::string strFile = (*mi).first;
int nRefCount = (*mi).second;
- LogPrint("db", "CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile, nRefCount);
+ LogPrint(BCLog::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);
+ LogPrint(BCLog::DB, "CDBEnv::Flush: %s checkpoint\n", strFile);
dbenv->txn_checkpoint(0, 0, 0);
- LogPrint("db", "CDBEnv::Flush: %s detach\n", strFile);
+ LogPrint(BCLog::DB, "CDBEnv::Flush: %s detach\n", strFile);
if (!fMockDb)
dbenv->lsn_reset(strFile.c_str(), 0);
- LogPrint("db", "CDBEnv::Flush: %s closed\n", strFile);
+ LogPrint(BCLog::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);
+ LogPrint(BCLog::DB, "CDBEnv::Flush: Flush(%s)%s took %15dms\n", fShutdown ? "true" : "false", fDbEnvInit ? "" : " database not started", GetTimeMillis() - nStart);
if (fShutdown) {
char** listp;
if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb)
- boost::filesystem::remove_all(boost::filesystem::path(strPath) / "database");
+ fs::remove_all(fs::path(strPath) / "database");
}
}
}
}
-bool CDB::PeriodicFlush(std::string strFile)
+bool CDB::PeriodicFlush(CWalletDBWrapper& dbw)
{
+ if (dbw.IsDummy()) {
+ return true;
+ }
bool ret = false;
+ CDBEnv *env = dbw.env;
+ const std::string& strFile = dbw.strFile;
TRY_LOCK(bitdb.cs_db,lockDb);
if (lockDb)
{
// Don't do this if any databases are in use
int nRefCount = 0;
- std::map<std::string, int>::iterator mit = bitdb.mapFileUseCount.begin();
- while (mit != bitdb.mapFileUseCount.end())
+ std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin();
+ while (mit != env->mapFileUseCount.end())
{
nRefCount += (*mit).second;
mit++;
@@ -614,18 +618,18 @@ bool CDB::PeriodicFlush(std::string strFile)
if (nRefCount == 0)
{
boost::this_thread::interruption_point();
- std::map<std::string, int>::iterator mi = bitdb.mapFileUseCount.find(strFile);
- if (mi != bitdb.mapFileUseCount.end())
+ std::map<std::string, int>::iterator mi = env->mapFileUseCount.find(strFile);
+ if (mi != env->mapFileUseCount.end())
{
- LogPrint("db", "Flushing %s\n", strFile);
+ LogPrint(BCLog::DB, "Flushing %s\n", strFile);
int64_t nStart = GetTimeMillis();
// Flush wallet file so it's self contained
- bitdb.CloseDb(strFile);
- bitdb.CheckpointLSN(strFile);
+ env->CloseDb(strFile);
+ env->CheckpointLSN(strFile);
- bitdb.mapFileUseCount.erase(mi++);
- LogPrint("db", "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
+ env->mapFileUseCount.erase(mi++);
+ LogPrint(BCLog::DB, "Flushed %s %dms\n", strFile, GetTimeMillis() - nStart);
ret = true;
}
}
@@ -633,3 +637,52 @@ bool CDB::PeriodicFlush(std::string strFile)
return ret;
}
+
+bool CWalletDBWrapper::Rewrite(const char* pszSkip)
+{
+ return CDB::Rewrite(*this, pszSkip);
+}
+
+bool CWalletDBWrapper::Backup(const std::string& strDest)
+{
+ if (IsDummy()) {
+ return false;
+ }
+ while (true)
+ {
+ {
+ LOCK(env->cs_db);
+ if (!env->mapFileUseCount.count(strFile) || env->mapFileUseCount[strFile] == 0)
+ {
+ // Flush log data to the dat file
+ env->CloseDb(strFile);
+ env->CheckpointLSN(strFile);
+ env->mapFileUseCount.erase(strFile);
+
+ // Copy wallet file
+ fs::path pathSrc = GetDataDir() / strFile;
+ fs::path pathDest(strDest);
+ if (fs::is_directory(pathDest))
+ pathDest /= strFile;
+
+ try {
+ fs::copy_file(pathSrc, pathDest, fs::copy_option::overwrite_if_exists);
+ LogPrintf("copied %s to %s\n", strFile, pathDest.string());
+ return true;
+ } catch (const fs::filesystem_error& e) {
+ LogPrintf("error copying %s to %s - %s\n", strFile, pathDest.string(), e.what());
+ return false;
+ }
+ }
+ }
+ MilliSleep(100);
+ }
+ return false;
+}
+
+void CWalletDBWrapper::Flush(bool shutdown)
+{
+ if (!IsDummy()) {
+ env->Flush(shutdown);
+ }
+}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 19c54e314c..1a46448cc7 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -7,6 +7,7 @@
#define BITCOIN_WALLET_DB_H
#include "clientversion.h"
+#include "fs.h"
#include "serialize.h"
#include "streams.h"
#include "sync.h"
@@ -16,8 +17,6 @@
#include <string>
#include <vector>
-#include <boost/filesystem/path.hpp>
-
#include <db_cxx.h>
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
@@ -28,7 +27,7 @@ class CDBEnv
private:
bool fDbEnvInit;
bool fMockDb;
- // Don't change into boost::filesystem::path, as that can result in
+ // Don't change into fs::path, as that can result in
// shutdown problems/crashes caused by a static initialized internal pointer.
std::string strPath;
@@ -67,13 +66,12 @@ public:
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
- bool Open(const boost::filesystem::path& path);
+ bool Open(const fs::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)
{
@@ -87,6 +85,52 @@ public:
extern CDBEnv bitdb;
+/** An instance of this class represents one database.
+ * For BerkeleyDB this is just a (env, strFile) tuple.
+ **/
+class CWalletDBWrapper
+{
+ friend class CDB;
+public:
+ /** Create dummy DB handle */
+ CWalletDBWrapper(): env(nullptr)
+ {
+ }
+
+ /** Create DB handle to real database */
+ CWalletDBWrapper(CDBEnv *env_in, const std::string &strFile_in):
+ env(env_in), strFile(strFile_in)
+ {
+ }
+
+ /** Rewrite the entire database on disk, with the exception of key pszSkip if non-zero
+ */
+ bool Rewrite(const char* pszSkip=nullptr);
+
+ /** Back up the entire database to a file.
+ */
+ bool Backup(const std::string& strDest);
+
+ /** Get a name for this database, for debugging etc.
+ */
+ std::string GetName() const { return strFile; }
+
+ /** Make sure all changes are flushed to disk.
+ */
+ void Flush(bool shutdown);
+
+private:
+ /** BerkeleyDB specific */
+ CDBEnv *env;
+ std::string strFile;
+
+ /** Return whether this database handle is a dummy for testing.
+ * Only to be used at a low level, application should ideally not care
+ * about this.
+ */
+ bool IsDummy() { return env == nullptr; }
+};
+
/** RAII class that provides access to a Berkeley database */
class CDB
@@ -97,28 +141,29 @@ protected:
DbTxn* activeTxn;
bool fReadOnly;
bool fFlushOnClose;
+ CDBEnv *env;
- explicit CDB(const std::string& strFilename, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
+public:
+ explicit CDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool fFlushOnCloseIn=true);
~CDB() { Close(); }
-public:
void Flush();
void Close();
static bool Recover(const std::string& filename, void *callbackDataIn, bool (*recoverKVcallback)(void* callbackData, CDataStream ssKey, CDataStream ssValue));
/* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */
- static bool PeriodicFlush(std::string strFile);
+ static bool PeriodicFlush(CWalletDBWrapper& dbw);
/* verifies the database environment */
- static bool VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr);
+ static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr);
/* verifies the database file */
- static bool VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile));
+ static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr, bool (*recoverFunc)(const std::string& strFile));
private:
CDB(const CDB&);
void operator=(const CDB&);
-protected:
+public:
template <typename K, typename T>
bool Read(const K& key, T& value)
{
@@ -157,7 +202,7 @@ protected:
bool Write(const K& key, const T& value, bool fOverwrite = true)
{
if (!pdb)
- return false;
+ return true;
if (fReadOnly)
assert(!"Write called on database in read-only mode");
@@ -311,7 +356,7 @@ public:
return Write(std::string("version"), nVersion);
}
- bool static Rewrite(const std::string& strFile, const char* pszSkip = NULL);
+ bool static Rewrite(CWalletDBWrapper& dbw, const char* pszSkip = NULL);
};
#endif // BITCOIN_WALLET_DB_H
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
new file mode 100644
index 0000000000..f5b63c1ecd
--- /dev/null
+++ b/src/wallet/feebumper.cpp
@@ -0,0 +1,284 @@
+// Copyright (c) 2017 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 "consensus/validation.h"
+#include "wallet/feebumper.h"
+#include "wallet/wallet.h"
+#include "policy/fees.h"
+#include "policy/policy.h"
+#include "policy/rbf.h"
+#include "validation.h" //for mempool access
+#include "txmempool.h"
+#include "utilmoneystr.h"
+#include "util.h"
+#include "net.h"
+
+// Calculate the size of the transaction assuming all signatures are max size
+// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
+// TODO: re-use this in CWallet::CreateTransaction (right now
+// CreateTransaction uses the constructed dummy-signed tx to do a priority
+// calculation, but we should be able to refactor after priority is removed).
+// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
+// be IsAllFromMe).
+int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *pWallet)
+{
+ CMutableTransaction txNew(tx);
+ std::vector<CInputCoin> vCoins;
+ // Look up the inputs. We should have already checked that this transaction
+ // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
+ // wallet, with a valid index into the vout array.
+ for (auto& input : tx.vin) {
+ const auto mi = pWallet->mapWallet.find(input.prevout.hash);
+ assert(mi != pWallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
+ vCoins.emplace_back(CInputCoin(&(mi->second), input.prevout.n));
+ }
+ if (!pWallet->DummySignTx(txNew, vCoins)) {
+ // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
+ // implies that we can sign for every input.
+ return -1;
+ }
+ return GetVirtualTransactionSize(txNew);
+}
+
+CFeeBumper::CFeeBumper(const CWallet *pWallet, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable)
+ :
+ txid(std::move(txidIn)),
+ nOldFee(0),
+ nNewFee(0)
+{
+ vErrors.clear();
+ bumpedTxid.SetNull();
+ AssertLockHeld(pWallet->cs_wallet);
+ if (!pWallet->mapWallet.count(txid)) {
+ vErrors.push_back("Invalid or non-wallet transaction id");
+ currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
+ return;
+ }
+ auto it = pWallet->mapWallet.find(txid);
+ const CWalletTx& wtx = it->second;
+
+ if (pWallet->HasWalletSpend(txid)) {
+ vErrors.push_back("Transaction has descendants in the wallet");
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+
+ {
+ LOCK(mempool.cs);
+ auto it_mp = mempool.mapTx.find(txid);
+ if (it_mp != mempool.mapTx.end() && it_mp->GetCountWithDescendants() > 1) {
+ vErrors.push_back("Transaction has descendants in the mempool");
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+ }
+
+ if (wtx.GetDepthInMainChain() != 0) {
+ vErrors.push_back("Transaction has been mined, or is conflicted with a mined transaction");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ if (!SignalsOptInRBF(wtx)) {
+ vErrors.push_back("Transaction is not BIP 125 replaceable");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ if (wtx.mapValue.count("replaced_by_txid")) {
+ vErrors.push_back(strprintf("Cannot bump transaction %s which was already bumped by transaction %s", txid.ToString(), wtx.mapValue.at("replaced_by_txid")));
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // check that original tx consists entirely of our inputs
+ // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
+ if (!pWallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
+ vErrors.push_back("Transaction contains inputs that don't belong to this wallet");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // figure out which output was change
+ // if there was no change output or multiple change outputs, fail
+ int nOutput = -1;
+ for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
+ if (pWallet->IsChange(wtx.tx->vout[i])) {
+ if (nOutput != -1) {
+ vErrors.push_back("Transaction has multiple change outputs");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+ nOutput = i;
+ }
+ }
+ if (nOutput == -1) {
+ vErrors.push_back("Transaction does not have a change output");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // Calculate the expected size of the new transaction.
+ int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
+ const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, pWallet);
+ if (maxNewTxSize < 0) {
+ vErrors.push_back("Transaction contains inputs that cannot be signed");
+ currentResult = BumpFeeResult::INVALID_ADDRESS_OR_KEY;
+ return;
+ }
+
+ // calculate the old fee and fee-rate
+ nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
+ CFeeRate nOldFeeRate(nOldFee, txSize);
+ CFeeRate nNewFeeRate;
+ // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
+ // future proof against changes to network wide policy for incremental relay
+ // fee that our node may not be aware of.
+ CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
+ if (::incrementalRelayFee > walletIncrementalRelayFee) {
+ walletIncrementalRelayFee = ::incrementalRelayFee;
+ }
+
+ if (totalFee > 0) {
+ CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize);
+ if (totalFee < minTotalFee) {
+ vErrors.push_back(strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
+ FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+ CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize);
+ if (totalFee < requiredFee) {
+ vErrors.push_back(strprintf("Insufficient totalFee (cannot be less than required fee %s)",
+ FormatMoney(requiredFee)));
+ currentResult = BumpFeeResult::INVALID_PARAMETER;
+ return;
+ }
+ nNewFee = totalFee;
+ nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
+ } else {
+ // if user specified a confirm target then don't consider any global payTxFee
+ if (specifiedConfirmTarget) {
+ nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator, true);
+ }
+ // otherwise use the regular wallet logic to select payTxFee or default confirm target
+ else {
+ nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, ::feeEstimator);
+ }
+
+ nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
+
+ // New fee rate must be at least old rate + minimum incremental relay rate
+ // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
+ // in that unit (fee per kb).
+ // However, nOldFeeRate is a calculated value from the tx fee/size, so
+ // add 1 satoshi to the result, because it may have been rounded down.
+ if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
+ nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
+ nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
+ }
+ }
+
+ // Check that in all cases the new fee doesn't violate maxTxFee
+ if (nNewFee > maxTxFee) {
+ vErrors.push_back(strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
+ FormatMoney(nNewFee), FormatMoney(maxTxFee)));
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // check that fee rate is higher than mempool's minimum fee
+ // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
+ // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
+ // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
+ // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment.
+ CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
+ if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
+ vErrors.push_back(strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK())));
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // Now modify the output to increase the fee.
+ // If the output is not large enough to pay the fee, fail.
+ CAmount nDelta = nNewFee - nOldFee;
+ assert(nDelta > 0);
+ mtx = *wtx.tx;
+ CTxOut* poutput = &(mtx.vout[nOutput]);
+ if (poutput->nValue < nDelta) {
+ vErrors.push_back("Change output is too small to bump the fee");
+ currentResult = BumpFeeResult::WALLET_ERROR;
+ return;
+ }
+
+ // If the output would become dust, discard it (converting the dust to fee)
+ poutput->nValue -= nDelta;
+ if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) {
+ LogPrint(BCLog::RPC, "Bumping fee and discarding dust output\n");
+ nNewFee += poutput->nValue;
+ mtx.vout.erase(mtx.vout.begin() + nOutput);
+ }
+
+ // Mark new tx not replaceable, if requested.
+ if (!newTxReplaceable) {
+ for (auto& input : mtx.vin) {
+ if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
+ }
+ }
+
+ currentResult = BumpFeeResult::OK;
+}
+
+bool CFeeBumper::signTransaction(CWallet *pWallet)
+{
+ return pWallet->SignTransaction(mtx);
+}
+
+bool CFeeBumper::commit(CWallet *pWallet)
+{
+ AssertLockHeld(pWallet->cs_wallet);
+ if (!vErrors.empty() || currentResult != BumpFeeResult::OK) {
+ return false;
+ }
+ if (txid.IsNull() || !pWallet->mapWallet.count(txid)) {
+ vErrors.push_back("Invalid or non-wallet transaction id");
+ currentResult = BumpFeeResult::MISC_ERROR;
+ return false;
+ }
+ CWalletTx& oldWtx = pWallet->mapWallet[txid];
+
+ CWalletTx wtxBumped(pWallet, MakeTransactionRef(std::move(mtx)));
+ // commit/broadcast the tx
+ CReserveKey reservekey(pWallet);
+ wtxBumped.mapValue = oldWtx.mapValue;
+ wtxBumped.mapValue["replaces_txid"] = oldWtx.GetHash().ToString();
+ wtxBumped.vOrderForm = oldWtx.vOrderForm;
+ wtxBumped.strFromAccount = oldWtx.strFromAccount;
+ wtxBumped.fTimeReceivedIsTxTime = true;
+ wtxBumped.fFromMe = true;
+ CValidationState state;
+ if (!pWallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
+ // NOTE: CommitTransaction never returns false, so this should never happen.
+ vErrors.push_back(strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
+ return false;
+ }
+
+ bumpedTxid = wtxBumped.GetHash();
+ if (state.IsInvalid()) {
+ // This can happen if the mempool rejected the transaction. Report
+ // what happened in the "errors" response.
+ vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
+ }
+
+ // mark the original tx as bumped
+ if (!pWallet->MarkReplaced(oldWtx.GetHash(), wtxBumped.GetHash())) {
+ // TODO: see if JSON-RPC has a standard way of returning a response
+ // along with an exception. It would be good to return information about
+ // wtxBumped to the caller even if marking the original transaction
+ // replaced does not succeed for some reason.
+ vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
+ }
+ return true;
+}
+
diff --git a/src/wallet/feebumper.h b/src/wallet/feebumper.h
new file mode 100644
index 0000000000..681f55e4e5
--- /dev/null
+++ b/src/wallet/feebumper.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2017 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_FEEBUMPER_H
+#define BITCOIN_WALLET_FEEBUMPER_H
+
+#include <primitives/transaction.h>
+
+class CWallet;
+class uint256;
+
+enum class BumpFeeResult
+{
+ OK,
+ INVALID_ADDRESS_OR_KEY,
+ INVALID_REQUEST,
+ INVALID_PARAMETER,
+ WALLET_ERROR,
+ MISC_ERROR,
+};
+
+class CFeeBumper
+{
+public:
+ CFeeBumper(const CWallet *pWalletIn, const uint256 txidIn, int newConfirmTarget, bool specifiedConfirmTarget, CAmount totalFee, bool newTxReplaceable);
+ BumpFeeResult getResult() const { return currentResult; }
+ const std::vector<std::string>& getErrors() const { return vErrors; }
+ CAmount getOldFee() const { return nOldFee; }
+ CAmount getNewFee() const { return nNewFee; }
+ uint256 getBumpedTxId() const { return bumpedTxid; }
+
+ /* signs the new transaction,
+ * returns false if the tx couldn't be found or if it was
+ * impossible to create the signature(s)
+ */
+ bool signTransaction(CWallet *pWallet);
+
+ /* commits the fee bump,
+ * returns true, in case of CWallet::CommitTransaction was successful
+ * but, eventually sets vErrors if the tx could not be added to the mempool (will try later)
+ * or if the old transaction could not be marked as replaced
+ */
+ bool commit(CWallet *pWalletNonConst);
+
+private:
+ const uint256 txid;
+ uint256 bumpedTxid;
+ CMutableTransaction mtx;
+ std::vector<std::string> vErrors;
+ BumpFeeResult currentResult;
+ CAmount nOldFee;
+ CAmount nNewFee;
+};
+
+#endif // BITCOIN_WALLET_FEEBUMPER_H
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 7ff9e7ae58..82708dab26 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -95,6 +95,8 @@ UniValue importprivkey(const JSONRPCRequest& request)
+ HelpExampleCli("importprivkey", "\"mykey\"") +
"\nImport using a label and without rescan\n"
+ HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
+ "\nImport using default blank label and without rescan\n"
+ + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
"\nAs a JSON-RPC call\n"
+ HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
);
@@ -154,6 +156,31 @@ UniValue importprivkey(const JSONRPCRequest& request)
return NullUniValue;
}
+UniValue abortrescan(const JSONRPCRequest& request)
+{
+ CWallet* const pwallet = GetWalletForJSONRPCRequest(request);
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() > 0)
+ throw std::runtime_error(
+ "abortrescan\n"
+ "\nStops current wallet rescan triggered e.g. by an importprivkey call.\n"
+ "\nExamples:\n"
+ "\nImport a private key\n"
+ + HelpExampleCli("importprivkey", "\"mykey\"") +
+ "\nAbort the running wallet rescan\n"
+ + HelpExampleCli("abortrescan", "") +
+ "\nAs a JSON-RPC call\n"
+ + HelpExampleRpc("abortrescan", "")
+ );
+
+ if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) return false;
+ pwallet->AbortRescan();
+ return true;
+}
+
void ImportAddress(CWallet*, const CBitcoinAddress& address, const std::string& strLabel);
void ImportScript(CWallet* const pwallet, const CScript& script, const std::string& strLabel, bool isRedeemScript)
{
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 22b1e3522c..d1e7485d04 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -9,8 +9,10 @@
#include "consensus/validation.h"
#include "core_io.h"
#include "init.h"
+#include "wallet/coincontrol.h"
#include "validation.h"
#include "net.h"
+#include "policy/fees.h"
#include "policy/policy.h"
#include "policy/rbf.h"
#include "rpc/server.h"
@@ -18,8 +20,9 @@
#include "timedata.h"
#include "util.h"
#include "utilmoneystr.h"
-#include "wallet.h"
-#include "walletdb.h"
+#include "wallet/feebumper.h"
+#include "wallet/wallet.h"
+#include "wallet/walletdb.h"
#include <stdint.h>
@@ -726,6 +729,8 @@ UniValue getbalance(const JSONRPCRequest& request)
if (request.params.size() == 0)
return ValueFromAmount(pwallet->GetBalance());
+ const std::string* account = request.params[0].get_str() != "*" ? &request.params[0].get_str() : nullptr;
+
int nMinDepth = 1;
if (request.params.size() > 1)
nMinDepth = request.params[1].get_int();
@@ -734,41 +739,7 @@ UniValue getbalance(const JSONRPCRequest& request)
if(request.params[2].get_bool())
filter = filter | ISMINE_WATCH_ONLY;
- if (request.params[0].get_str() == "*") {
- // Calculate total balance in a very different way from GetBalance().
- // The biggest difference is that GetBalance() sums up all unspent
- // TxOuts paying to the wallet, while this sums up both spent and
- // unspent TxOuts paying to the wallet, and then subtracts the values of
- // TxIns spending from the wallet. This also has fewer restrictions on
- // which unconfirmed transactions are considered trusted.
- CAmount nBalance = 0;
- for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) {
- const CWalletTx& wtx = pairWtx.second;
- if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0)
- continue;
-
- CAmount allFee;
- std::string strSentAccount;
- std::list<COutputEntry> listReceived;
- std::list<COutputEntry> listSent;
- wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter);
- if (wtx.GetDepthInMainChain() >= nMinDepth)
- {
- BOOST_FOREACH(const COutputEntry& r, listReceived)
- nBalance += r.amount;
- }
- BOOST_FOREACH(const COutputEntry& s, listSent)
- nBalance -= s.amount;
- nBalance -= allFee;
- }
- return ValueFromAmount(nBalance);
- }
-
- std::string strAccount = AccountFromValue(request.params[0]);
-
- CAmount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, filter);
-
- return ValueFromAmount(nBalance);
+ return ValueFromAmount(pwallet->GetLegacyBalance(filter, nMinDepth, account));
}
UniValue getunconfirmedbalance(const JSONRPCRequest &request)
@@ -898,7 +869,7 @@ UniValue sendfrom(const JSONRPCRequest& request)
EnsureWalletIsUnlocked(pwallet);
// Check funds
- CAmount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE);
+ CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount);
if (nAmount > nBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
@@ -1007,7 +978,7 @@ UniValue sendmany(const JSONRPCRequest& request)
EnsureWalletIsUnlocked(pwallet);
// Check funds
- CAmount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE);
+ CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount);
if (totalAmount > nBalance)
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds");
@@ -2075,7 +2046,7 @@ UniValue walletpassphrase(const JSONRPCRequest& request)
int64_t nSleepTime = request.params[1].get_int64();
pwallet->nRelockTime = GetTime() + nSleepTime;
- RPCRunLater(strprintf("lockwallet(%s)", pwallet->strWalletFile), boost::bind(LockWallet, pwallet), nSleepTime);
+ RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime);
return NullUniValue;
}
@@ -2778,33 +2749,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request)
return result;
}
-// Calculate the size of the transaction assuming all signatures are max size
-// Use DummySignatureCreator, which inserts 72 byte signatures everywhere.
-// TODO: re-use this in CWallet::CreateTransaction (right now
-// CreateTransaction uses the constructed dummy-signed tx to do a priority
-// calculation, but we should be able to refactor after priority is removed).
-// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
-// be IsAllFromMe).
-int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, CWallet &wallet)
-{
- CMutableTransaction txNew(tx);
- std::vector<std::pair<CWalletTx*, unsigned int>> vCoins;
- // Look up the inputs. We should have already checked that this transaction
- // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
- // wallet, with a valid index into the vout array.
- for (auto& input : tx.vin) {
- const auto mi = wallet.mapWallet.find(input.prevout.hash);
- assert(mi != wallet.mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
- vCoins.emplace_back(std::make_pair(&(mi->second), input.prevout.n));
- }
- if (!wallet.DummySignTx(txNew, vCoins)) {
- // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
- // implies that we can sign for every input.
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed");
- }
- return GetVirtualTransactionSize(txNew);
-}
-
UniValue bumpfee(const JSONRPCRequest& request)
{
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -2859,63 +2803,6 @@ UniValue bumpfee(const JSONRPCRequest& request)
uint256 hash;
hash.SetHex(request.params[0].get_str());
- // retrieve the original tx from the wallet
- LOCK2(cs_main, pwallet->cs_wallet);
- EnsureWalletIsUnlocked(pwallet);
- if (!pwallet->mapWallet.count(hash)) {
- throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
- }
- CWalletTx& wtx = pwallet->mapWallet[hash];
-
- if (pwallet->HasWalletSpend(hash)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction has descendants in the wallet");
- }
-
- {
- LOCK(mempool.cs);
- auto it = mempool.mapTx.find(hash);
- if (it != mempool.mapTx.end() && it->GetCountWithDescendants() > 1) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction has descendants in the mempool");
- }
- }
-
- if (wtx.GetDepthInMainChain() != 0) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction has been mined, or is conflicted with a mined transaction");
- }
-
- if (!SignalsOptInRBF(wtx)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction is not BIP 125 replaceable");
- }
-
- if (wtx.mapValue.count("replaced_by_txid")) {
- throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Cannot bump transaction %s which was already bumped by transaction %s", hash.ToString(), wtx.mapValue.at("replaced_by_txid")));
- }
-
- // check that original tx consists entirely of our inputs
- // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
- if (!pwallet->IsAllFromMe(wtx, ISMINE_SPENDABLE)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction contains inputs that don't belong to this wallet");
- }
-
- // figure out which output was change
- // if there was no change output or multiple change outputs, fail
- int nOutput = -1;
- for (size_t i = 0; i < wtx.tx->vout.size(); ++i) {
- if (pwallet->IsChange(wtx.tx->vout[i])) {
- if (nOutput != -1) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction has multiple change outputs");
- }
- nOutput = i;
- }
- }
- if (nOutput == -1) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Transaction does not have a change output");
- }
-
- // Calculate the expected size of the new transaction.
- int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
- const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx, *pwallet);
-
// optional parameters
bool specifiedConfirmTarget = false;
int newConfirmTarget = nTxConfirmTarget;
@@ -2941,11 +2828,8 @@ UniValue bumpfee(const JSONRPCRequest& request)
}
} else if (options.exists("totalFee")) {
totalFee = options["totalFee"].get_int64();
- CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize);
- if (totalFee < requiredFee ) {
- throw JSONRPCError(RPC_INVALID_PARAMETER,
- strprintf("Insufficient totalFee (cannot be less than required fee %s)",
- FormatMoney(requiredFee)));
+ if (totalFee <= 0) {
+ throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee)));
}
}
@@ -2954,148 +2838,53 @@ UniValue bumpfee(const JSONRPCRequest& request)
}
}
- // calculate the old fee and fee-rate
- CAmount nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut();
- CFeeRate nOldFeeRate(nOldFee, txSize);
- CAmount nNewFee;
- CFeeRate nNewFeeRate;
- // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to
- // future proof against changes to network wide policy for incremental relay
- // fee that our node may not be aware of.
- CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE);
- if (::incrementalRelayFee > walletIncrementalRelayFee) {
- walletIncrementalRelayFee = ::incrementalRelayFee;
- }
-
- if (totalFee > 0) {
- CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize);
- if (totalFee < minTotalFee) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)",
- FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize))));
- }
- nNewFee = totalFee;
- nNewFeeRate = CFeeRate(totalFee, maxNewTxSize);
- } else {
- // if user specified a confirm target then don't consider any global payTxFee
- if (specifiedConfirmTarget) {
- nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, CAmount(0));
- }
- // otherwise use the regular wallet logic to select payTxFee or default confirm target
- else {
- nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool);
- }
-
- nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize);
-
- // New fee rate must be at least old rate + minimum incremental relay rate
- // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized
- // in that unit (fee per kb).
- // However, nOldFeeRate is a calculated value from the tx fee/size, so
- // add 1 satoshi to the result, because it may have been rounded down.
- if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) {
- nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK());
- nNewFee = nNewFeeRate.GetFee(maxNewTxSize);
- }
- }
-
- // Check that in all cases the new fee doesn't violate maxTxFee
- if (nNewFee > maxTxFee) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)",
- FormatMoney(nNewFee), FormatMoney(maxTxFee)));
- }
-
- // check that fee rate is higher than mempool's minimum fee
- // (no point in bumping fee if we know that the new tx won't be accepted to the mempool)
- // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps,
- // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a
- // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment.
- CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000);
- if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) {
- throw JSONRPCError(RPC_WALLET_ERROR, strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK())));
- }
-
- // Now modify the output to increase the fee.
- // If the output is not large enough to pay the fee, fail.
- CAmount nDelta = nNewFee - nOldFee;
- assert(nDelta > 0);
- CMutableTransaction tx(*(wtx.tx));
- CTxOut* poutput = &(tx.vout[nOutput]);
- if (poutput->nValue < nDelta) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Change output is too small to bump the fee");
- }
-
- // If the output would become dust, discard it (converting the dust to fee)
- poutput->nValue -= nDelta;
- if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) {
- LogPrint("rpc", "Bumping fee and discarding dust output\n");
- nNewFee += poutput->nValue;
- tx.vout.erase(tx.vout.begin() + nOutput);
- }
-
- // Mark new tx not replaceable, if requested.
- if (!replaceable) {
- for (auto& input : tx.vin) {
- if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe;
- }
- }
+ LOCK2(cs_main, pwallet->cs_wallet);
+ EnsureWalletIsUnlocked(pwallet);
- // sign the new tx
- CTransaction txNewConst(tx);
- int nIn = 0;
- for (auto& input : tx.vin) {
- std::map<uint256, CWalletTx>::const_iterator mi = pwallet->mapWallet.find(input.prevout.hash);
- assert(mi != pwallet->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size());
- const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey;
- const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue;
- SignatureData sigdata;
- if (!ProduceSignature(TransactionSignatureCreator(pwallet, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
- throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
+ CFeeBumper feeBump(pwallet, hash, newConfirmTarget, specifiedConfirmTarget, totalFee, replaceable);
+ BumpFeeResult res = feeBump.getResult();
+ if (res != BumpFeeResult::OK)
+ {
+ switch(res) {
+ case BumpFeeResult::INVALID_ADDRESS_OR_KEY:
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, feeBump.getErrors()[0]);
+ break;
+ case BumpFeeResult::INVALID_REQUEST:
+ throw JSONRPCError(RPC_INVALID_REQUEST, feeBump.getErrors()[0]);
+ break;
+ case BumpFeeResult::INVALID_PARAMETER:
+ throw JSONRPCError(RPC_INVALID_PARAMETER, feeBump.getErrors()[0]);
+ break;
+ case BumpFeeResult::WALLET_ERROR:
+ throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]);
+ break;
+ default:
+ throw JSONRPCError(RPC_MISC_ERROR, feeBump.getErrors()[0]);
+ break;
}
- UpdateTransaction(tx, nIn, sigdata);
- nIn++;
}
- // commit/broadcast the tx
- CReserveKey reservekey(pwallet);
- CWalletTx wtxBumped(pwallet, MakeTransactionRef(std::move(tx)));
- wtxBumped.mapValue = wtx.mapValue;
- wtxBumped.mapValue["replaces_txid"] = hash.ToString();
- wtxBumped.vOrderForm = wtx.vOrderForm;
- wtxBumped.strFromAccount = wtx.strFromAccount;
- wtxBumped.fTimeReceivedIsTxTime = true;
- wtxBumped.fFromMe = true;
- CValidationState state;
- if (!pwallet->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) {
- // NOTE: CommitTransaction never returns false, so this should never happen.
- throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()));
+ // sign bumped transaction
+ if (!feeBump.signTransaction(pwallet)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction.");
}
-
- UniValue vErrors(UniValue::VARR);
- if (state.IsInvalid()) {
- // This can happen if the mempool rejected the transaction. Report
- // what happened in the "errors" response.
- vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state)));
+ // commit the bumped transaction
+ if(!feeBump.commit(pwallet)) {
+ throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]);
}
-
- // mark the original tx as bumped
- if (!pwallet->MarkReplaced(wtx.GetHash(), wtxBumped.GetHash())) {
- // TODO: see if JSON-RPC has a standard way of returning a response
- // along with an exception. It would be good to return information about
- // wtxBumped to the caller even if marking the original transaction
- // replaced does not succeed for some reason.
- vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced.");
- }
-
UniValue result(UniValue::VOBJ);
- result.push_back(Pair("txid", wtxBumped.GetHash().GetHex()));
- result.push_back(Pair("origfee", ValueFromAmount(nOldFee)));
- result.push_back(Pair("fee", ValueFromAmount(nNewFee)));
- result.push_back(Pair("errors", vErrors));
+ result.push_back(Pair("txid", feeBump.getBumpedTxId().GetHex()));
+ result.push_back(Pair("origfee", ValueFromAmount(feeBump.getOldFee())));
+ result.push_back(Pair("fee", ValueFromAmount(feeBump.getNewFee())));
+ UniValue errors(UniValue::VARR);
+ for (const std::string& err: feeBump.getErrors())
+ errors.push_back(err);
+ result.push_back(errors);
return result;
}
+extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue importprivkey(const JSONRPCRequest& request);
extern UniValue importaddress(const JSONRPCRequest& request);
@@ -3112,6 +2901,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, false, {"hexstring","options"} },
{ "hidden", "resendwallettransactions", &resendwallettransactions, true, {} },
{ "wallet", "abandontransaction", &abandontransaction, false, {"txid"} },
+ { "wallet", "abortrescan", &abortrescan, false, {} },
{ "wallet", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} },
{ "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address"} },
{ "wallet", "backupwallet", &backupwallet, true, {"destination"} },
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index a76db37617..1989bf8d9b 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -14,7 +14,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
bitdb.MakeMock();
bool fFirstRun;
- pwalletMain = new CWallet("wallet_test.dat");
+ std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
+ pwalletMain = new CWallet(std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);
RegisterValidationInterface(pwalletMain);
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 67e5e90224..8eeba72a06 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -29,7 +29,7 @@ extern UniValue importmulti(const JSONRPCRequest& request);
std::vector<std::unique_ptr<CWalletTx>> wtxn;
-typedef std::set<std::pair<const CWalletTx*,unsigned int> > CoinSet;
+typedef std::set<CInputCoin> CoinSet;
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
@@ -424,6 +424,17 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup)
BOOST_CHECK_EQUAL(response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":\"Failed to rescan before time %d, transactions may be missing.\"}},{\"success\":true}]", newTip->GetBlockTimeMax()));
::pwalletMain = backup;
}
+
+ // Verify ScanForWalletTransactions does not return null when the scan is
+ // elided due to the nTimeFirstKey optimization.
+ {
+ CWallet wallet;
+ {
+ LOCK(wallet.cs_wallet);
+ wallet.UpdateTimeFirstKey(newTip->GetBlockTime() + 7200 + 1);
+ }
+ BOOST_CHECK_EQUAL(newTip, wallet.ScanForWalletTransactions(newTip));
+ }
}
// Check that GetImmatureCredit() returns a newly calculated value instead of
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 42cecfb541..fa82c9c9df 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -11,11 +11,14 @@
#include "wallet/coincontrol.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
+#include "fs.h"
#include "key.h"
#include "keystore.h"
#include "validation.h"
#include "net.h"
+#include "policy/fees.h"
#include "policy/policy.h"
+#include "policy/rbf.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "script/script.h"
@@ -30,7 +33,6 @@
#include <assert.h>
#include <boost/algorithm/string/replace.hpp>
-#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
CWallet* pwalletMain = NULL;
@@ -64,10 +66,10 @@ const uint256 CMerkleTx::ABANDON_HASH(uint256S("00000000000000000000000000000000
struct CompareValueOnly
{
- bool operator()(const std::pair<CAmount, std::pair<const CWalletTx*, unsigned int> >& t1,
- const std::pair<CAmount, std::pair<const CWalletTx*, unsigned int> >& t2) const
+ bool operator()(const CInputCoin& t1,
+ const CInputCoin& t2) const
{
- return t1.first < t2.first;
+ return t1.txout.nValue < t2.txout.nValue;
}
};
@@ -160,7 +162,7 @@ void CWallet::DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool inter
secret = childKey.key;
metadata.hdMasterKeyID = hdChain.masterKeyID;
// update the chain model in the database
- if (!CWalletDB(strWalletFile).WriteHDChain(hdChain))
+ if (!CWalletDB(*dbw).WriteHDChain(hdChain))
throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed");
}
@@ -179,10 +181,8 @@ bool CWallet::AddKeyPubKey(const CKey& secret, const CPubKey &pubkey)
if (HaveWatchOnly(script))
RemoveWatchOnly(script);
- if (!fFileBacked)
- return true;
if (!IsCrypted()) {
- return CWalletDB(strWalletFile).WriteKey(pubkey,
+ return CWalletDB(*dbw).WriteKey(pubkey,
secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
@@ -194,8 +194,6 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
{
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret))
return false;
- if (!fFileBacked)
- return true;
{
LOCK(cs_wallet);
if (pwalletdbEncryption)
@@ -203,7 +201,7 @@ bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
else
- return CWalletDB(strWalletFile).WriteCryptedKey(vchPubKey,
+ return CWalletDB(*dbw).WriteCryptedKey(vchPubKey,
vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
@@ -239,9 +237,7 @@ bool CWallet::AddCScript(const CScript& redeemScript)
{
if (!CCryptoKeyStore::AddCScript(redeemScript))
return false;
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).WriteCScript(Hash160(redeemScript), redeemScript);
+ return CWalletDB(*dbw).WriteCScript(Hash160(redeemScript), redeemScript);
}
bool CWallet::LoadCScript(const CScript& redeemScript)
@@ -267,9 +263,7 @@ bool CWallet::AddWatchOnly(const CScript& dest)
const CKeyMetadata& meta = mapKeyMetadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).WriteWatchOnly(dest, meta);
+ return CWalletDB(*dbw).WriteWatchOnly(dest, meta);
}
bool CWallet::AddWatchOnly(const CScript& dest, int64_t nCreateTime)
@@ -285,9 +279,8 @@ bool CWallet::RemoveWatchOnly(const CScript &dest)
return false;
if (!HaveWatchOnly())
NotifyWatchonlyChanged(false);
- if (fFileBacked)
- if (!CWalletDB(strWalletFile).EraseWatchOnly(dest))
- return false;
+ if (!CWalletDB(*dbw).EraseWatchOnly(dest))
+ return false;
return true;
}
@@ -352,7 +345,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
return false;
if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey))
return false;
- CWalletDB(strWalletFile).WriteMasterKey(pMasterKey.first, pMasterKey.second);
+ CWalletDB(*dbw).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked)
Lock();
return true;
@@ -365,7 +358,7 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
void CWallet::SetBestChain(const CBlockLocator& loc)
{
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
walletdb.WriteBestBlock(loc);
}
@@ -384,9 +377,8 @@ bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB* pwalletdbIn,
if (nVersion > nWalletMaxVersion)
nWalletMaxVersion = nVersion;
- if (fFileBacked)
{
- CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(strWalletFile);
+ CWalletDB* pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(*dbw);
if (nWalletVersion > 40000)
pwalletdb->WriteMinVersion(nWalletVersion);
if (!pwalletdbIn)
@@ -440,7 +432,7 @@ bool CWallet::HasWalletSpend(const uint256& txid) const
void CWallet::Flush(bool shutdown)
{
- bitdb.Flush(shutdown);
+ dbw->Flush(shutdown);
}
bool CWallet::Verify()
@@ -593,24 +585,19 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
{
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);
+ assert(!pwalletdbEncryption);
+ pwalletdbEncryption = new CWalletDB(*dbw);
+ if (!pwalletdbEncryption->TxnBegin()) {
+ delete pwalletdbEncryption;
+ pwalletdbEncryption = NULL;
+ return false;
}
+ pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
if (!EncryptKeys(_vMasterKey))
{
- if (fFileBacked) {
- pwalletdbEncryption->TxnAbort();
- delete pwalletdbEncryption;
- }
+ pwalletdbEncryption->TxnAbort();
+ delete pwalletdbEncryption;
// We now probably have half of our keys encrypted in memory, and half not...
// die and let the user reload the unencrypted wallet.
assert(false);
@@ -619,19 +606,16 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
// 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 the unencrypted wallet.
- assert(false);
- }
-
+ if (!pwalletdbEncryption->TxnCommit()) {
delete pwalletdbEncryption;
- pwalletdbEncryption = NULL;
+ // We now have keys encrypted in memory, but not on disk...
+ // die to avoid confusion and let the user reload the unencrypted wallet.
+ assert(false);
}
+ delete pwalletdbEncryption;
+ pwalletdbEncryption = NULL;
+
Lock();
Unlock(strWalletPassphrase);
@@ -647,7 +631,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
// 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);
+ dbw->Rewrite();
}
NotifyStatusChanged(this);
@@ -658,7 +642,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase)
DBErrors CWallet::ReorderTransactions()
{
LOCK(cs_wallet);
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
// Old wallets didn't have any defined order for transactions
// Probably a bad idea to change the output of this
@@ -739,14 +723,14 @@ int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
if (pwalletdb) {
pwalletdb->WriteOrderPosNext(nOrderPosNext);
} else {
- CWalletDB(strWalletFile).WriteOrderPosNext(nOrderPosNext);
+ CWalletDB(*dbw).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmount, std::string strComment)
{
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
if (!walletdb.TxnBegin())
return false;
@@ -780,7 +764,7 @@ bool CWallet::AccountMove(std::string strFrom, std::string strTo, CAmount nAmoun
bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew)
{
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
CAccount account;
walletdb.ReadAccount(strAccount, account);
@@ -841,7 +825,7 @@ bool CWallet::MarkReplaced(const uint256& originalHash, const uint256& newHash)
wtx.mapValue["replaced_by_txid"] = newHash.ToString();
- CWalletDB walletdb(strWalletFile, "r+");
+ CWalletDB walletdb(*dbw, "r+");
bool success = true;
if (!walletdb.WriteTx(wtx)) {
@@ -858,7 +842,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose)
{
LOCK(cs_wallet);
- CWalletDB walletdb(strWalletFile, "r+", fFlushOnClose);
+ CWalletDB walletdb(*dbw, "r+", fFlushOnClose);
uint256 hash = wtxIn.GetHash();
@@ -953,9 +937,9 @@ bool CWallet::LoadToWallet(const CWalletTx& wtxIn)
/**
* Add a transaction to the wallet, or update it. pIndex and posInBlock should
* be set when the transaction was known to be included in a block. When
- * posInBlock = SYNC_TRANSACTION_NOT_IN_BLOCK (-1) , then wallet state is not
- * updated in AddToWallet, but notifications happen and cached balances are
- * marked dirty.
+ * pIndex == NULL, then wallet state is not updated in AddToWallet, but
+ * notifications happen and cached balances are marked dirty.
+ *
* If fUpdate is true, existing transactions will be updated.
* TODO: One exception to this is that the abandoned state is cleared under the
* assumption that any further notification of a transaction that was considered
@@ -963,12 +947,13 @@ bool CWallet::LoadToWallet(const CWalletTx& wtxIn)
* Abandoned state should probably be more carefully tracked via different
* posInBlock signals or by checking mempool presence when necessary.
*/
-bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate)
+bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef& ptx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate)
{
+ const CTransaction& tx = *ptx;
{
AssertLockHeld(cs_wallet);
- if (posInBlock != -1) {
+ if (pIndex != NULL) {
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
@@ -985,10 +970,10 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
{
- CWalletTx wtx(this, MakeTransactionRef(tx));
+ CWalletTx wtx(this, ptx);
// Get merkle branch if transaction was found in a block
- if (posInBlock != -1)
+ if (pIndex != NULL)
wtx.SetMerkleBranch(pIndex, posInBlock);
return AddToWallet(wtx, false);
@@ -1001,7 +986,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
{
LOCK2(cs_main, cs_wallet);
- CWalletDB walletdb(strWalletFile, "r+");
+ CWalletDB walletdb(*dbw, "r+");
std::set<uint256> todo;
std::set<uint256> done;
@@ -1073,7 +1058,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
return;
// Do not flush the wallet here for performance reasons
- CWalletDB walletdb(strWalletFile, "r+", false);
+ CWalletDB walletdb(*dbw, "r+", false);
std::set<uint256> todo;
std::set<uint256> done;
@@ -1113,11 +1098,10 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
}
}
-void CWallet::SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock)
-{
- LOCK2(cs_main, cs_wallet);
+void CWallet::SyncTransaction(const CTransactionRef& ptx, const CBlockIndex *pindex, int posInBlock) {
+ const CTransaction& tx = *ptx;
- if (!AddToWalletIfInvolvingMe(tx, pindex, posInBlock, true))
+ if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true))
return; // Not one of ours
// If a transaction changes 'conflicted' state, that changes the balance
@@ -1130,6 +1114,38 @@ void CWallet::SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex,
}
}
+void CWallet::TransactionAddedToMempool(const CTransactionRef& ptx) {
+ LOCK2(cs_main, cs_wallet);
+ SyncTransaction(ptx);
+}
+
+void CWallet::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) {
+ LOCK2(cs_main, cs_wallet);
+ // TODO: Temporarily ensure that mempool removals are notified before
+ // connected transactions. This shouldn't matter, but the abandoned
+ // state of transactions in our wallet is currently cleared when we
+ // receive another notification and there is a race condition where
+ // notification of a connected conflict might cause an outside process
+ // to abandon a transaction and then have it inadvertently cleared by
+ // the notification that the conflicted transaction was evicted.
+
+ for (const CTransactionRef& ptx : vtxConflicted) {
+ SyncTransaction(ptx);
+ }
+ for (size_t i = 0; i < pblock->vtx.size(); i++) {
+ SyncTransaction(pblock->vtx[i], pindex, i);
+ }
+}
+
+void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) {
+ LOCK2(cs_main, cs_wallet);
+
+ for (const CTransactionRef& ptx : pblock->vtx) {
+ SyncTransaction(ptx);
+ }
+}
+
+
isminetype CWallet::IsMine(const CTxIn &txin) const
{
@@ -1322,7 +1338,7 @@ bool CWallet::SetHDMasterKey(const CPubKey& pubkey)
bool CWallet::SetHDChain(const CHDChain& chain, bool memonly)
{
LOCK(cs_wallet);
- if (!memonly && !CWalletDB(strWalletFile).WriteHDChain(chain))
+ if (!memonly && !CWalletDB(*dbw).WriteHDChain(chain))
throw std::runtime_error(std::string(__func__) + ": writing chain failed");
hdChain = chain;
@@ -1435,59 +1451,27 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
}
-void CWalletTx::GetAccountAmounts(const std::string& strAccount, CAmount& nReceived,
- CAmount& nSent, CAmount& nFee, const isminefilter& filter) const
-{
- nReceived = nSent = nFee = 0;
-
- CAmount allFee;
- std::string strSentAccount;
- std::list<COutputEntry> listReceived;
- std::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))
- {
- std::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;
- }
- }
- }
-}
-
/**
* 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.
*
* Returns pointer to the first block in the last contiguous range that was
- * successfully scanned.
- *
+ * successfully scanned or elided (elided if pIndexStart points at a block
+ * before CWallet::nTimeFirstKey). Returns null if there is no such range, or
+ * the range doesn't include chainActive.Tip().
*/
CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
{
- CBlockIndex* ret = nullptr;
int64_t nNow = GetTime();
const CChainParams& chainParams = Params();
CBlockIndex* pindex = pindexStart;
+ CBlockIndex* ret = pindexStart;
{
LOCK2(cs_main, cs_wallet);
+ fAbortRescan = false;
+ fScanningWallet = true;
// no need to read and scan block, if block was created before
// our wallet birthday (as adjusted for block time variability)
@@ -1497,15 +1481,19 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f
ShowProgress(_("Rescanning..."), 0); // show rescan progress in GUI as dialog or on splashscreen, if -rescan on startup
double dProgressStart = GuessVerificationProgress(chainParams.TxData(), pindex);
double dProgressTip = GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
- while (pindex)
+ while (pindex && !fAbortRescan)
{
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0)
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((GuessVerificationProgress(chainParams.TxData(), pindex) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
+ if (GetTime() >= nNow + 60) {
+ nNow = GetTime();
+ LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
+ }
CBlock block;
if (ReadBlockFromDisk(block, pindex, Params().GetConsensus())) {
for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) {
- AddToWalletIfInvolvingMe(*block.vtx[posInBlock], pindex, posInBlock, fUpdate);
+ AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex, posInBlock, fUpdate);
}
if (!ret) {
ret = pindex;
@@ -1514,12 +1502,13 @@ CBlockIndex* CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool f
ret = nullptr;
}
pindex = chainActive.Next(pindex);
- if (GetTime() >= nNow + 60) {
- nNow = GetTime();
- LogPrintf("Still rescanning. At block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
- }
+ }
+ if (pindex && fAbortRescan) {
+ LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight, GuessVerificationProgress(chainParams.TxData(), pindex));
}
ShowProgress(_("Rescanning..."), 100); // hide progress dialog in GUI
+
+ fScanningWallet = false;
}
return ret;
}
@@ -1754,10 +1743,7 @@ CAmount CWalletTx::GetChange() const
bool CWalletTx::InMempool() const
{
LOCK(mempool.cs);
- if (mempool.exists(GetHash())) {
- return true;
- }
- return false;
+ return mempool.exists(GetHash());
}
bool CWalletTx::IsTrusted() const
@@ -1948,6 +1934,49 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
return nTotal;
}
+// Calculate total balance in a different way from GetBalance. The biggest
+// difference is that GetBalance sums up all unspent TxOuts paying to the
+// wallet, while this sums up both spent and unspent TxOuts paying to the
+// wallet, and then subtracts the values of TxIns spending from the wallet. This
+// also has fewer restrictions on which unconfirmed transactions are considered
+// trusted.
+CAmount CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const
+{
+ LOCK2(cs_main, cs_wallet);
+
+ CAmount balance = 0;
+ for (const auto& entry : mapWallet) {
+ const CWalletTx& wtx = entry.second;
+ const int depth = wtx.GetDepthInMainChain();
+ if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.GetBlocksToMaturity() > 0) {
+ continue;
+ }
+
+ // Loop through tx outputs and add incoming payments. For outgoing txs,
+ // treat change outputs specially, as part of the amount debited.
+ CAmount debit = wtx.GetDebit(filter);
+ const bool outgoing = debit > 0;
+ for (const CTxOut& out : wtx.tx->vout) {
+ if (outgoing && IsChange(out)) {
+ debit -= out.nValue;
+ } else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetAccountName(out.scriptPubKey))) {
+ balance += out.nValue;
+ }
+ }
+
+ // For outgoing txs, subtract amount debited.
+ if (outgoing && (!account || *account == wtx.strFromAccount)) {
+ balance -= debit;
+ }
+ }
+
+ if (account) {
+ balance += CWalletDB(*dbw).GetAccountCreditDebit(*account);
+ }
+
+ return balance;
+}
+
void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const CCoinControl *coinControl, bool fIncludeZeroValue) const
{
vCoins.clear();
@@ -2025,7 +2054,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, bool fOnlySafe, const
}
}
-static void ApproximateBestSubset(const std::vector<std::pair<CAmount, std::pair<const CWalletTx*,unsigned int> > >& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
+static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
std::vector<char> vfIncluded;
@@ -2050,9 +2079,9 @@ static void ApproximateBestSubset(const std::vector<std::pair<CAmount, std::pair
//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.rand32()&1 : !vfIncluded[i])
+ if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
{
- nTotal += vValue[i].first;
+ nTotal += vValue[i].txout.nValue;
vfIncluded[i] = true;
if (nTotal >= nTargetValue)
{
@@ -2062,7 +2091,7 @@ static void ApproximateBestSubset(const std::vector<std::pair<CAmount, std::pair
nBest = nTotal;
vfBest = vfIncluded;
}
- nTotal -= vValue[i].first;
+ nTotal -= vValue[i].txout.nValue;
vfIncluded[i] = false;
}
}
@@ -2072,16 +2101,14 @@ static void ApproximateBestSubset(const std::vector<std::pair<CAmount, std::pair
}
bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMine, const int nConfTheirs, const uint64_t nMaxAncestors, std::vector<COutput> vCoins,
- std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const
+ std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) const
{
setCoinsRet.clear();
nValueRet = 0;
// List of values less than target
- std::pair<CAmount, std::pair<const CWalletTx*,unsigned int> > coinLowestLarger;
- coinLowestLarger.first = std::numeric_limits<CAmount>::max();
- coinLowestLarger.second.first = NULL;
- std::vector<std::pair<CAmount, std::pair<const CWalletTx*,unsigned int> > > vValue;
+ boost::optional<CInputCoin> coinLowestLarger;
+ std::vector<CInputCoin> vValue;
CAmount nTotalLower = 0;
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
@@ -2100,22 +2127,21 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin
continue;
int i = output.i;
- CAmount n = pcoin->tx->vout[i].nValue;
- std::pair<CAmount,std::pair<const CWalletTx*,unsigned int> > coin = std::make_pair(n,std::make_pair(pcoin, i));
+ CInputCoin coin = CInputCoin(pcoin, i);
- if (n == nTargetValue)
+ if (coin.txout.nValue == nTargetValue)
{
- setCoinsRet.insert(coin.second);
- nValueRet += coin.first;
+ setCoinsRet.insert(coin);
+ nValueRet += coin.txout.nValue;
return true;
}
- else if (n < nTargetValue + MIN_CHANGE)
+ else if (coin.txout.nValue < nTargetValue + MIN_CHANGE)
{
vValue.push_back(coin);
- nTotalLower += n;
+ nTotalLower += coin.txout.nValue;
}
- else if (n < coinLowestLarger.first)
+ else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue)
{
coinLowestLarger = coin;
}
@@ -2125,18 +2151,18 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin
{
for (unsigned int i = 0; i < vValue.size(); ++i)
{
- setCoinsRet.insert(vValue[i].second);
- nValueRet += vValue[i].first;
+ setCoinsRet.insert(vValue[i]);
+ nValueRet += vValue[i].txout.nValue;
}
return true;
}
if (nTotalLower < nTargetValue)
{
- if (coinLowestLarger.second.first == NULL)
+ if (!coinLowestLarger)
return false;
- setCoinsRet.insert(coinLowestLarger.second);
- nValueRet += coinLowestLarger.first;
+ setCoinsRet.insert(coinLowestLarger.get());
+ nValueRet += coinLowestLarger->txout.nValue;
return true;
}
@@ -2152,31 +2178,35 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const int nConfMin
// 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 + MIN_CHANGE) || coinLowestLarger.first <= nBest))
+ if (coinLowestLarger &&
+ ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest))
{
- setCoinsRet.insert(coinLowestLarger.second);
- nValueRet += coinLowestLarger.first;
+ setCoinsRet.insert(coinLowestLarger.get());
+ nValueRet += coinLowestLarger->txout.nValue;
}
else {
for (unsigned int i = 0; i < vValue.size(); i++)
if (vfBest[i])
{
- setCoinsRet.insert(vValue[i].second);
- nValueRet += vValue[i].first;
+ setCoinsRet.insert(vValue[i]);
+ nValueRet += vValue[i].txout.nValue;
}
- 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));
+ if (LogAcceptCategory(BCLog::SELECTCOINS)) {
+ LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
+ for (unsigned int i = 0; i < vValue.size(); i++) {
+ if (vfBest[i]) {
+ LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue));
+ }
+ }
+ LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
+ }
}
return true;
}
-bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
+bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
{
std::vector<COutput> vCoins(vAvailableCoins);
@@ -2188,13 +2218,13 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
if (!out.fSpendable)
continue;
nValueRet += out.tx->tx->vout[out.i].nValue;
- setCoinsRet.insert(std::make_pair(out.tx, out.i));
+ setCoinsRet.insert(CInputCoin(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
// calculate value from preset inputs and store them
- std::set<std::pair<const CWalletTx*, uint32_t> > setPresetCoins;
+ std::set<CInputCoin> setPresetCoins;
CAmount nValueFromPresetInputs = 0;
std::vector<COutPoint> vPresetInputs;
@@ -2210,7 +2240,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
if (pcoin->tx->vout.size() <= outpoint.n)
return false;
nValueFromPresetInputs += pcoin->tx->vout[outpoint.n].nValue;
- setPresetCoins.insert(std::make_pair(pcoin, outpoint.n));
+ setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
}
@@ -2218,7 +2248,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// remove preset inputs from vCoins
for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coinControl && coinControl->HasSelected();)
{
- if (setPresetCoins.count(std::make_pair(it->tx, it->i)))
+ if (setPresetCoins.count(CInputCoin(it->tx, it->i)))
it = vCoins.erase(it);
else
++it;
@@ -2245,6 +2275,28 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return res;
}
+bool CWallet::SignTransaction(CMutableTransaction &tx)
+{
+ // sign the new tx
+ CTransaction txNewConst(tx);
+ int nIn = 0;
+ for (auto& input : tx.vin) {
+ std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(input.prevout.hash);
+ if(mi == mapWallet.end() || input.prevout.n >= mi->second.tx->vout.size()) {
+ return false;
+ }
+ const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey;
+ const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue;
+ SignatureData sigdata;
+ if (!ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) {
+ return false;
+ }
+ UpdateTransaction(tx, nIn, sigdata);
+ nIn++;
+ }
+ return true;
+}
+
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey, const CTxDestination& destChange)
{
std::vector<CRecipient> vecSend;
@@ -2362,7 +2414,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
{
- std::set<std::pair<const CWalletTx*,unsigned int> > setCoins;
+ std::set<CInputCoin> setCoins;
LOCK2(cs_main, cs_wallet);
{
std::vector<COutput> vAvailableCoins;
@@ -2505,9 +2557,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
std::vector<CTxOut>::iterator position = txNew.vout.begin()+nChangePosInOut;
txNew.vout.insert(position, newTxOut);
}
- }
- else
+ } else {
reservekey.ReturnKey();
+ nChangePosInOut = -1;
+ }
// Fill vin
//
@@ -2521,7 +2574,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
// behavior."
bool rbf = coinControl ? coinControl->signalRbf : fWalletRbf;
for (const auto& coin : setCoins)
- txNew.vin.push_back(CTxIn(coin.first->GetHash(),coin.second,CScript(),
+ txNew.vin.push_back(CTxIn(coin.outpoint,CScript(),
std::numeric_limits<unsigned int>::max() - (rbf ? 2 : 1)));
// Fill in dummy signatures for fee calculation.
@@ -2545,7 +2598,7 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
if (coinControl && coinControl->nConfirmTarget > 0)
currentConfirmationTarget = coinControl->nConfirmTarget;
- CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, mempool);
+ CAmount nFeeNeeded = GetMinimumFee(nBytes, currentConfirmationTarget, ::mempool, ::feeEstimator);
if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
nFeeNeeded = coinControl->nMinimumTotalFee;
}
@@ -2604,10 +2657,10 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
int nIn = 0;
for (const auto& coin : setCoins)
{
- const CScript& scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey;
+ const CScript& scriptPubKey = coin.txout.scriptPubKey;
SignatureData sigdata;
- if (!ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.first->tx->vout[coin.second].nValue, SIGHASH_ALL), scriptPubKey, sigdata))
+ if (!ProduceSignature(TransactionSignatureCreator(this, &txNewConst, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata))
{
strFailReason = _("Signing transaction failed");
return false;
@@ -2691,13 +2744,13 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey, CCon
}
void CWallet::ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& entries) {
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
return walletdb.ListAccountCreditDebit(strAccount, entries);
}
bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry)
{
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
return AddAccountingEntry(acentry, &walletdb);
}
@@ -2719,19 +2772,14 @@ CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
}
-CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
+CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, bool ignoreUserSetFee)
{
// payTxFee is the user-set global for desired feerate
- return GetMinimumFee(nTxBytes, nConfirmTarget, pool, payTxFee.GetFee(nTxBytes));
-}
-
-CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, CAmount targetFee)
-{
- CAmount nFeeNeeded = targetFee;
+ CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
// User didn't set: use -txconfirmtarget to estimate...
- if (nFeeNeeded == 0) {
+ if (nFeeNeeded == 0 || ignoreUserSetFee) {
int estimateFoundTarget = nConfirmTarget;
- nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes);
+ nFeeNeeded = estimator.estimateSmartFee(nConfirmTarget, &estimateFoundTarget, pool).GetFee(nTxBytes);
// ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
if (nFeeNeeded == 0)
nFeeNeeded = fallbackFee.GetFee(nTxBytes);
@@ -2749,13 +2797,11 @@ CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarge
DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
{
- if (!fFileBacked)
- return DB_LOAD_OK;
fFirstRunRet = false;
- DBErrors nLoadWalletRet = CWalletDB(strWalletFile,"cr+").LoadWallet(this);
+ DBErrors nLoadWalletRet = CWalletDB(*dbw,"cr+").LoadWallet(this);
if (nLoadWalletRet == DB_NEED_REWRITE)
{
- if (CDB::Rewrite(strWalletFile, "\x04pool"))
+ if (dbw->Rewrite("\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
@@ -2776,17 +2822,15 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet)
DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256>& vHashOut)
{
- if (!fFileBacked)
- return DB_LOAD_OK;
AssertLockHeld(cs_wallet); // mapWallet
vchDefaultKey = CPubKey();
- DBErrors nZapSelectTxRet = CWalletDB(strWalletFile,"cr+").ZapSelectTx(vHashIn, vHashOut);
+ DBErrors nZapSelectTxRet = CWalletDB(*dbw,"cr+").ZapSelectTx(vHashIn, vHashOut);
for (uint256 hash : vHashOut)
mapWallet.erase(hash);
if (nZapSelectTxRet == DB_NEED_REWRITE)
{
- if (CDB::Rewrite(strWalletFile, "\x04pool"))
+ if (dbw->Rewrite("\x04pool"))
{
setKeyPool.clear();
// Note: can't top-up keypool here, because wallet is locked.
@@ -2806,13 +2850,11 @@ DBErrors CWallet::ZapSelectTx(std::vector<uint256>& vHashIn, std::vector<uint256
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx>& vWtx)
{
- if (!fFileBacked)
- return DB_LOAD_OK;
vchDefaultKey = CPubKey();
- DBErrors nZapWalletTxRet = CWalletDB(strWalletFile,"cr+").ZapWalletTx(vWtx);
+ DBErrors nZapWalletTxRet = CWalletDB(*dbw,"cr+").ZapWalletTx(vWtx);
if (nZapWalletTxRet == DB_NEED_REWRITE)
{
- if (CDB::Rewrite(strWalletFile, "\x04pool"))
+ if (dbw->Rewrite("\x04pool"))
{
LOCK(cs_wallet);
setKeyPool.clear();
@@ -2842,11 +2884,9 @@ bool CWallet::SetAddressBook(const CTxDestination& address, const std::string& s
}
NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO,
strPurpose, (fUpdated ? CT_UPDATED : CT_NEW) );
- if (!fFileBacked)
+ if (!strPurpose.empty() && !CWalletDB(*dbw).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose))
return false;
- if (!strPurpose.empty() && !CWalletDB(strWalletFile).WritePurpose(CBitcoinAddress(address).ToString(), strPurpose))
- return false;
- return CWalletDB(strWalletFile).WriteName(CBitcoinAddress(address).ToString(), strName);
+ return CWalletDB(*dbw).WriteName(CBitcoinAddress(address).ToString(), strName);
}
bool CWallet::DelAddressBook(const CTxDestination& address)
@@ -2854,33 +2894,40 @@ 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(std::string, std::string) &item, mapAddressBook[address].destdata)
{
- // Delete destdata tuples associated with address
- std::string strAddress = CBitcoinAddress(address).ToString();
- BOOST_FOREACH(const PAIRTYPE(std::string, std::string) &item, mapAddressBook[address].destdata)
- {
- CWalletDB(strWalletFile).EraseDestData(strAddress, item.first);
- }
+ CWalletDB(*dbw).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());
+ CWalletDB(*dbw).ErasePurpose(CBitcoinAddress(address).ToString());
+ return CWalletDB(*dbw).EraseName(CBitcoinAddress(address).ToString());
}
-bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
+const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const
{
- if (fFileBacked)
- {
- if (!CWalletDB(strWalletFile).WriteDefaultKey(vchPubKey))
- return false;
+ CTxDestination address;
+ if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) {
+ auto mi = mapAddressBook.find(address);
+ if (mi != mapAddressBook.end()) {
+ return mi->second.name;
+ }
}
+ // A scriptPubKey that doesn't have an entry in the address book is
+ // associated with the default account ("").
+ const static std::string DEFAULT_ACCOUNT_NAME;
+ return DEFAULT_ACCOUNT_NAME;
+}
+
+bool CWallet::SetDefaultKey(const CPubKey &vchPubKey)
+{
+ if (!CWalletDB(*dbw).WriteDefaultKey(vchPubKey))
+ return false;
vchDefaultKey = vchPubKey;
return true;
}
@@ -2893,7 +2940,7 @@ bool CWallet::NewKeyPool()
{
{
LOCK(cs_wallet);
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
BOOST_FOREACH(int64_t nIndex, setKeyPool)
walletdb.ErasePool(nIndex);
setKeyPool.clear();
@@ -2914,7 +2961,7 @@ size_t CWallet::KeypoolCountExternalKeys()
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT))
return setKeyPool.size();
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
// count amount of external keys
size_t amountE = 0;
@@ -2957,7 +3004,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
missingInternal = 0;
}
bool internal = false;
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
for (int64_t i = missingInternal + missingExternal; i--;)
{
int64_t nEnd = 1;
@@ -2988,7 +3035,7 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool int
if(setKeyPool.empty())
return;
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
// try to find a key that matches the internal/external filter
for(const int64_t& id : setKeyPool)
@@ -3014,11 +3061,8 @@ void CWallet::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool int
void CWallet::KeepKey(int64_t nIndex)
{
// Remove from key pool
- if (fFileBacked)
- {
- CWalletDB walletdb(strWalletFile);
- walletdb.ErasePool(nIndex);
- }
+ CWalletDB walletdb(*dbw);
+ walletdb.ErasePool(nIndex);
LogPrintf("keypool keep %d\n", nIndex);
}
@@ -3060,7 +3104,7 @@ int64_t CWallet::GetOldestKeyPoolTime()
return GetTime();
CKeyPool keypool;
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT))
{
@@ -3226,37 +3270,6 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings()
return ret;
}
-CAmount CWallet::GetAccountBalance(const std::string& strAccount, int nMinDepth, const isminefilter& filter)
-{
- CWalletDB walletdb(strWalletFile);
- return GetAccountBalance(walletdb, strAccount, nMinDepth, filter);
-}
-
-CAmount CWallet::GetAccountBalance(CWalletDB& walletdb, const std::string& strAccount, int nMinDepth, const isminefilter& filter)
-{
- CAmount nBalance = 0;
-
- // Tally wallet transactions
- for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
- {
- const CWalletTx& wtx = (*it).second;
- if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0)
- continue;
-
- CAmount nReceived, nSent, nFee;
- wtx.GetAccountAmounts(strAccount, nReceived, nSent, nFee, filter);
-
- if (nReceived != 0 && wtx.GetDepthInMainChain() >= nMinDepth)
- nBalance += nReceived;
- nBalance -= nSent + nFee;
- }
-
- // Tally internal accounting entries
- nBalance += walletdb.GetAccountCreditDebit(strAccount);
-
- return nBalance;
-}
-
std::set<CTxDestination> CWallet::GetAccountAddresses(const std::string& strAccount) const
{
LOCK(cs_wallet);
@@ -3308,7 +3321,7 @@ void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
{
setAddress.clear();
- CWalletDB walletdb(strWalletFile);
+ CWalletDB walletdb(*dbw);
LOCK2(cs_main, cs_wallet);
BOOST_FOREACH(const int64_t& id, setKeyPool)
@@ -3324,20 +3337,9 @@ void CWallet::GetAllReserveKeys(std::set<CKeyID>& setAddress) const
}
}
-void CWallet::UpdatedTransaction(const uint256 &hashTx)
+void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script)
{
- {
- LOCK(cs_wallet);
- // Only notify UI if this transaction is in this wallet
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(hashTx);
- if (mi != mapWallet.end())
- NotifyTransactionChanged(this, hashTx, CT_UPDATED);
- }
-}
-
-void CWallet::GetScriptForMining(boost::shared_ptr<CReserveScript> &script)
-{
- boost::shared_ptr<CReserveKey> rKey(new CReserveKey(this));
+ std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this);
CPubKey pubkey;
if (!rKey->GetReservedKey(pubkey))
return;
@@ -3541,18 +3543,14 @@ bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, co
return false;
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
- if (!fFileBacked)
- return true;
- return CWalletDB(strWalletFile).WriteDestData(CBitcoinAddress(dest).ToString(), key, value);
+ return CWalletDB(*dbw).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);
+ return CWalletDB(*dbw).EraseDestData(CBitcoinAddress(dest).ToString(), key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value)
@@ -3622,7 +3620,8 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
if (GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
- CWallet *tempWallet = new CWallet(walletFile);
+ std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
+ CWallet *tempWallet = new CWallet(std::move(dbw));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DB_LOAD_OK) {
InitError(strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
@@ -3637,7 +3636,8 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
- CWallet *walletInstance = new CWallet(walletFile);
+ std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, walletFile));
+ CWallet *walletInstance = new CWallet(std::move(dbw));
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DB_LOAD_OK)
{
@@ -3728,7 +3728,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
CBlockIndex *pindexRescan = chainActive.Genesis();
if (!GetBoolArg("-rescan", false))
{
- CWalletDB walletdb(walletFile);
+ CWalletDB walletdb(*walletInstance->dbw);
CBlockLocator locator;
if (walletdb.ReadBestBlock(locator))
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
@@ -3761,7 +3761,7 @@ CWallet* CWallet::CreateWalletFromFile(const std::string walletFile)
// Restore wallet transaction metadata after -zapwallettxes=1
if (GetBoolArg("-zapwallettxes", false) && GetArg("-zapwallettxes", "1") != "2")
{
- CWalletDB walletdb(walletFile);
+ CWalletDB walletdb(*walletInstance->dbw);
BOOST_FOREACH(const CWalletTx& wtxOld, vWtx)
{
@@ -3805,7 +3805,7 @@ bool CWallet::InitLoadWallet()
std::string walletFile = GetArg("-wallet", DEFAULT_WALLET_DAT);
- if (walletFile.find_first_of("/\\") != std::string::npos) {
+ if (boost::filesystem::path(walletFile).filename() != walletFile) {
return InitError(_("-wallet parameter must only specify a filename (not a path)"));
} else if (SanitizeString(walletFile, SAFE_CHARS_FILENAME) != walletFile) {
return InitError(_("Invalid characters in -wallet filename"));
@@ -3921,38 +3921,7 @@ bool CWallet::ParameterInteraction()
bool CWallet::BackupWallet(const std::string& strDest)
{
- if (!fFileBacked)
- return false;
- while (true)
- {
- {
- LOCK(bitdb.cs_db);
- if (!bitdb.mapFileUseCount.count(strWalletFile) || bitdb.mapFileUseCount[strWalletFile] == 0)
- {
- // Flush log data to the dat file
- bitdb.CloseDb(strWalletFile);
- bitdb.CheckpointLSN(strWalletFile);
- bitdb.mapFileUseCount.erase(strWalletFile);
-
- // Copy wallet file
- boost::filesystem::path pathSrc = GetDataDir() / strWalletFile;
- boost::filesystem::path pathDest(strDest);
- if (boost::filesystem::is_directory(pathDest))
- pathDest /= strWalletFile;
-
- try {
- boost::filesystem::copy_file(pathSrc, pathDest, boost::filesystem::copy_option::overwrite_if_exists);
- LogPrintf("copied %s to %s\n", strWalletFile, pathDest.string());
- return true;
- } catch (const boost::filesystem::filesystem_error& e) {
- LogPrintf("error copying %s to %s - %s\n", strWalletFile, pathDest.string(), e.what());
- return false;
- }
- }
- }
- MilliSleep(100);
- }
- return false;
+ return dbw->Backup(strDest);
}
CKeyPool::CKeyPool()
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 4e7e1b5797..81f7ec59f5 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -28,8 +28,6 @@
#include <utility>
#include <vector>
-#include <boost/shared_ptr.hpp>
-
extern CWallet* pwalletMain;
/**
@@ -75,6 +73,7 @@ class CReserveKey;
class CScript;
class CScheduler;
class CTxMemPool;
+class CBlockPolicyEstimator;
class CWalletTx;
/** (client) version numbers for particular wallet features */
@@ -452,9 +451,6 @@ public:
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);
@@ -475,7 +471,34 @@ public:
};
+class CInputCoin {
+public:
+ CInputCoin(const CWalletTx* walletTx, unsigned int i)
+ {
+ if (!walletTx)
+ throw std::invalid_argument("walletTx should not be null");
+ if (i >= walletTx->tx->vout.size())
+ throw std::out_of_range("The output index is out of range");
+
+ outpoint = COutPoint(walletTx->GetHash(), i);
+ txout = walletTx->tx->vout[i];
+ }
+
+ COutPoint outpoint;
+ CTxOut txout;
+
+ bool operator<(const CInputCoin& rhs) const {
+ return outpoint < rhs.outpoint;
+ }
+
+ bool operator!=(const CInputCoin& rhs) const {
+ return outpoint != rhs.outpoint;
+ }
+ bool operator==(const CInputCoin& rhs) const {
+ return outpoint == rhs.outpoint;
+ }
+};
class COutput
{
@@ -626,13 +649,15 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface
{
private:
static std::atomic<bool> fFlushScheduled;
+ std::atomic<bool> fAbortRescan;
+ std::atomic<bool> fScanningWallet;
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
* all coins from coinControl are selected; Never select unconfirmed coins
* if they are not ours
*/
- bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
+ bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
CWalletDB *pwalletdbEncryption;
@@ -661,14 +686,16 @@ private:
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
+ /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected.
+ * Should be called with pindexBlock and posInBlock if this is for a transaction that is included in a block. */
+ void SyncTransaction(const CTransactionRef& tx, const CBlockIndex *pindex = NULL, int posInBlock = 0);
+
/* the HD chain data model (external chain counters) */
CHDChain hdChain;
/* HD derive new child key (on internal or external chain) */
void DeriveNewChildKey(CKeyMetadata& metadata, CKey& secret, bool internal = false);
- bool fFileBacked;
-
std::set<int64_t> setKeyPool;
int64_t nTimeFirstKey;
@@ -684,17 +711,33 @@ private:
*/
bool AddWatchOnly(const CScript& dest) override;
+ std::unique_ptr<CWalletDBWrapper> dbw;
+
public:
/*
* Main wallet lock.
- * This lock protects all the fields added by CWallet
- * except for:
- * fFileBacked (immutable after instantiation)
- * strWalletFile (immutable after instantiation)
+ * This lock protects all the fields added by CWallet.
*/
mutable CCriticalSection cs_wallet;
- const std::string strWalletFile;
+ /** Get database handle used by this wallet. Ideally this function would
+ * not be necessary.
+ */
+ CWalletDBWrapper& GetDBHandle()
+ {
+ return *dbw;
+ }
+
+ /** Get a name for this wallet for logging/debugging purposes.
+ */
+ std::string GetName() const
+ {
+ if (dbw) {
+ return dbw->GetName();
+ } else {
+ return "dummy";
+ }
+ }
void LoadKeyPool(int nIndex, const CKeyPool &keypool)
{
@@ -716,15 +759,16 @@ public:
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID;
- CWallet()
+ // Create wallet with dummy database handle
+ CWallet(): dbw(new CWalletDBWrapper())
{
SetNull();
}
- CWallet(const std::string& strWalletFileIn) : strWalletFile(strWalletFileIn)
+ // Create wallet with passed-in database handle
+ CWallet(std::unique_ptr<CWalletDBWrapper> dbw_in) : dbw(std::move(dbw_in))
{
SetNull();
- fFileBacked = true;
}
~CWallet()
@@ -737,7 +781,6 @@ public:
{
nWalletVersion = FEATURE_BASE;
nWalletMaxVersion = FEATURE_BASE;
- fFileBacked = false;
nMasterKeyMaxID = 0;
pwalletdbEncryption = NULL;
nOrderPosNext = 0;
@@ -746,6 +789,8 @@ public:
nTimeFirstKey = 0;
fBroadcastTransactions = false;
nRelockTime = 0;
+ fAbortRescan = false;
+ fScanningWallet = false;
}
std::map<uint256, CWalletTx> mapWallet;
@@ -780,7 +825,7 @@ public:
* completion the coin set and corresponding actual target value is
* assembled
*/
- bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
+ bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector<COutput> vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet) const;
bool IsSpent(const uint256& hash, unsigned int n) const;
@@ -790,6 +835,13 @@ public:
void UnlockAllCoins();
void ListLockedCoins(std::vector<COutPoint>& vOutpts);
+ /*
+ * Rescan abort properties
+ */
+ void AbortRescan() { fAbortRescan = true; }
+ bool IsAbortingRescan() { return fAbortRescan; }
+ bool IsScanning() { return fScanningWallet; }
+
/**
* keystore implementation
* Generate a new key
@@ -849,8 +901,10 @@ public:
void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose=true);
bool LoadToWallet(const CWalletTx& wtxIn);
- void SyncTransaction(const CTransaction& tx, const CBlockIndex *pindex, int posInBlock) override;
- bool AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
+ void TransactionAddedToMempool(const CTransactionRef& tx) override;
+ void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex *pindex, const std::vector<CTransactionRef>& vtxConflicted) override;
+ void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock) override;
+ bool AddToWalletIfInvolvingMe(const CTransactionRef& tx, const CBlockIndex* pIndex, int posInBlock, bool fUpdate);
CBlockIndex* ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions();
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
@@ -861,12 +915,14 @@ public:
CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
+ CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const;
/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
*/
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate& specificFeeRate, int& nChangePosInOut, std::string& strFailReason, bool includeWatching, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, bool keepReserveKey = true, const CTxDestination& destChange = CNoDestination());
+ bool SignTransaction(CMutableTransaction& tx);
/**
* Create a new transaction paying the recipients with a set of coins
@@ -881,7 +937,7 @@ public:
bool AddAccountingEntry(const CAccountingEntry&);
bool AddAccountingEntry(const CAccountingEntry&, CWalletDB *pwalletdb);
template <typename ContainerType>
- bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins);
+ bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const;
static CFeeRate minTxFee;
static CFeeRate fallbackFee;
@@ -889,12 +945,7 @@ public:
* Estimate the minimum fee considering user set parameters
* and the required fee
*/
- static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool);
- /**
- * Estimate the minimum fee considering required fee and targetFee or if 0
- * then fee estimation for nConfirmTarget
- */
- static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, CAmount targetFee);
+ static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool, const CBlockPolicyEstimator& estimator, bool ignoreUserSetFee = false);
/**
* Return the minimum required fee taking into account the
* floating relay fee and user set minimum transaction fee
@@ -914,8 +965,6 @@ public:
std::set< std::set<CTxDestination> > GetAddressGroupings();
std::map<CTxDestination, CAmount> GetAddressBalances();
- CAmount GetAccountBalance(const std::string& strAccount, int nMinDepth, const isminefilter& filter);
- CAmount GetAccountBalance(CWalletDB& walletdb, const std::string& strAccount, int nMinDepth, const isminefilter& filter);
std::set<CTxDestination> GetAccountAddresses(const std::string& strAccount) const;
isminetype IsMine(const CTxIn& txin) const;
@@ -946,7 +995,7 @@ public:
bool DelAddressBook(const CTxDestination& address);
- void UpdatedTransaction(const uint256 &hashTx) override;
+ const std::string& GetAccountName(const CScript& scriptPubKey) const;
void Inventory(const uint256 &hash) override
{
@@ -958,12 +1007,7 @@ public:
}
}
- void GetScriptForMining(boost::shared_ptr<CReserveScript> &script) override;
- void ResetRequestCount(const uint256 &hash) override
- {
- LOCK(cs_wallet);
- mapRequestCount[hash] = 0;
- };
+ void GetScriptForMining(std::shared_ptr<CReserveScript> &script) override;
unsigned int GetKeyPoolSize()
{
@@ -1126,13 +1170,13 @@ public:
// ContainerType is meant to hold pair<CWalletTx *, int>, and be iterable
// so that each entry corresponds to each vIn, in order.
template <typename ContainerType>
-bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins)
+bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) const
{
// Fill in dummy signatures for fee calculation.
int nIn = 0;
for (const auto& coin : coins)
{
- const CScript& scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey;
+ const CScript& scriptPubKey = coin.txout.scriptPubKey;
SignatureData sigdata;
if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata))
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index d017965385..a90fa6dbbd 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -7,6 +7,7 @@
#include "base58.h"
#include "consensus/validation.h"
+#include "fs.h"
#include "validation.h" // For CheckTransaction
#include "protocol.h"
#include "serialize.h"
@@ -18,7 +19,6 @@
#include <atomic>
#include <boost/version.hpp>
-#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/thread.hpp>
@@ -33,7 +33,7 @@ static std::atomic<unsigned int> nWalletDBUpdateCounter;
bool CWalletDB::WriteName(const std::string& strAddress, const std::string& strName)
{
nWalletDBUpdateCounter++;
- return Write(make_pair(std::string("name"), strAddress), strName);
+ return batch.Write(std::make_pair(std::string("name"), strAddress), strName);
}
bool CWalletDB::EraseName(const std::string& strAddress)
@@ -41,38 +41,38 @@ bool CWalletDB::EraseName(const std::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.
nWalletDBUpdateCounter++;
- return Erase(make_pair(std::string("name"), strAddress));
+ return batch.Erase(std::make_pair(std::string("name"), strAddress));
}
bool CWalletDB::WritePurpose(const std::string& strAddress, const std::string& strPurpose)
{
nWalletDBUpdateCounter++;
- return Write(make_pair(std::string("purpose"), strAddress), strPurpose);
+ return batch.Write(std::make_pair(std::string("purpose"), strAddress), strPurpose);
}
bool CWalletDB::ErasePurpose(const std::string& strPurpose)
{
nWalletDBUpdateCounter++;
- return Erase(make_pair(std::string("purpose"), strPurpose));
+ return batch.Erase(std::make_pair(std::string("purpose"), strPurpose));
}
bool CWalletDB::WriteTx(const CWalletTx& wtx)
{
nWalletDBUpdateCounter++;
- return Write(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
+ return batch.Write(std::make_pair(std::string("tx"), wtx.GetHash()), wtx);
}
bool CWalletDB::EraseTx(uint256 hash)
{
nWalletDBUpdateCounter++;
- return Erase(std::make_pair(std::string("tx"), hash));
+ return batch.Erase(std::make_pair(std::string("tx"), hash));
}
bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, const CKeyMetadata& keyMeta)
{
nWalletDBUpdateCounter++;
- if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
+ if (!batch.Write(std::make_pair(std::string("keymeta"), vchPubKey),
keyMeta, false))
return false;
@@ -82,7 +82,7 @@ bool CWalletDB::WriteKey(const CPubKey& vchPubKey, const CPrivKey& vchPrivKey, c
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);
+ return batch.Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false);
}
bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
@@ -92,16 +92,16 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
const bool fEraseUnencryptedKey = true;
nWalletDBUpdateCounter++;
- if (!Write(std::make_pair(std::string("keymeta"), vchPubKey),
+ if (!batch.Write(std::make_pair(std::string("keymeta"), vchPubKey),
keyMeta))
return false;
- if (!Write(std::make_pair(std::string("ckey"), vchPubKey), vchCryptedSecret, false))
+ if (!batch.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));
+ batch.Erase(std::make_pair(std::string("key"), vchPubKey));
+ batch.Erase(std::make_pair(std::string("wkey"), vchPubKey));
}
return true;
}
@@ -109,92 +109,92 @@ bool CWalletDB::WriteCryptedKey(const CPubKey& vchPubKey,
bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
{
nWalletDBUpdateCounter++;
- return Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
+ return batch.Write(std::make_pair(std::string("mkey"), nID), kMasterKey, true);
}
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
{
nWalletDBUpdateCounter++;
- return Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
+ return batch.Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
}
bool CWalletDB::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMeta)
{
nWalletDBUpdateCounter++;
- if (!Write(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)), keyMeta))
+ if (!batch.Write(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest)), keyMeta))
return false;
- return Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
+ return batch.Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
}
bool CWalletDB::EraseWatchOnly(const CScript &dest)
{
nWalletDBUpdateCounter++;
- if (!Erase(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest))))
+ if (!batch.Erase(std::make_pair(std::string("watchmeta"), *(const CScriptBase*)(&dest))))
return false;
- return Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
+ return batch.Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
}
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
{
nWalletDBUpdateCounter++;
- Write(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
- return Write(std::string("bestblock_nomerkle"), locator);
+ batch.Write(std::string("bestblock"), CBlockLocator()); // Write empty block locator so versions that require a merkle branch automatically rescan
+ return batch.Write(std::string("bestblock_nomerkle"), locator);
}
bool CWalletDB::ReadBestBlock(CBlockLocator& locator)
{
- if (Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true;
- return Read(std::string("bestblock_nomerkle"), locator);
+ if (batch.Read(std::string("bestblock"), locator) && !locator.vHave.empty()) return true;
+ return batch.Read(std::string("bestblock_nomerkle"), locator);
}
bool CWalletDB::WriteOrderPosNext(int64_t nOrderPosNext)
{
nWalletDBUpdateCounter++;
- return Write(std::string("orderposnext"), nOrderPosNext);
+ return batch.Write(std::string("orderposnext"), nOrderPosNext);
}
bool CWalletDB::WriteDefaultKey(const CPubKey& vchPubKey)
{
nWalletDBUpdateCounter++;
- return Write(std::string("defaultkey"), vchPubKey);
+ return batch.Write(std::string("defaultkey"), vchPubKey);
}
bool CWalletDB::ReadPool(int64_t nPool, CKeyPool& keypool)
{
- return Read(std::make_pair(std::string("pool"), nPool), keypool);
+ return batch.Read(std::make_pair(std::string("pool"), nPool), keypool);
}
bool CWalletDB::WritePool(int64_t nPool, const CKeyPool& keypool)
{
nWalletDBUpdateCounter++;
- return Write(std::make_pair(std::string("pool"), nPool), keypool);
+ return batch.Write(std::make_pair(std::string("pool"), nPool), keypool);
}
bool CWalletDB::ErasePool(int64_t nPool)
{
nWalletDBUpdateCounter++;
- return Erase(std::make_pair(std::string("pool"), nPool));
+ return batch.Erase(std::make_pair(std::string("pool"), nPool));
}
bool CWalletDB::WriteMinVersion(int nVersion)
{
- return Write(std::string("minversion"), nVersion);
+ return batch.Write(std::string("minversion"), nVersion);
}
bool CWalletDB::ReadAccount(const std::string& strAccount, CAccount& account)
{
account.SetNull();
- return Read(make_pair(std::string("acc"), strAccount), account);
+ return batch.Read(std::make_pair(std::string("acc"), strAccount), account);
}
bool CWalletDB::WriteAccount(const std::string& strAccount, const CAccount& account)
{
- return Write(make_pair(std::string("acc"), strAccount), account);
+ return batch.Write(std::make_pair(std::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);
+ return batch.Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
}
bool CWalletDB::WriteAccountingEntry_Backend(const CAccountingEntry& acentry)
@@ -218,7 +218,7 @@ void CWalletDB::ListAccountCreditDebit(const std::string& strAccount, std::list<
{
bool fAllAccounts = (strAccount == "*");
- Dbc* pcursor = GetCursor();
+ Dbc* pcursor = batch.GetCursor();
if (!pcursor)
throw std::runtime_error(std::string(__func__) + ": cannot create DB cursor");
bool setRange = true;
@@ -229,7 +229,7 @@ void CWalletDB::ListAccountCreditDebit(const std::string& strAccount, std::list<
if (setRange)
ssKey << std::make_pair(std::string("acentry"), std::make_pair((fAllAccounts ? std::string("") : strAccount), uint64_t(0)));
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue, setRange);
+ int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue, setRange);
setRange = false;
if (ret == DB_NOTFOUND)
break;
@@ -560,7 +560,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
LOCK(pwallet->cs_wallet);
try {
int nMinVersion = 0;
- if (Read((std::string)"minversion", nMinVersion))
+ if (batch.Read((std::string)"minversion", nMinVersion))
{
if (nMinVersion > CLIENT_VERSION)
return DB_TOO_NEW;
@@ -568,7 +568,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
}
// Get cursor
- Dbc* pcursor = GetCursor();
+ Dbc* pcursor = batch.GetCursor();
if (!pcursor)
{
LogPrintf("Error getting wallet database cursor\n");
@@ -580,7 +580,7 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue);
+ int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
@@ -664,14 +664,14 @@ DBErrors CWalletDB::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWal
try {
int nMinVersion = 0;
- if (Read((std::string)"minversion", nMinVersion))
+ if (batch.Read((std::string)"minversion", nMinVersion))
{
if (nMinVersion > CLIENT_VERSION)
return DB_TOO_NEW;
}
// Get cursor
- Dbc* pcursor = GetCursor();
+ Dbc* pcursor = batch.GetCursor();
if (!pcursor)
{
LogPrintf("Error getting wallet database cursor\n");
@@ -683,7 +683,7 @@ DBErrors CWalletDB::FindWalletTx(std::vector<uint256>& vTxHash, std::vector<CWal
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
- int ret = ReadAtCursor(pcursor, ssKey, ssValue);
+ int ret = batch.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND)
break;
else if (ret != 0)
@@ -745,7 +745,7 @@ DBErrors CWalletDB::ZapSelectTx(std::vector<uint256>& vTxHashIn, std::vector<uin
}
else if ((*it) == hash) {
if(!EraseTx(hash)) {
- LogPrint("db", "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
+ LogPrint(BCLog::DB, "Transaction was found for deletion but returned database error: %s\n", hash.GetHex());
delerror = true;
}
vTxHashOut.push_back(hash);
@@ -797,9 +797,9 @@ void MaybeCompactWalletDB()
if (nLastFlushed != CWalletDB::GetUpdateCounter() && GetTime() - nLastWalletUpdate >= 2)
{
- const std::string& strFile = pwalletMain->strWalletFile;
- if (CDB::PeriodicFlush(strFile))
+ if (CDB::PeriodicFlush(pwalletMain->GetDBHandle())) {
nLastFlushed = CWalletDB::GetUpdateCounter();
+ }
}
fOneThread = false;
}
@@ -842,12 +842,12 @@ bool CWalletDB::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey, CDa
return true;
}
-bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr)
+bool CWalletDB::VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr)
{
return CDB::VerifyEnvironment(walletFile, dataDir, errorStr);
}
-bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr)
+bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr)
{
return CDB::VerifyDatabaseFile(walletFile, dataDir, errorStr, warningStr, CWalletDB::Recover);
}
@@ -855,20 +855,20 @@ bool CWalletDB::VerifyDatabaseFile(const std::string& walletFile, const boost::f
bool CWalletDB::WriteDestData(const std::string &address, const std::string &key, const std::string &value)
{
nWalletDBUpdateCounter++;
- return Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
+ return batch.Write(std::make_pair(std::string("destdata"), std::make_pair(address, key)), value);
}
bool CWalletDB::EraseDestData(const std::string &address, const std::string &key)
{
nWalletDBUpdateCounter++;
- return Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
+ return batch.Erase(std::make_pair(std::string("destdata"), std::make_pair(address, key)));
}
bool CWalletDB::WriteHDChain(const CHDChain& chain)
{
nWalletDBUpdateCounter++;
- return Write(std::string("hdchain"), chain);
+ return batch.Write(std::string("hdchain"), chain);
}
void CWalletDB::IncrementUpdateCounter()
@@ -880,3 +880,28 @@ unsigned int CWalletDB::GetUpdateCounter()
{
return nWalletDBUpdateCounter;
}
+
+bool CWalletDB::TxnBegin()
+{
+ return batch.TxnBegin();
+}
+
+bool CWalletDB::TxnCommit()
+{
+ return batch.TxnCommit();
+}
+
+bool CWalletDB::TxnAbort()
+{
+ return batch.TxnAbort();
+}
+
+bool CWalletDB::ReadVersion(int& nVersion)
+{
+ return batch.ReadVersion(nVersion);
+}
+
+bool CWalletDB::WriteVersion(int nVersion)
+{
+ return batch.WriteVersion(nVersion);
+}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 271c1d66c9..cd9fe279c5 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -17,6 +17,21 @@
#include <utility>
#include <vector>
+/**
+ * Overview of wallet database classes:
+ *
+ * - CDBEnv is an environment in which the database exists (has no analog in dbwrapper.h)
+ * - CWalletDBWrapper represents a wallet database (similar to CDBWrapper in dbwrapper.h)
+ * - CDB is a low-level database transaction (similar to CDBBatch in dbwrapper.h)
+ * - CWalletDB is a modifier object for the wallet, and encapsulates a database
+ * transaction as well as methods to act on the database (no analog in
+ * dbwrapper.h)
+ *
+ * The latter two are named confusingly, in contrast to what the names CDB
+ * and CWalletDB suggest they are transient transaction objects and don't
+ * represent the database itself.
+ */
+
static const bool DEFAULT_FLUSHWALLET = true;
class CAccount;
@@ -118,11 +133,16 @@ public:
}
};
-/** Access to the wallet database */
-class CWalletDB : public CDB
+/** Access to the wallet database.
+ * This should really be named CWalletDBBatch, as it represents a single transaction at the
+ * database. It will be committed when the object goes out of scope.
+ * Optionally (on by default) it will flush to disk as well.
+ */
+class CWalletDB
{
public:
- CWalletDB(const std::string& strFilename, const char* pszMode = "r+", bool _fFlushOnClose = true) : CDB(strFilename, pszMode, _fFlushOnClose)
+ CWalletDB(CWalletDBWrapper& dbw, const char* pszMode = "r+", bool _fFlushOnClose = true) :
+ batch(dbw, pszMode, _fFlushOnClose)
{
}
@@ -185,16 +205,29 @@ public:
/* Function to determine if a certain KV/key-type is a key (cryptographical key) type */
static bool IsKeyType(const std::string& strType);
/* verifies the database environment */
- static bool VerifyEnvironment(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& errorStr);
+ static bool VerifyEnvironment(const std::string& walletFile, const fs::path& dataDir, std::string& errorStr);
/* verifies the database file */
- static bool VerifyDatabaseFile(const std::string& walletFile, const boost::filesystem::path& dataDir, std::string& warningStr, std::string& errorStr);
+ static bool VerifyDatabaseFile(const std::string& walletFile, const fs::path& dataDir, std::string& warningStr, std::string& errorStr);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain& chain);
static void IncrementUpdateCounter();
static unsigned int GetUpdateCounter();
+
+ //! Begin a new transaction
+ bool TxnBegin();
+ //! Commit current transaction
+ bool TxnCommit();
+ //! Abort current transaction
+ bool TxnAbort();
+ //! Read wallet version
+ bool ReadVersion(int& nVersion);
+ //! Write wallet version
+ bool WriteVersion(int nVersion);
private:
+ CDB batch;
+
CWalletDB(const CWalletDB&);
void operator=(const CWalletDB&);
};