Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index cf4cbe930..105bd9da0 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -1,933 +1,932 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/bdb.h>
#include <wallet/db.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <cstdint>
#ifndef WIN32
#include <sys/stat.h>
#endif
namespace {
//! Make sure database has a unique fileid within the environment. If it
//! doesn't, throw an error. BDB caches do not work properly when more than one
//! open database has the same fileid (values written to one database may show
//! up in reads to other databases).
//!
//! BerkeleyDB generates unique fileids by default
//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
//! so bitcoin should never create different databases with the same fileid, but
//! this error can be triggered if users manually copy database files.
void CheckUniqueFileid(const BerkeleyEnvironment &env,
const std::string &filename, Db &db,
WalletDatabaseFileId &fileid) {
if (env.IsMock()) {
return;
}
int ret = db.get_mpf()->get_fileid(fileid.value);
if (ret != 0) {
throw std::runtime_error(
strprintf("BerkeleyDatabase: Can't open database %s (get_fileid "
"failed with %d)",
filename, ret));
}
for (const auto &item : env.m_fileids) {
if (fileid == item.second && &fileid != &item.second) {
throw std::runtime_error(
strprintf("BerkeleyDatabase: Can't open database %s "
"(duplicates fileid %s "
"from %s)",
filename, HexStr(item.second.value), item.first));
}
}
}
RecursiveMutex cs_db;
//! Map from directory name to db environment.
std::map<std::string, std::weak_ptr<BerkeleyEnvironment>>
g_dbenvs GUARDED_BY(cs_db);
} // namespace
bool WalletDatabaseFileId::operator==(const WalletDatabaseFileId &rhs) const {
return memcmp(value, &rhs.value, sizeof(value)) == 0;
}
/**
* @param[in] wallet_path Path to wallet directory. Or (for backwards
* compatibility only) a path to a berkeley btree data file inside a wallet
* directory.
* @param[out] database_filename Filename of berkeley btree data file inside the
* wallet directory.
* @return A shared pointer to the BerkeleyEnvironment object for the wallet
* directory, never empty because ~BerkeleyEnvironment erases the weak pointer
* from the g_dbenvs map.
* @post A new BerkeleyEnvironment weak pointer is inserted into g_dbenvs if the
* directory path key was not already in the map.
*/
std::shared_ptr<BerkeleyEnvironment>
GetWalletEnv(const fs::path &wallet_path, std::string &database_filename) {
fs::path env_directory;
SplitWalletPath(wallet_path, env_directory, database_filename);
LOCK(cs_db);
auto inserted = g_dbenvs.emplace(env_directory.string(),
std::weak_ptr<BerkeleyEnvironment>());
if (inserted.second) {
auto env =
std::make_shared<BerkeleyEnvironment>(env_directory.string());
inserted.first->second = env;
return env;
}
return inserted.first->second.lock();
}
//
// BerkeleyBatch
//
void BerkeleyEnvironment::Close() {
if (!fDbEnvInit) {
return;
}
fDbEnvInit = false;
for (auto &db : m_databases) {
BerkeleyDatabase &database = db.second.get();
assert(database.m_refcount <= 0);
if (database.m_db) {
database.m_db->close(0);
database.m_db.reset();
}
}
FILE *error_file = nullptr;
dbenv->get_errfile(&error_file);
int ret = dbenv->close(0);
if (ret != 0) {
LogPrintf("BerkeleyEnvironment::Close: Error %d closing database "
"environment: %s\n",
ret, DbEnv::strerror(ret));
}
if (!fMockDb) {
DbEnv(u_int32_t(0)).remove(strPath.c_str(), 0);
}
if (error_file) {
fclose(error_file);
}
UnlockDirectory(strPath, ".walletlock");
}
void BerkeleyEnvironment::Reset() {
dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS));
fDbEnvInit = false;
fMockDb = false;
}
BerkeleyEnvironment::BerkeleyEnvironment(const fs::path &dir_path)
: strPath(dir_path.string()) {
Reset();
}
BerkeleyEnvironment::~BerkeleyEnvironment() {
LOCK(cs_db);
g_dbenvs.erase(strPath);
Close();
}
bool BerkeleyEnvironment::Open(bilingual_str &err) {
if (fDbEnvInit) {
return true;
}
fs::path pathIn = strPath;
TryCreateDirectories(pathIn);
if (!LockDirectory(pathIn, ".walletlock")) {
LogPrintf("Cannot obtain a lock on wallet directory %s. Another "
"instance of bitcoin may be using it.\n",
strPath);
err = strprintf(_("Error initializing wallet database environment %s!"),
Directory());
return false;
}
fs::path pathLogDir = pathIn / "database";
TryCreateDirectories(pathLogDir);
fs::path pathErrorFile = pathIn / "db.log";
LogPrintf("BerkeleyEnvironment::Open: LogDir=%s ErrorFile=%s\n",
pathLogDir.string(), pathErrorFile.string());
unsigned int nEnvFlags = 0;
if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) {
nEnvFlags |= DB_PRIVATE;
}
dbenv->set_lg_dir(pathLogDir.string().c_str());
// 1 MiB should be enough for just the wallet
dbenv->set_cachesize(0, 0x100000, 1);
dbenv->set_lg_bsize(0x10000);
dbenv->set_lg_max(1048576);
dbenv->set_lk_max_locks(40000);
dbenv->set_lk_max_objects(40000);
/// debug
dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a"));
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
int ret =
dbenv->open(strPath.c_str(),
DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL |
DB_INIT_TXN | DB_THREAD | DB_RECOVER | nEnvFlags,
S_IRUSR | S_IWUSR);
if (ret != 0) {
LogPrintf("BerkeleyEnvironment::Open: Error %d opening database "
"environment: %s\n",
ret, DbEnv::strerror(ret));
int ret2 = dbenv->close(0);
if (ret2 != 0) {
LogPrintf("BerkeleyEnvironment::Open: Error %d closing failed "
"database environment: %s\n",
ret2, DbEnv::strerror(ret2));
}
Reset();
err = strprintf(_("Error initializing wallet database environment %s!"),
Directory());
if (ret == DB_RUNRECOVERY) {
err += Untranslated(" ") +
_("This error could occur if this wallet was not shutdown "
"cleanly and was last loaded using a build with a newer "
"version of Berkeley DB. If so, please use the software "
"that last loaded this wallet");
}
return false;
}
fDbEnvInit = true;
fMockDb = false;
return true;
}
//! Construct an in-memory mock Berkeley environment for testing
BerkeleyEnvironment::BerkeleyEnvironment() {
Reset();
LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::MakeMock\n");
dbenv->set_cachesize(1, 0, 1);
dbenv->set_lg_bsize(10485760 * 4);
dbenv->set_lg_max(10485760);
dbenv->set_lk_max_locks(10000);
dbenv->set_lk_max_objects(10000);
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
int ret =
dbenv->open(nullptr,
DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL |
DB_INIT_TXN | DB_THREAD | DB_PRIVATE,
S_IRUSR | S_IWUSR);
if (ret > 0) {
throw std::runtime_error(
strprintf("BerkeleyEnvironment::MakeMock: Error %d opening "
"database environment.",
ret));
}
fDbEnvInit = true;
fMockDb = true;
}
BerkeleyBatch::SafeDbt::SafeDbt() {
m_dbt.set_flags(DB_DBT_MALLOC);
}
BerkeleyBatch::SafeDbt::SafeDbt(void *data, size_t size) : m_dbt(data, size) {}
BerkeleyBatch::SafeDbt::~SafeDbt() {
if (m_dbt.get_data() != nullptr) {
// Clear memory, e.g. in case it was a private key
memory_cleanse(m_dbt.get_data(), m_dbt.get_size());
// under DB_DBT_MALLOC, data is malloced by the Dbt, but must be
// freed by the caller.
// https://docs.oracle.com/cd/E17275_01/html/api_reference/C/dbt.html
if (m_dbt.get_flags() & DB_DBT_MALLOC) {
free(m_dbt.get_data());
}
}
}
const void *BerkeleyBatch::SafeDbt::get_data() const {
return m_dbt.get_data();
}
u_int32_t BerkeleyBatch::SafeDbt::get_size() const {
return m_dbt.get_size();
}
BerkeleyBatch::SafeDbt::operator Dbt *() {
return &m_dbt;
}
bool BerkeleyDatabase::Verify(bilingual_str &errorStr) {
fs::path walletDir = env->Directory();
fs::path file_path = walletDir / strFile;
LogPrintf("Using BerkeleyDB version %s\n", BerkeleyDatabaseVersion());
LogPrintf("Using wallet %s\n", file_path.string());
if (!env->Open(errorStr)) {
return false;
}
if (fs::exists(file_path)) {
assert(m_refcount == 0);
Db db(env->dbenv.get(), 0);
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
if (result != 0) {
errorStr =
strprintf(_("%s corrupt. Try using the wallet tool "
"bitcoin-wallet to salvage or restoring a backup."),
file_path);
return false;
}
}
// also return true if files does not exists
return true;
}
void BerkeleyEnvironment::CheckpointLSN(const std::string &strFile) {
dbenv->txn_checkpoint(0, 0, 0);
if (fMockDb) {
return;
}
dbenv->lsn_reset(strFile.c_str(), 0);
}
BerkeleyDatabase::~BerkeleyDatabase() {
if (env) {
LOCK(cs_db);
env->CloseDb(strFile);
assert(!m_db);
size_t erased = env->m_databases.erase(strFile);
assert(erased == 1);
env->m_fileids.erase(strFile);
}
}
BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase &database, const char *pszMode,
bool fFlushOnCloseIn)
: pdb(nullptr), activeTxn(nullptr), m_cursor(nullptr),
m_database(database) {
database.AddRef();
database.Open(pszMode);
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
fFlushOnClose = fFlushOnCloseIn;
env = database.env.get();
pdb = database.m_db.get();
strFile = database.strFile;
bool fCreate = strchr(pszMode, 'c') != nullptr;
if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly;
fReadOnly = false;
Write(std::string("version"), CLIENT_VERSION);
fReadOnly = fTmp;
}
}
void BerkeleyDatabase::Open(const char *pszMode) {
bool fCreate = strchr(pszMode, 'c') != nullptr;
unsigned int nFlags = DB_THREAD;
if (fCreate) {
nFlags |= DB_CREATE;
}
{
LOCK(cs_db);
bilingual_str open_err;
if (!env->Open(open_err)) {
throw std::runtime_error(
"BerkeleyDatabase: Failed to open database environment.");
}
if (m_db == nullptr) {
int ret;
std::unique_ptr<Db> pdb_temp =
std::make_unique<Db>(env->dbenv.get(), 0);
bool fMockDb = env->IsMock();
if (fMockDb) {
DbMpoolFile *mpf = pdb_temp->get_mpf();
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
if (ret != 0) {
throw std::runtime_error(strprintf(
"BerkeleyDatabase: Failed to configure for no "
"temp file backing for database %s",
strFile));
}
}
ret = pdb_temp->open(
nullptr, // Txn pointer
fMockDb ? nullptr : strFile.c_str(), // Filename
fMockDb ? strFile.c_str() : "main", // Logical db name
DB_BTREE, // Database type
nFlags, // Flags
0);
if (ret != 0) {
throw std::runtime_error(strprintf(
"BerkeleyDatabase: Error %d, can't open database %s", ret,
strFile));
}
- m_file_path = (env->Directory() / strFile).string();
// Call CheckUniqueFileid on the containing BDB environment to
// avoid BDB data consistency bugs that happen when different data
// files in the same environment have the same fileid.
CheckUniqueFileid(*env, strFile, *pdb_temp,
this->env->m_fileids[strFile]);
m_db.reset(pdb_temp.release());
}
}
}
void BerkeleyBatch::Flush() {
if (activeTxn) {
return;
}
// Flush database activity from memory pool to disk log
unsigned int nMinutes = 0;
if (fReadOnly) {
nMinutes = 1;
}
// env is nullptr for dummy databases (i.e. in tests). Don't actually flush
// if env is nullptr so we don't segfault
if (env) {
env->dbenv->txn_checkpoint(
nMinutes
? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024
: 0,
nMinutes, 0);
}
}
void BerkeleyDatabase::IncrementUpdateCounter() {
++nUpdateCounter;
}
BerkeleyBatch::~BerkeleyBatch() {
Close();
m_database.RemoveRef();
}
void BerkeleyBatch::Close() {
if (!pdb) {
return;
}
if (activeTxn) {
activeTxn->abort();
}
activeTxn = nullptr;
pdb = nullptr;
CloseCursor();
if (fFlushOnClose) {
Flush();
}
}
void BerkeleyEnvironment::CloseDb(const std::string &strFile) {
LOCK(cs_db);
auto it = m_databases.find(strFile);
assert(it != m_databases.end());
BerkeleyDatabase &database = it->second.get();
if (database.m_db) {
// Close the database handle
database.m_db->close(0);
database.m_db.reset();
}
}
void BerkeleyEnvironment::ReloadDbEnv() {
// Make sure that no Db's are in use
AssertLockNotHeld(cs_db);
std::unique_lock<RecursiveMutex> lock(cs_db);
m_db_in_use.wait(lock, [this]() {
for (auto &db : m_databases) {
if (db.second.get().m_refcount > 0) {
return false;
}
}
return true;
});
std::vector<std::string> filenames;
for (auto it : m_databases) {
filenames.push_back(it.first);
}
// Close the individual Db's
for (const std::string &filename : filenames) {
CloseDb(filename);
}
// Reset the environment
// This will flush and close the environment
Flush(true);
Reset();
bilingual_str open_err;
Open(open_err);
}
bool BerkeleyDatabase::Rewrite(const char *pszSkip) {
while (true) {
{
LOCK(cs_db);
if (m_refcount <= 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
m_refcount = -1;
bool fSuccess = true;
LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
std::string strFileRes = strFile + ".rewrite";
{ // surround usage of db with extra {}
BerkeleyBatch db(*this, "r");
std::unique_ptr<Db> pdbCopy =
std::make_unique<Db>(env->dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
strFileRes.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
DB_CREATE, // Flags
0);
if (ret > 0) {
LogPrintf("BerkeleyBatch::Rewrite: Can't create "
"database file %s\n",
strFileRes);
fSuccess = false;
}
if (db.StartCursor()) {
while (fSuccess) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
bool complete;
bool ret1 =
db.ReadAtCursor(ssKey, ssValue, complete);
if (complete) {
break;
}
if (!ret1) {
fSuccess = false;
break;
}
if (pszSkip &&
strncmp(ssKey.data(), pszSkip,
std::min(ssKey.size(),
strlen(pszSkip))) == 0) {
continue;
}
if (strncmp(ssKey.data(), "\x07version", 8) == 0) {
// Update version:
ssValue.clear();
ssValue << CLIENT_VERSION;
}
Dbt datKey(ssKey.data(), ssKey.size());
Dbt datValue(ssValue.data(), ssValue.size());
int ret2 = pdbCopy->put(nullptr, &datKey, &datValue,
DB_NOOVERWRITE);
if (ret2 > 0) {
fSuccess = false;
}
}
db.CloseCursor();
}
if (fSuccess) {
db.Close();
env->CloseDb(strFile);
if (pdbCopy->close(0)) {
fSuccess = false;
}
} else {
pdbCopy->close(0);
}
}
if (fSuccess) {
Db dbA(env->dbenv.get(), 0);
if (dbA.remove(strFile.c_str(), nullptr, 0)) {
fSuccess = false;
}
Db dbB(env->dbenv.get(), 0);
if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(),
0)) {
fSuccess = false;
}
}
if (!fSuccess) {
LogPrintf("BerkeleyBatch::Rewrite: Failed to rewrite "
"database file %s\n",
strFileRes);
}
return fSuccess;
}
}
UninterruptibleSleep(std::chrono::milliseconds{100});
}
}
void BerkeleyEnvironment::Flush(bool fShutdown) {
int64_t nStart = GetTimeMillis();
// Flush log data to the actual data file on all files that are not in use
LogPrint(BCLog::WALLETDB, "BerkeleyEnvironment::Flush: [%s] Flush(%s)%s\n",
strPath, fShutdown ? "true" : "false",
fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit) {
return;
}
{
LOCK(cs_db);
bool no_dbs_accessed = true;
for (auto &db_it : m_databases) {
std::string strFile = db_it.first;
int nRefCount = db_it.second.get().m_refcount;
if (nRefCount < 0) {
continue;
}
LogPrint(
BCLog::WALLETDB,
"BerkeleyEnvironment::Flush: Flushing %s (refcount = %d)...\n",
strFile, nRefCount);
if (nRefCount == 0) {
// Move log data to the dat file
CloseDb(strFile);
LogPrint(BCLog::WALLETDB,
"BerkeleyEnvironment::Flush: %s checkpoint\n",
strFile);
dbenv->txn_checkpoint(0, 0, 0);
LogPrint(BCLog::WALLETDB,
"BerkeleyEnvironment::Flush: %s detach\n", strFile);
if (!fMockDb) {
dbenv->lsn_reset(strFile.c_str(), 0);
}
LogPrint(BCLog::WALLETDB,
"BerkeleyEnvironment::Flush: %s closed\n", strFile);
nRefCount = -1;
} else {
no_dbs_accessed = false;
}
}
LogPrint(BCLog::WALLETDB,
"BerkeleyEnvironment::Flush: Flush(%s)%s took %15dms\n",
fShutdown ? "true" : "false",
fDbEnvInit ? "" : " database not started",
GetTimeMillis() - nStart);
if (fShutdown) {
char **listp;
if (no_dbs_accessed) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb) {
fs::remove_all(fs::path(strPath) / "database");
}
}
}
}
}
bool BerkeleyDatabase::PeriodicFlush() {
// Don't flush if we can't acquire the lock.
TRY_LOCK(cs_db, lockDb);
if (!lockDb) {
return false;
}
// Don't flush if any databases are in use
for (auto &it : env->m_databases) {
if (it.second.get().m_refcount > 0) {
return false;
}
}
// Don't flush if there haven't been any batch writes for this database.
if (m_refcount < 0) {
return false;
}
LogPrint(BCLog::WALLETDB, "Flushing %s\n", strFile);
int64_t nStart = GetTimeMillis();
// Flush wallet file so it's self contained
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
m_refcount = -1;
LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile,
GetTimeMillis() - nStart);
return true;
}
bool BerkeleyDatabase::Backup(const std::string &strDest) const {
while (true) {
{
LOCK(cs_db);
if (m_refcount <= 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
// Copy wallet file.
fs::path pathSrc = env->Directory() / strFile;
fs::path pathDest(strDest);
if (fs::is_directory(pathDest)) {
pathDest /= strFile;
}
try {
if (fs::equivalent(pathSrc, pathDest)) {
LogPrintf("cannot backup to wallet source file %s\n",
pathDest.string());
return false;
}
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(),
fsbridge::get_filesystem_error_message(e));
return false;
}
}
}
UninterruptibleSleep(std::chrono::milliseconds{100});
}
}
void BerkeleyDatabase::Flush() {
env->Flush(false);
}
void BerkeleyDatabase::Close() {
env->Flush(true);
}
void BerkeleyDatabase::ReloadDbEnv() {
env->ReloadDbEnv();
}
bool BerkeleyBatch::StartCursor() {
assert(!m_cursor);
if (!pdb) {
return false;
}
int ret = pdb->cursor(nullptr, &m_cursor, 0);
return ret == 0;
}
bool BerkeleyBatch::ReadAtCursor(CDataStream &ssKey, CDataStream &ssValue,
bool &complete) {
complete = false;
if (m_cursor == nullptr) {
return false;
}
// Read at cursor
SafeDbt datKey;
SafeDbt datValue;
int ret = m_cursor->get(datKey, datValue, DB_NEXT);
if (ret == DB_NOTFOUND) {
complete = true;
}
if (ret != 0) {
return false;
} else if (datKey.get_data() == nullptr || datValue.get_data() == nullptr) {
return false;
}
// Convert to streams
ssKey.SetType(SER_DISK);
ssKey.clear();
ssKey.write((char *)datKey.get_data(), datKey.get_size());
ssValue.SetType(SER_DISK);
ssValue.clear();
ssValue.write((char *)datValue.get_data(), datValue.get_size());
return true;
}
void BerkeleyBatch::CloseCursor() {
if (!m_cursor) {
return;
}
m_cursor->close();
m_cursor = nullptr;
}
bool BerkeleyBatch::TxnBegin() {
if (!pdb || activeTxn) {
return false;
}
DbTxn *ptxn = env->TxnBegin();
if (!ptxn) {
return false;
}
activeTxn = ptxn;
return true;
}
bool BerkeleyBatch::TxnCommit() {
if (!pdb || !activeTxn) {
return false;
}
int ret = activeTxn->commit(0);
activeTxn = nullptr;
return (ret == 0);
}
bool BerkeleyBatch::TxnAbort() {
if (!pdb || !activeTxn) {
return false;
}
int ret = activeTxn->abort();
activeTxn = nullptr;
return (ret == 0);
}
std::string BerkeleyDatabaseVersion() {
return DbEnv::version(nullptr, nullptr, nullptr);
}
bool BerkeleyBatch::ReadKey(CDataStream &&key, CDataStream &value) {
if (!pdb) {
return false;
}
SafeDbt datKey(key.data(), key.size());
SafeDbt datValue;
int ret = pdb->get(activeTxn, datKey, datValue, 0);
if (ret == 0 && datValue.get_data() != nullptr) {
value.write((char *)datValue.get_data(), datValue.get_size());
return true;
}
return false;
}
bool BerkeleyBatch::WriteKey(CDataStream &&key, CDataStream &&value,
bool overwrite) {
if (!pdb) {
return false;
}
if (fReadOnly) {
assert(!"Write called on database in read-only mode");
}
SafeDbt datKey(key.data(), key.size());
SafeDbt datValue(value.data(), value.size());
int ret =
pdb->put(activeTxn, datKey, datValue, (overwrite ? 0 : DB_NOOVERWRITE));
return (ret == 0);
}
bool BerkeleyBatch::EraseKey(CDataStream &&key) {
if (!pdb) {
return false;
}
if (fReadOnly) {
assert(!"Erase called on database in read-only mode");
}
SafeDbt datKey(key.data(), key.size());
int ret = pdb->del(activeTxn, datKey, 0);
return (ret == 0 || ret == DB_NOTFOUND);
}
bool BerkeleyBatch::HasKey(CDataStream &&key) {
if (!pdb) {
return false;
}
SafeDbt datKey(key.data(), key.size());
int ret = pdb->exists(activeTxn, datKey, 0);
return ret == 0;
}
void BerkeleyDatabase::AddRef() {
LOCK(cs_db);
if (m_refcount < 0) {
m_refcount = 1;
} else {
m_refcount++;
}
}
void BerkeleyDatabase::RemoveRef() {
LOCK(cs_db);
m_refcount--;
if (env) {
env->m_db_in_use.notify_all();
}
}
std::unique_ptr<DatabaseBatch>
BerkeleyDatabase::MakeBatch(const char *mode, bool flush_on_close) {
return std::make_unique<BerkeleyBatch>(*this, mode, flush_on_close);
}
bool ExistsBerkeleyDatabase(const fs::path &path) {
fs::path env_directory;
std::string data_filename;
SplitWalletPath(path, env_directory, data_filename);
return IsBerkeleyBtree(env_directory / data_filename);
}
std::unique_ptr<BerkeleyDatabase>
MakeBerkeleyDatabase(const fs::path &path, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error) {
std::unique_ptr<BerkeleyDatabase> db;
{
// Lock env.m_databases until insert in BerkeleyDatabase constructor
LOCK(cs_db);
std::string data_filename;
std::shared_ptr<BerkeleyEnvironment> env =
GetWalletEnv(path, data_filename);
if (env->m_databases.count(data_filename)) {
error = Untranslated(strprintf(
"Refusing to load database. Data file '%s' is already loaded.",
(env->Directory() / data_filename).string()));
status = DatabaseStatus::FAILED_ALREADY_LOADED;
return nullptr;
}
db = std::make_unique<BerkeleyDatabase>(std::move(env),
std::move(data_filename));
}
if (options.verify && !db->Verify(error)) {
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
status = DatabaseStatus::SUCCESS;
return db;
}
diff --git a/src/wallet/bdb.h b/src/wallet/bdb.h
index df98ba783..dfb724047 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -1,253 +1,258 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2020 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_BDB_H
#define BITCOIN_WALLET_BDB_H
#include <clientversion.h>
#include <fs.h>
#include <serialize.h>
#include <streams.h>
#include <util/system.h>
#include <wallet/db.h>
#include <db_cxx.h>
#include <atomic>
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
struct bilingual_str;
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
static const bool DEFAULT_WALLET_PRIVDB = true;
struct WalletDatabaseFileId {
u_int8_t value[DB_FILE_ID_LEN];
bool operator==(const WalletDatabaseFileId &rhs) const;
};
class BerkeleyDatabase;
class BerkeleyEnvironment {
private:
bool fDbEnvInit;
bool fMockDb;
// Don't change into fs::path, as that can result in
// shutdown problems/crashes caused by a static initialized internal
// pointer.
std::string strPath;
public:
std::unique_ptr<DbEnv> dbenv;
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
std::unordered_map<std::string, WalletDatabaseFileId> m_fileids;
std::condition_variable_any m_db_in_use;
BerkeleyEnvironment(const fs::path &env_directory);
BerkeleyEnvironment();
~BerkeleyEnvironment();
void Reset();
void MakeMock();
bool IsMock() const { return fMockDb; }
bool IsInitialized() const { return fDbEnvInit; }
fs::path Directory() const { return strPath; }
bool Open(bilingual_str &error);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string &strFile);
void CloseDb(const std::string &strFile);
void ReloadDbEnv();
DbTxn *TxnBegin(int flags = DB_TXN_WRITE_NOSYNC) {
DbTxn *ptxn = nullptr;
int ret = dbenv->txn_begin(nullptr, &ptxn, flags);
if (!ptxn || ret != 0) {
return nullptr;
}
return ptxn;
}
};
/** Get BerkeleyEnvironment and database filename given a wallet path. */
std::shared_ptr<BerkeleyEnvironment>
GetWalletEnv(const fs::path &wallet_path, std::string &database_filename);
/** Check format of database file */
bool IsBerkeleyBtree(const fs::path &path);
class BerkeleyBatch;
/**
* An instance of this class represents one database.
* For BerkeleyDB this is just a (env, strFile) tuple.
*/
class BerkeleyDatabase : public WalletDatabase {
public:
BerkeleyDatabase() = delete;
/** Create DB handle to real database */
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> envIn,
std::string filename)
: WalletDatabase(), env(std::move(envIn)),
strFile(std::move(filename)) {
auto inserted =
this->env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
}
~BerkeleyDatabase() override;
/**
* Open the database if it is not already opened.
* Dummy function, doesn't do anything right now, but is needed for class
* abstraction
*/
void Open(const char *mode) override;
/**
* Rewrite the entire database on disk, with the exception of key pszSkip if
* non-zero
*/
bool Rewrite(const char *pszSkip = nullptr) override;
/** Indicate the a new database user has began using the database. */
void AddRef() override;
/**
* Indicate that database user has stopped using the database and that it
* could be flushed or closed.
*/
void RemoveRef() override;
/**
* Back up the entire database to a file.
*/
bool Backup(const std::string &strDest) const override;
/**
* Make sure all changes are flushed to database file.
*/
void Flush() override;
/**
* Flush to the database file and close the database.
* Also close the environment if no other databases are open in it.
*/
void Close() override;
/**
* flush the wallet passively (TRY_LOCK)
* ideal to be called periodically
*/
bool PeriodicFlush() override;
void IncrementUpdateCounter() override;
void ReloadDbEnv() override;
/** Verifies the environment and database file */
bool Verify(bilingual_str &error);
+ /** Return path to main database filename */
+ std::string Filename() override {
+ return (env->Directory() / strFile).string();
+ }
+
/**
* Pointer to shared database environment.
*
* Normally there is only one BerkeleyDatabase object per
* BerkeleyEnvivonment, but in the special, backwards compatible case where
* multiple wallet BDB data files are loaded from the same directory, this
* will point to a shared instance that gets freed when the last data file
* is closed.
*/
std::shared_ptr<BerkeleyEnvironment> env;
/**
* Database pointer. This is initialized lazily and reset during flushes,
* so it can be null.
*/
std::unique_ptr<Db> m_db;
std::string strFile;
/** Make a BerkeleyBatch connected to this database */
std::unique_ptr<DatabaseBatch>
MakeBatch(const char *mode = "r+", bool flush_on_close = true) override;
};
/** RAII class that provides access to a Berkeley database */
class BerkeleyBatch : public DatabaseBatch {
/** RAII class that automatically cleanses its data on destruction */
class SafeDbt final {
Dbt m_dbt;
public:
// construct Dbt with internally-managed data
SafeDbt();
// construct Dbt with provided data
SafeDbt(void *data, size_t size);
~SafeDbt();
// delegate to Dbt
const void *get_data() const;
u_int32_t get_size() const;
// conversion operator to access the underlying Dbt
operator Dbt *();
};
private:
bool ReadKey(CDataStream &&key, CDataStream &value) override;
bool WriteKey(CDataStream &&key, CDataStream &&value,
bool overwrite = true) override;
bool EraseKey(CDataStream &&key) override;
bool HasKey(CDataStream &&key) override;
protected:
Db *pdb;
std::string strFile;
DbTxn *activeTxn;
Dbc *m_cursor;
bool fReadOnly;
bool fFlushOnClose;
BerkeleyEnvironment *env;
BerkeleyDatabase &m_database;
public:
explicit BerkeleyBatch(BerkeleyDatabase &database,
const char *pszMode = "r+",
bool fFlushOnCloseIn = true);
~BerkeleyBatch() override;
BerkeleyBatch(const BerkeleyBatch &) = delete;
BerkeleyBatch &operator=(const BerkeleyBatch &) = delete;
void Flush() override;
void Close() override;
bool StartCursor() override;
bool ReadAtCursor(CDataStream &ssKey, CDataStream &ssValue,
bool &complete) override;
void CloseCursor() override;
bool TxnBegin() override;
bool TxnCommit() override;
bool TxnAbort() override;
};
std::string BerkeleyDatabaseVersion();
//! Check if Berkeley database exists at specified path.
bool ExistsBerkeleyDatabase(const fs::path &path);
//! Return object giving access to Berkeley database at specified path.
std::unique_ptr<BerkeleyDatabase>
MakeBerkeleyDatabase(const fs::path &path, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error);
#endif // BITCOIN_WALLET_BDB_H
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 37fdf43c5..aa14d4d3d 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,32 +1,25 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 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 <fs.h>
#include <wallet/db.h>
#include <string>
void SplitWalletPath(const fs::path &wallet_path, fs::path &env_directory,
std::string &database_filename) {
if (fs::is_regular_file(wallet_path)) {
// Special case for backwards compatibility: if wallet path points to an
// existing file, treat it as the path to a BDB data file in a parent
// directory that also contains BDB log files.
env_directory = wallet_path.parent_path();
database_filename = wallet_path.filename().string();
} else {
// Normal case: Interpret wallet path as a directory path containing
// data and log files.
env_directory = wallet_path;
database_filename = "wallet.dat";
}
}
-
-fs::path WalletDataFilePath(const fs::path &wallet_path) {
- fs::path env_directory;
- std::string database_filename;
- SplitWalletPath(wallet_path, env_directory, database_filename);
- return env_directory / database_filename;
-}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 64797c8d9..62b3e59ef 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,249 +1,246 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 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_DB_H
#define BITCOIN_WALLET_DB_H
#include <clientversion.h>
#include <fs.h>
#include <streams.h>
#include <support/allocators/secure.h>
#include <atomic>
#include <memory>
#include <string>
struct bilingual_str;
-/**
- * Given a wallet directory path or legacy file path, return path to main data
- * file in the wallet database.
- */
-fs::path WalletDataFilePath(const fs::path &wallet_path);
void SplitWalletPath(const fs::path &wallet_path, fs::path &env_directory,
std::string &database_filename);
/** RAII class that provides access to a WalletDatabase */
class DatabaseBatch {
private:
virtual bool ReadKey(CDataStream &&key, CDataStream &value) = 0;
virtual bool WriteKey(CDataStream &&key, CDataStream &&value,
bool overwrite = true) = 0;
virtual bool EraseKey(CDataStream &&key) = 0;
virtual bool HasKey(CDataStream &&key) = 0;
public:
explicit DatabaseBatch() {}
virtual ~DatabaseBatch() {}
DatabaseBatch(const DatabaseBatch &) = delete;
DatabaseBatch &operator=(const DatabaseBatch &) = delete;
virtual void Flush() = 0;
virtual void Close() = 0;
template <typename K, typename T> bool Read(const K &key, T &value) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
if (!ReadKey(std::move(ssKey), ssValue)) {
return false;
}
try {
ssValue >> value;
return true;
} catch (const std::exception &) {
return false;
}
}
template <typename K, typename T>
bool Write(const K &key, const T &value, bool fOverwrite = true) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue.reserve(10000);
ssValue << value;
return WriteKey(std::move(ssKey), std::move(ssValue), fOverwrite);
}
template <typename K> bool Erase(const K &key) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
return EraseKey(std::move(ssKey));
}
template <typename K> bool Exists(const K &key) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
return HasKey(std::move(ssKey));
}
virtual bool StartCursor() = 0;
virtual bool ReadAtCursor(CDataStream &ssKey, CDataStream &ssValue,
bool &complete) = 0;
virtual void CloseCursor() = 0;
virtual bool TxnBegin() = 0;
virtual bool TxnCommit() = 0;
virtual bool TxnAbort() = 0;
};
/**
* An instance of this class represents one database.
*/
class WalletDatabase {
public:
/** Create dummy DB handle */
WalletDatabase()
: nUpdateCounter(0), nLastSeen(0), nLastFlushed(0),
nLastWalletUpdate(0) {}
virtual ~WalletDatabase(){};
/** Open the database if it is not already opened. */
virtual void Open(const char *mode) = 0;
//! Counts the number of active database users to be sure that the database
//! is not closed while someone is using it
std::atomic<int> m_refcount{0};
/**
* Indicate the a new database user has began using the database.
* Increments m_refcount
*/
virtual void AddRef() = 0;
/**
* Indicate that database user has stopped using the database and that it
* could be flushed or closed. Decrement m_refcount
*/
virtual void RemoveRef() = 0;
/**
* Rewrite the entire database on disk, with the exception of key pszSkip
* if non-zero
*/
virtual bool Rewrite(const char *pszSkip = nullptr) = 0;
/**
* Back up the entire database to a file.
*/
virtual bool Backup(const std::string &strDest) const = 0;
/**
* Make sure all changes are flushed to database file.
*/
virtual void Flush() = 0;
/**
* Flush to the database file and close the database.
* Also close the environment if no other databases are open in it.
*/
virtual void Close() = 0;
/* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */
virtual bool PeriodicFlush() = 0;
virtual void IncrementUpdateCounter() = 0;
virtual void ReloadDbEnv() = 0;
+ /** Return path to main database file for logs and error messages. */
+ virtual std::string Filename() = 0;
+
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
int64_t nLastWalletUpdate;
- std::string m_file_path;
-
/** Make a DatabaseBatch connected to this database */
virtual std::unique_ptr<DatabaseBatch>
MakeBatch(const char *mode = "r+", bool flush_on_close = true) = 0;
};
/** RAII class that provides access to a DummyDatabase. Never fails. */
class DummyBatch : public DatabaseBatch {
private:
bool ReadKey(CDataStream &&key, CDataStream &value) override {
return true;
}
bool WriteKey(CDataStream &&key, CDataStream &&value,
bool overwrite = true) override {
return true;
}
bool EraseKey(CDataStream &&key) override { return true; }
bool HasKey(CDataStream &&key) override { return true; }
public:
void Flush() override {}
void Close() override {}
bool StartCursor() override { return true; }
bool ReadAtCursor(CDataStream &ssKey, CDataStream &ssValue,
bool &complete) override {
return true;
}
void CloseCursor() override {}
bool TxnBegin() override { return true; }
bool TxnCommit() override { return true; }
bool TxnAbort() override { return true; }
};
/**
* A dummy WalletDatabase that does nothing and never fails. Only used by unit
* tests.
*/
class DummyDatabase : public WalletDatabase {
public:
void Open(const char *mode) override{};
void AddRef() override {}
void RemoveRef() override {}
bool Rewrite(const char *pszSkip = nullptr) override { return true; }
bool Backup(const std::string &strDest) const override { return true; }
void Close() override {}
void Flush() override {}
bool PeriodicFlush() override { return true; }
void IncrementUpdateCounter() override { ++nUpdateCounter; }
void ReloadDbEnv() override {}
+ std::string Filename() override { return "dummy"; }
std::unique_ptr<DatabaseBatch>
MakeBatch(const char *mode = "r+", bool flush_on_close = true) override {
return std::make_unique<DummyBatch>();
}
};
enum class DatabaseFormat {
BERKELEY,
};
struct DatabaseOptions {
bool require_existing = false;
bool require_create = false;
uint64_t create_flags = 0;
SecureString create_passphrase;
bool verify = true;
};
enum class DatabaseStatus {
SUCCESS,
FAILED_BAD_PATH,
FAILED_BAD_FORMAT,
FAILED_ALREADY_LOADED,
FAILED_ALREADY_EXISTS,
FAILED_NOT_FOUND,
FAILED_CREATE,
FAILED_VERIFY,
FAILED_ENCRYPT,
};
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path &path,
const DatabaseOptions &options,
DatabaseStatus &status,
bilingual_str &error);
#endif // BITCOIN_WALLET_DB_H
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 500ece2d9..8ec90aeca 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -1,145 +1,155 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/load.h>
#include <fs.h>
#include <interfaces/chain.h>
#include <scheduler.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
#include <univalue.h>
bool VerifyWallets(interfaces::Chain &chain,
const std::vector<std::string> &wallet_files) {
if (gArgs.IsArgSet("-walletdir")) {
fs::path wallet_dir = gArgs.GetArg("-walletdir", "");
boost::system::error_code error;
// The canonical path cleans the path, preventing >1 Berkeley
// environment instances for the same directory
fs::path canonical_wallet_dir = fs::canonical(wallet_dir, error);
if (error || !fs::exists(wallet_dir)) {
chain.initError(
strprintf(_("Specified -walletdir \"%s\" does not exist"),
wallet_dir.string()));
return false;
} else if (!fs::is_directory(wallet_dir)) {
chain.initError(
strprintf(_("Specified -walletdir \"%s\" is not a directory"),
wallet_dir.string()));
return false;
// The canonical path transforms relative paths into absolute ones,
// so we check the non-canonical version
} else if (!wallet_dir.is_absolute()) {
chain.initError(
strprintf(_("Specified -walletdir \"%s\" is a relative path"),
wallet_dir.string()));
return false;
}
gArgs.ForceSetArg("-walletdir", canonical_wallet_dir.string());
}
LogPrintf("Using wallet directory %s\n", GetWalletDir().string());
chain.initMessage(_("Verifying wallet(s)...").translated);
// Keep track of each wallet absolute path to detect duplicates.
std::set<fs::path> wallet_paths;
for (const auto &wallet_file : wallet_files) {
const fs::path path = fs::absolute(wallet_file, GetWalletDir());
if (!wallet_paths.insert(path).second) {
chain.initError(strprintf(_("Error loading wallet %s. Duplicate "
"-wallet filename specified."),
wallet_file));
return false;
}
DatabaseOptions options;
DatabaseStatus status;
options.verify = true;
bilingual_str error_string;
if (!MakeWalletDatabase(wallet_file, options, status, error_string)) {
chain.initError(error_string);
return false;
}
}
return true;
}
bool LoadWallets(interfaces::Chain &chain,
const std::vector<std::string> &wallet_files) {
try {
- for (const std::string &walletFile : wallet_files) {
+ for (const std::string &name : wallet_files) {
+ DatabaseOptions options;
+ DatabaseStatus status;
+ // No need to verify, assuming verified earlier in VerifyWallets()
+ options.verify = false;
bilingual_str error;
std::vector<bilingual_str> warnings;
- std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(
- chain, walletFile, error, warnings);
+ std::unique_ptr<WalletDatabase> database =
+ MakeWalletDatabase(name, options, status, error);
+ std::shared_ptr<CWallet> pwallet =
+ database
+ ? CWallet::Create(chain, name, std::move(database),
+ options.create_flags, error, warnings)
+ : nullptr;
+
if (!warnings.empty()) {
chain.initWarning(Join(warnings, Untranslated("\n")));
}
if (!pwallet) {
chain.initError(error);
return false;
}
AddWallet(pwallet);
}
return true;
} catch (const std::runtime_error &e) {
chain.initError(Untranslated(e.what()));
return false;
}
}
void StartWallets(CScheduler &scheduler, const ArgsManager &args) {
for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) {
pwallet->postInitProcess();
}
// Schedule periodic wallet flushes and tx rebroadcasts
if (args.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
scheduler.scheduleEvery(
[] {
MaybeCompactWalletDB();
return true;
},
std::chrono::milliseconds{500});
}
scheduler.scheduleEvery(
[] {
MaybeResendWalletTxs();
return true;
},
std::chrono::milliseconds{1000});
}
void FlushWallets() {
for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) {
pwallet->Flush();
}
}
void StopWallets() {
for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) {
pwallet->Close();
}
}
void UnloadWallets() {
auto wallets = GetWallets();
while (!wallets.empty()) {
auto wallet = wallets.back();
wallets.pop_back();
std::vector<bilingual_str> warnings;
RemoveWallet(wallet, std::nullopt, warnings);
UnloadWallet(std::move(wallet));
}
}
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 8c6d2115c..744d4f323 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -1,902 +1,906 @@
// Copyright (c) 2012-2019 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 <chain.h>
#include <chainparams.h>
#include <config.h>
#include <interfaces/chain.h>
#include <node/context.h>
#include <policy/policy.h>
#include <rpc/server.h>
#include <util/ref.h>
#include <util/translation.h>
#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/rpcdump.h>
#include <wallet/wallet.h>
#include <test/util/logging.h>
#include <test/util/setup_common.h>
#include <wallet/test/wallet_test_fixture.h>
#include <boost/test/unit_test.hpp>
#include <univalue.h>
#include <cstdint>
#include <future>
#include <memory>
#include <vector>
BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup)
static std::shared_ptr<CWallet> TestLoadWallet(interfaces::Chain &chain) {
+ DatabaseOptions options;
+ DatabaseStatus status;
bilingual_str error;
std::vector<bilingual_str> warnings;
- auto wallet = CWallet::CreateWalletFromFile(chain, "", error, warnings);
+ auto database = MakeWalletDatabase("", options, status, error);
+ auto wallet = CWallet::Create(chain, "", std::move(database),
+ options.create_flags, error, warnings);
wallet->postInitProcess();
return wallet;
}
static void TestUnloadWallet(std::shared_ptr<CWallet> &&wallet) {
SyncWithValidationInterfaceQueue();
wallet->m_chain_notifications_handler.reset();
UnloadWallet(std::move(wallet));
}
static CMutableTransaction TestSimpleSpend(const CTransaction &from,
uint32_t index, const CKey &key,
const CScript &pubkey) {
CMutableTransaction mtx;
mtx.vout.push_back(
{from.vout[index].nValue - DEFAULT_TRANSACTION_MAXFEE, pubkey});
mtx.vin.push_back({CTxIn{from.GetId(), index}});
FillableSigningProvider keystore;
keystore.AddKey(key);
std::map<COutPoint, Coin> coins;
coins[mtx.vin[0].prevout].GetTxOut() = from.vout[index];
std::map<int, std::string> input_errors;
BOOST_CHECK(SignTransaction(mtx, &keystore, coins,
SigHashType().withForkId(), input_errors));
return mtx;
}
static void AddKey(CWallet &wallet, const CKey &key) {
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
spk_man->AddKeyPubKey(key, key.GetPubKey());
}
BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) {
// Cap last block file size, and mine new block in a new block file.
CBlockIndex *oldTip = ::ChainActive().Tip();
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex *newTip = ::ChainActive().Tip();
NodeContext node;
auto chain = interfaces::MakeChain(node, Params());
// Verify ScanForWalletTransactions fails to read an unknown start block.
{
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(
BlockHash() /* start_block */, 0 /* start_height */,
{} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, Amount::zero());
}
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(
oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */,
reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN);
}
// Prune the older block file.
{
LOCK(cs_main);
Assert(m_node.chainman)
->m_blockman.PruneOneBlockFile(oldTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
// Verify ScanForWalletTransactions only picks transactions in the new block
// file.
{
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(
oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */,
reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN);
}
// Prune the remaining block file.
{
LOCK(cs_main);
Assert(m_node.chainman)
->m_blockman.PruneOneBlockFile(newTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({newTip->GetBlockPos().nFile});
// Verify ScanForWalletTransactions scans no blocks.
{
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetLastBlockProcessed(::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
CWallet::ScanResult result = wallet.ScanForWalletTransactions(
oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */,
reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, Amount::zero());
}
}
BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) {
// Cap last block file size, and mine new block in a new block file.
CBlockIndex *oldTip = ::ChainActive().Tip();
GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE;
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
CBlockIndex *newTip = ::ChainActive().Tip();
NodeContext node;
auto chain = interfaces::MakeChain(node, Params());
// Prune the older block file.
{
LOCK(cs_main);
Assert(m_node.chainman)
->m_blockman.PruneOneBlockFile(oldTip->GetBlockPos().nFile);
}
UnlinkPrunedFiles({oldTip->GetBlockPos().nFile});
// Verify importmulti RPC returns failure for a key whose creation time is
// before the missing block, and success for a key whose creation time is
// after.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(
chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
WITH_LOCK(wallet->cs_wallet,
wallet->SetLastBlockProcessed(newTip->nHeight,
newTip->GetBlockHash()));
AddWallet(wallet);
UniValue keys;
keys.setArray();
UniValue key;
key.setObject();
key.pushKV("scriptPubKey",
HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey())));
key.pushKV("timestamp", 0);
key.pushKV("internal", UniValue(true));
keys.push_back(key);
key.clear();
key.setObject();
CKey futureKey;
futureKey.MakeNewKey(true);
key.pushKV("scriptPubKey",
HexStr(GetScriptForRawPubKey(futureKey.GetPubKey())));
key.pushKV("timestamp",
newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1);
key.pushKV("internal", UniValue(true));
keys.push_back(key);
util::Ref context;
JSONRPCRequest request(context);
request.params.setArray();
request.params.push_back(keys);
UniValue response = importmulti().HandleRequest(GetConfig(), request);
BOOST_CHECK_EQUAL(
response.write(),
strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":"
"\"Rescan failed for key with creation timestamp %d. "
"There was an error reading a block from time %d, which "
"is after or within %d seconds of key creation, and "
"could contain transactions pertaining to the key. As a "
"result, transactions and coins using this key may not "
"appear in the wallet. This error could be caused by "
"pruning or data corruption (see bitcoind log for "
"details) and could be dealt with by downloading and "
"rescanning the relevant blocks (see -reindex and "
"-rescan options).\"}},{\"success\":true}]",
0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW));
RemoveWallet(wallet, std::nullopt);
}
}
// Verify importwallet RPC starts rescan at earliest block with timestamp
// greater or equal than key birthday. Previously there was a bug where
// importwallet RPC would start the scan at the latest block with timestamp less
// than or equal to key birthday.
BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) {
// Create two blocks with same timestamp to verify that importwallet rescan
// will pick up both blocks, not just the first.
const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5;
SetMockTime(BLOCK_TIME);
m_coinbase_txns.emplace_back(
CreateAndProcessBlock({},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
m_coinbase_txns.emplace_back(
CreateAndProcessBlock({},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
// Set key birthday to block time increased by the timestamp window, so
// rescan will start at the block time.
const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW;
SetMockTime(KEY_TIME);
m_coinbase_txns.emplace_back(
CreateAndProcessBlock({},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
NodeContext node;
auto chain = interfaces::MakeChain(node, Params());
std::string backup_file = (GetDataDir() / "wallet.backup").string();
// Import key into wallet and call dumpwallet to create backup file.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(
chain.get(), "", CreateDummyWalletDatabase());
{
auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()]
.nCreateTime = KEY_TIME;
spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey());
AddWallet(wallet);
wallet->SetLastBlockProcessed(
::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
}
util::Ref context;
JSONRPCRequest request(context);
request.params.setArray();
request.params.push_back(backup_file);
::dumpwallet().HandleRequest(GetConfig(), request);
RemoveWallet(wallet, std::nullopt);
}
// Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME
// were scanned, and no prior blocks were scanned.
{
std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(
chain.get(), "", CreateDummyWalletDatabase());
LOCK(wallet->cs_wallet);
wallet->SetupLegacyScriptPubKeyMan();
util::Ref context;
JSONRPCRequest request(context);
request.params.setArray();
request.params.push_back(backup_file);
AddWallet(wallet);
wallet->SetLastBlockProcessed(::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
::importwallet().HandleRequest(GetConfig(), request);
RemoveWallet(wallet, std::nullopt);
BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U);
BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U);
for (size_t i = 0; i < m_coinbase_txns.size(); ++i) {
bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetId());
bool expected = i >= 100;
BOOST_CHECK_EQUAL(found, expected);
}
}
}
// Check that GetImmatureCredit() returns a newly calculated value instead of
// the cached value after a MarkDirty() call.
//
// This is a regression test written to verify a bugfix for the immature credit
// function. Similar tests probably should be written for the other credit and
// debit functions.
BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) {
NodeContext node;
auto chain = interfaces::MakeChain(node, Params());
CWallet wallet(chain.get(), "", CreateDummyWalletDatabase());
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
CWalletTx wtx(&wallet, m_coinbase_txns.back());
LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
wallet.SetLastBlockProcessed(::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED,
::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash(), 0);
wtx.m_confirm = confirm;
// Call GetImmatureCredit() once before adding the key to the wallet to
// cache the current immature credit amount, which is 0.
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), Amount::zero());
// Invalidate the cached value, add the key, and make sure a new immature
// credit amount is calculated.
wtx.MarkDirty();
BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()));
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50 * COIN);
}
static int64_t AddTx(ChainstateManager &chainman, CWallet &wallet,
uint32_t lockTime, int64_t mockTime, int64_t blockTime) {
CMutableTransaction tx;
CWalletTx::Confirmation confirm;
tx.nLockTime = lockTime;
SetMockTime(mockTime);
CBlockIndex *block = nullptr;
if (blockTime > 0) {
LOCK(cs_main);
auto inserted = chainman.BlockIndex().emplace(BlockHash(GetRandHash()),
new CBlockIndex);
assert(inserted.second);
const BlockHash &hash = inserted.first->first;
block = inserted.first->second;
block->nTime = blockTime;
block->phashBlock = &hash;
confirm = {CWalletTx::Status::CONFIRMED, block->nHeight, hash, 0};
}
// If transaction is already in map, to avoid inconsistencies,
// unconfirmation is needed before confirm again with different block.
return wallet
.AddToWallet(MakeTransactionRef(tx), confirm,
[&](CWalletTx &wtx, bool /* new_tx */) {
wtx.setUnconfirmed();
return true;
})
->nTimeSmart;
}
// Simple test to verify assignment of CWalletTx::nSmartTime value. Could be
// expanded to cover more corner cases of smart time logic.
BOOST_AUTO_TEST_CASE(ComputeTimeSmart) {
// New transaction should use clock time if lower than block time.
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 1, 100, 120), 100);
// Test that updating existing transaction does not change smart time.
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 1, 200, 220), 100);
// New transaction should use clock time if there's no block time.
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 2, 300, 0), 300);
// New transaction should use block time if lower than clock time.
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 3, 420, 400), 400);
// New transaction should use latest entry time if higher than
// min(block time, clock time).
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 4, 500, 390), 400);
// If there are future entries, new transaction should use time of the
// newest entry that is no more than 300 seconds ahead of the clock time.
BOOST_CHECK_EQUAL(AddTx(*m_node.chainman, m_wallet, 5, 50, 600), 300);
// Reset mock time for other tests.
SetMockTime(0);
}
BOOST_AUTO_TEST_CASE(LoadReceiveRequests) {
CTxDestination dest = PKHash();
LOCK(m_wallet.cs_wallet);
WalletBatch batch{m_wallet.GetDatabase()};
m_wallet.AddDestData(batch, dest, "misc", "val_misc");
m_wallet.AddDestData(batch, dest, "rr0", "val_rr0");
m_wallet.AddDestData(batch, dest, "rr1", "val_rr1");
auto values = m_wallet.GetDestValues("rr");
BOOST_CHECK_EQUAL(values.size(), 2U);
BOOST_CHECK_EQUAL(values[0], "val_rr0");
BOOST_CHECK_EQUAL(values[1], "val_rr1");
}
// Test some watch-only LegacyScriptPubKeyMan methods by the procedure of
// loading (LoadWatchOnly), checking (HaveWatchOnly), getting (GetWatchPubKey)
// and removing (RemoveWatchOnly) a given PubKey, resp. its corresponding P2PK
// Script. Results of the the impact on the address -> PubKey map is dependent
// on whether the PubKey is a point on the curve
static void TestWatchOnlyPubKey(LegacyScriptPubKeyMan *spk_man,
const CPubKey &add_pubkey) {
CScript p2pk = GetScriptForRawPubKey(add_pubkey);
CKeyID add_address = add_pubkey.GetID();
CPubKey found_pubkey;
LOCK(spk_man->cs_KeyStore);
// all Scripts (i.e. also all PubKeys) are added to the general watch-only
// set
BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk));
spk_man->LoadWatchOnly(p2pk);
BOOST_CHECK(spk_man->HaveWatchOnly(p2pk));
// only PubKeys on the curve shall be added to the watch-only address ->
// PubKey map
bool is_pubkey_fully_valid = add_pubkey.IsFullyValid();
if (is_pubkey_fully_valid) {
BOOST_CHECK(spk_man->GetWatchPubKey(add_address, found_pubkey));
BOOST_CHECK(found_pubkey == add_pubkey);
} else {
BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey));
// passed key is unchanged
BOOST_CHECK(found_pubkey == CPubKey());
}
spk_man->RemoveWatchOnly(p2pk);
BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk));
if (is_pubkey_fully_valid) {
BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey));
// passed key is unchanged
BOOST_CHECK(found_pubkey == add_pubkey);
}
}
// Cryptographically invalidate a PubKey whilst keeping length and first byte
static void PollutePubKey(CPubKey &pubkey) {
std::vector<uint8_t> pubkey_raw(pubkey.begin(), pubkey.end());
std::fill(pubkey_raw.begin() + 1, pubkey_raw.end(), 0);
pubkey = CPubKey(pubkey_raw);
assert(!pubkey.IsFullyValid());
assert(pubkey.IsValid());
}
// Test watch-only logic for PubKeys
BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys) {
CKey key;
CPubKey pubkey;
LegacyScriptPubKeyMan *spk_man =
m_wallet.GetOrCreateLegacyScriptPubKeyMan();
BOOST_CHECK(!spk_man->HaveWatchOnly());
// uncompressed valid PubKey
key.MakeNewKey(false);
pubkey = key.GetPubKey();
assert(!pubkey.IsCompressed());
TestWatchOnlyPubKey(spk_man, pubkey);
// uncompressed cryptographically invalid PubKey
PollutePubKey(pubkey);
TestWatchOnlyPubKey(spk_man, pubkey);
// compressed valid PubKey
key.MakeNewKey(true);
pubkey = key.GetPubKey();
assert(pubkey.IsCompressed());
TestWatchOnlyPubKey(spk_man, pubkey);
// compressed cryptographically invalid PubKey
PollutePubKey(pubkey);
TestWatchOnlyPubKey(spk_man, pubkey);
// invalid empty PubKey
pubkey = CPubKey();
TestWatchOnlyPubKey(spk_man, pubkey);
}
class ListCoinsTestingSetup : public TestChain100Setup {
public:
ListCoinsTestingSetup() {
CreateAndProcessBlock({},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
wallet = std::make_unique<CWallet>(m_chain.get(), "",
CreateMockWalletDatabase());
{
LOCK2(wallet->cs_wallet, ::cs_main);
wallet->SetLastBlockProcessed(
::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash());
}
bool firstRun;
wallet->LoadWallet(firstRun);
AddKey(*wallet, coinbaseKey);
WalletRescanReserver reserver(*wallet);
reserver.reserve();
CWallet::ScanResult result = wallet->ScanForWalletTransactions(
::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */,
{} /* max_height */, reserver, false /* update */);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK_EQUAL(result.last_scanned_block,
::ChainActive().Tip()->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height,
::ChainActive().Height());
BOOST_CHECK(result.last_failed_block.IsNull());
}
~ListCoinsTestingSetup() { wallet.reset(); }
CWalletTx &AddTx(CRecipient recipient) {
CTransactionRef tx;
Amount fee;
int changePos = -1;
bilingual_str error;
CCoinControl dummy;
{
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee,
changePos, error, dummy));
}
wallet->CommitTransaction(tx, {}, {});
CMutableTransaction blocktx;
{
LOCK(wallet->cs_wallet);
blocktx =
CMutableTransaction(*wallet->mapWallet.at(tx->GetId()).tx);
}
CreateAndProcessBlock({CMutableTransaction(blocktx)},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
LOCK(wallet->cs_wallet);
wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1,
::ChainActive().Tip()->GetBlockHash());
auto it = wallet->mapWallet.find(tx->GetId());
BOOST_CHECK(it != wallet->mapWallet.end());
CWalletTx::Confirmation confirm(
CWalletTx::Status::CONFIRMED, ::ChainActive().Height(),
::ChainActive().Tip()->GetBlockHash(), 1);
it->second.m_confirm = confirm;
return it->second;
}
std::unique_ptr<interfaces::Chain> m_chain =
interfaces::MakeChain(m_node, Params());
std::unique_ptr<CWallet> wallet;
};
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) {
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
// Confirm ListCoins initially returns 1 coin grouped under coinbaseKey
// address.
std::map<CTxDestination, std::vector<COutput>> list;
{
LOCK(wallet->cs_wallet);
list = wallet->ListCoins();
}
BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(),
coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
// Check initial balance from one mature coinbase transaction.
BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance());
// Add a transaction creating a change address, and confirm ListCoins still
// returns the coin associated with the change address underneath the
// coinbaseKey pubkey, even though the change address has a different
// pubkey.
AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN,
false /* subtract fee */});
{
LOCK(wallet->cs_wallet);
list = wallet->ListCoins();
}
BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(),
coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
// Lock both coins. Confirm number of available coins drops to 0.
{
LOCK(wallet->cs_wallet);
std::vector<COutput> available;
wallet->AvailableCoins(available);
BOOST_CHECK_EQUAL(available.size(), 2U);
}
for (const auto &group : list) {
for (const auto &coin : group.second) {
LOCK(wallet->cs_wallet);
wallet->LockCoin(COutPoint(coin.tx->GetId(), coin.i));
}
}
{
LOCK(wallet->cs_wallet);
std::vector<COutput> available;
wallet->AvailableCoins(available);
BOOST_CHECK_EQUAL(available.size(), 0U);
}
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
{
LOCK(wallet->cs_wallet);
list = wallet->ListCoins();
}
BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(),
coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U);
}
BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) {
NodeContext node;
auto chain = interfaces::MakeChain(node, Params());
std::shared_ptr<CWallet> wallet =
std::make_shared<CWallet>(chain.get(), "", CreateDummyWalletDatabase());
wallet->SetupLegacyScriptPubKeyMan();
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
CTxDestination dest;
std::string error;
BOOST_CHECK(
!wallet->GetNewDestination(OutputType::LEGACY, "", dest, error));
}
// Explicit calculation which is used to test the wallet constant
static size_t CalculateP2PKHInputSize(bool use_max_sig) {
// Generate ephemeral valid pubkey
CKey key;
key.MakeNewKey(true);
CPubKey pubkey = key.GetPubKey();
// Generate pubkey hash
PKHash key_hash(pubkey);
// Create script to enter into keystore. Key hash can't be 0...
CScript script = GetScriptForDestination(key_hash);
// Add script to key store and key to watchonly
FillableSigningProvider keystore;
keystore.AddKeyPubKey(key, pubkey);
// Fill in dummy signatures for fee calculation.
SignatureData sig_data;
if (!ProduceSignature(keystore,
use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR
: DUMMY_SIGNATURE_CREATOR,
script, sig_data)) {
// We're hand-feeding it correct arguments; shouldn't happen
assert(false);
}
CTxIn tx_in;
UpdateInput(tx_in, sig_data);
return (size_t)GetVirtualTransactionInputSize(tx_in);
}
BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup) {
BOOST_CHECK(CalculateP2PKHInputSize(false) <= DUMMY_P2PKH_INPUT_SIZE);
BOOST_CHECK_EQUAL(CalculateP2PKHInputSize(true), DUMMY_P2PKH_INPUT_SIZE);
}
bool malformed_descriptor(std::ios_base::failure e) {
std::string s(e.what());
return s.find("Missing checksum") != std::string::npos;
}
BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup) {
std::vector<uint8_t> malformed_record;
CVectorWriter vw(0, 0, malformed_record, 0);
vw << std::string("notadescriptor");
vw << (uint64_t)0;
vw << (int32_t)0;
vw << (int32_t)0;
vw << (int32_t)1;
VectorReader vr(0, 0, malformed_record, 0);
WalletDescriptor w_desc;
BOOST_CHECK_EXCEPTION(vr >> w_desc, std::ios_base::failure,
malformed_descriptor);
}
//! Test CreateWalletFromFile function and its behavior handling potential race
//! conditions if it's called the same time an incoming transaction shows up in
//! the mempool or a new block.
//!
//! It isn't possible to verify there aren't race condition in every case, so
//! this test just checks two specific cases and ensures that timing of
//! notifications in these cases doesn't prevent the wallet from detecting
//! transactions.
//!
//! In the first case, block and mempool transactions are created before the
//! wallet is loaded, but notifications about these transactions are delayed
//! until after it is loaded. The notifications are superfluous in this case, so
//! the test verifies the transactions are detected before they arrive.
//!
//! In the second case, block and mempool transactions are created after the
//! wallet rescan and notifications are immediately synced, to verify the wallet
//! must already have a handler in place for them, and there's no gap after
//! rescanning where new transactions in new blocks could be lost.
BOOST_FIXTURE_TEST_CASE(CreateWalletFromFile, TestChain100Setup) {
// Create new wallet with known key and unload it.
auto chain = interfaces::MakeChain(m_node, Params());
auto wallet = TestLoadWallet(*chain);
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
TestUnloadWallet(std::move(wallet));
// Add log hook to detect AddToWallet events from rescans, blockConnected,
// and transactionAddedToMempool notifications
int addtx_count = 0;
DebugLogHelper addtx_counter("[default wallet] AddToWallet",
[&](const std::string *s) {
if (s) {
++addtx_count;
}
return false;
});
bool rescan_completed = false;
DebugLogHelper rescan_check("[default wallet] Rescan completed",
[&](const std::string *s) {
if (s) {
rescan_completed = true;
}
return false;
});
// Block the queue to prevent the wallet receiving blockConnected and
// transactionAddedToMempool notifications, and create block and mempool
// transactions paying to the wallet
std::promise<void> promise;
CallFunctionInValidationInterfaceQueue(
[&promise] { promise.get_future().wait(); });
std::string error;
m_coinbase_txns.push_back(
CreateAndProcessBlock({},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey,
GetScriptForRawPubKey(key.GetPubKey()));
m_coinbase_txns.push_back(
CreateAndProcessBlock({block_tx},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
auto mempool_tx = TestSimpleSpend(*m_coinbase_txns[1], 0, coinbaseKey,
GetScriptForRawPubKey(key.GetPubKey()));
BOOST_CHECK(
chain->broadcastTransaction(GetConfig(), MakeTransactionRef(mempool_tx),
DEFAULT_TRANSACTION_MAXFEE, false, error));
// Reload wallet and make sure new transactions are detected despite events
// being blocked
wallet = TestLoadWallet(*chain);
BOOST_CHECK(rescan_completed);
BOOST_CHECK_EQUAL(addtx_count, 2);
{
LOCK(wallet->cs_wallet);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetId()), 1U);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetId()), 1U);
}
// Unblock notification queue and make sure stale blockConnected and
// transactionAddedToMempool events are processed
promise.set_value();
SyncWithValidationInterfaceQueue();
BOOST_CHECK_EQUAL(addtx_count, 4);
TestUnloadWallet(std::move(wallet));
// Load wallet again, this time creating new block and mempool transactions
// paying to the wallet as the wallet finishes loading and syncing the
// queue so the events have to be handled immediately. Releasing the wallet
// lock during the sync is a little artificial but is needed to avoid a
// deadlock during the sync and simulates a new block notification happening
// as soon as possible.
addtx_count = 0;
auto handler = HandleLoadWallet(
[&](std::unique_ptr<interfaces::Wallet> wallet_param)
EXCLUSIVE_LOCKS_REQUIRED(wallet_param->wallet()->cs_wallet) {
BOOST_CHECK(rescan_completed);
m_coinbase_txns.push_back(
CreateAndProcessBlock(
{}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
block_tx =
TestSimpleSpend(*m_coinbase_txns[2], 0, coinbaseKey,
GetScriptForRawPubKey(key.GetPubKey()));
m_coinbase_txns.push_back(
CreateAndProcessBlock(
{block_tx},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
mempool_tx =
TestSimpleSpend(*m_coinbase_txns[3], 0, coinbaseKey,
GetScriptForRawPubKey(key.GetPubKey()));
BOOST_CHECK(chain->broadcastTransaction(
GetConfig(), MakeTransactionRef(mempool_tx),
DEFAULT_TRANSACTION_MAXFEE, false, error));
LEAVE_CRITICAL_SECTION(wallet_param->wallet()->cs_wallet);
SyncWithValidationInterfaceQueue();
ENTER_CRITICAL_SECTION(wallet_param->wallet()->cs_wallet);
});
wallet = TestLoadWallet(*chain);
BOOST_CHECK_EQUAL(addtx_count, 4);
{
LOCK(wallet->cs_wallet);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetId()), 1U);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(mempool_tx.GetId()), 1U);
}
TestUnloadWallet(std::move(wallet));
}
BOOST_FIXTURE_TEST_CASE(ZapSelectTx, TestChain100Setup) {
auto chain = interfaces::MakeChain(m_node, Params());
auto wallet = TestLoadWallet(*chain);
CKey key;
key.MakeNewKey(true);
AddKey(*wallet, key);
std::string error;
m_coinbase_txns.push_back(
CreateAndProcessBlock({},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()))
.vtx[0]);
auto block_tx = TestSimpleSpend(*m_coinbase_txns[0], 0, coinbaseKey,
GetScriptForRawPubKey(key.GetPubKey()));
CreateAndProcessBlock({block_tx},
GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
SyncWithValidationInterfaceQueue();
{
auto block_id = block_tx.GetId();
auto prev_id = m_coinbase_txns[0]->GetId();
LOCK(wallet->cs_wallet);
BOOST_CHECK(wallet->HasWalletSpend(prev_id));
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_id), 1u);
std::vector<TxId> vIdIn{block_id}, vIdOut;
BOOST_CHECK_EQUAL(wallet->ZapSelectTx(vIdIn, vIdOut),
DBErrors::LOAD_OK);
BOOST_CHECK(!wallet->HasWalletSpend(prev_id));
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_id), 0u);
}
TestUnloadWallet(std::move(wallet));
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 4f1f8ad07..dc6c2d993 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5090 +1,5097 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/wallet.h>
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <fs.h>
#include <interfaces/wallet.h>
#include <key.h>
#include <key_io.h>
#include <policy/mempool.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <random.h>
#include <script/descriptor.h>
#include <script/script.h>
#include <script/sighashtype.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <txmempool.h>
#include <univalue.h>
#include <util/bip32.h>
#include <util/check.h>
#include <util/error.h>
#include <util/moneystr.h>
#include <util/string.h>
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
#include <boost/algorithm/string/replace.hpp>
using interfaces::FoundBlock;
const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{
{WALLET_FLAG_AVOID_REUSE,
"You need to rescan the blockchain in order to correctly mark used "
"destinations in the past. Until this is done, some destinations may "
"be considered unused, even if the opposite is the case."},
};
static RecursiveMutex cs_wallets;
static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets);
static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets);
bool AddWalletSetting(interfaces::Chain &chain,
const std::string &wallet_name) {
util::SettingsValue setting_value = chain.getRwSetting("wallet");
if (!setting_value.isArray()) {
setting_value.setArray();
}
for (const util::SettingsValue &value : setting_value.getValues()) {
if (value.isStr() && value.get_str() == wallet_name) {
return true;
}
}
setting_value.push_back(wallet_name);
return chain.updateRwSetting("wallet", setting_value);
}
bool RemoveWalletSetting(interfaces::Chain &chain,
const std::string &wallet_name) {
util::SettingsValue setting_value = chain.getRwSetting("wallet");
if (!setting_value.isArray()) {
return true;
}
util::SettingsValue new_value(util::SettingsValue::VARR);
for (const util::SettingsValue &value : setting_value.getValues()) {
if (!value.isStr() || value.get_str() != wallet_name) {
new_value.push_back(value);
}
}
if (new_value.size() == setting_value.size()) {
return true;
}
return chain.updateRwSetting("wallet", new_value);
}
static void UpdateWalletSetting(interfaces::Chain &chain,
const std::string &wallet_name,
std::optional<bool> load_on_startup,
std::vector<bilingual_str> &warnings) {
if (!load_on_startup) {
return;
}
if (load_on_startup.value() && !AddWalletSetting(chain, wallet_name)) {
warnings.emplace_back(
Untranslated("Wallet load on startup setting could not be updated, "
"so wallet may not be loaded next node startup."));
} else if (!load_on_startup.value() &&
!RemoveWalletSetting(chain, wallet_name)) {
warnings.emplace_back(
Untranslated("Wallet load on startup setting could not be updated, "
"so wallet may still be loaded next node startup."));
}
}
bool AddWallet(const std::shared_ptr<CWallet> &wallet) {
LOCK(cs_wallets);
assert(wallet);
std::vector<std::shared_ptr<CWallet>>::const_iterator i =
std::find(vpwallets.begin(), vpwallets.end(), wallet);
if (i != vpwallets.end()) {
return false;
}
vpwallets.push_back(wallet);
wallet->ConnectScriptPubKeyManNotifiers();
return true;
}
bool RemoveWallet(const std::shared_ptr<CWallet> &wallet,
std::optional<bool> load_on_start,
std::vector<bilingual_str> &warnings) {
assert(wallet);
interfaces::Chain &chain = wallet->chain();
std::string name = wallet->GetName();
// Unregister with the validation interface which also drops shared ponters.
wallet->m_chain_notifications_handler.reset();
LOCK(cs_wallets);
std::vector<std::shared_ptr<CWallet>>::iterator i =
std::find(vpwallets.begin(), vpwallets.end(), wallet);
if (i == vpwallets.end()) {
return false;
}
vpwallets.erase(i);
// Write the wallet setting
UpdateWalletSetting(chain, name, load_on_start, warnings);
return true;
}
bool RemoveWallet(const std::shared_ptr<CWallet> &wallet,
std::optional<bool> load_on_start) {
std::vector<bilingual_str> warnings;
return RemoveWallet(wallet, load_on_start, warnings);
}
std::vector<std::shared_ptr<CWallet>> GetWallets() {
LOCK(cs_wallets);
return vpwallets;
}
std::shared_ptr<CWallet> GetWallet(const std::string &name) {
LOCK(cs_wallets);
for (const std::shared_ptr<CWallet> &wallet : vpwallets) {
if (wallet->GetName() == name) {
return wallet;
}
}
return nullptr;
}
std::unique_ptr<interfaces::Handler>
HandleLoadWallet(LoadWalletFn load_wallet) {
LOCK(cs_wallets);
auto it = g_load_wallet_fns.emplace(g_load_wallet_fns.end(),
std::move(load_wallet));
return interfaces::MakeHandler([it] {
LOCK(cs_wallets);
g_load_wallet_fns.erase(it);
});
}
static Mutex g_loading_wallet_mutex;
static Mutex g_wallet_release_mutex;
static std::condition_variable g_wallet_release_cv;
static std::set<std::string>
g_loading_wallet_set GUARDED_BY(g_loading_wallet_mutex);
static std::set<std::string>
g_unloading_wallet_set GUARDED_BY(g_wallet_release_mutex);
// Custom deleter for shared_ptr<CWallet>.
static void ReleaseWallet(CWallet *wallet) {
const std::string name = wallet->GetName();
wallet->WalletLogPrintf("Releasing wallet\n");
wallet->Flush();
delete wallet;
// Wallet is now released, notify UnloadWallet, if any.
{
LOCK(g_wallet_release_mutex);
if (g_unloading_wallet_set.erase(name) == 0) {
// UnloadWallet was not called for this wallet, all done.
return;
}
}
g_wallet_release_cv.notify_all();
}
void UnloadWallet(std::shared_ptr<CWallet> &&wallet) {
// Mark wallet for unloading.
const std::string name = wallet->GetName();
{
LOCK(g_wallet_release_mutex);
auto it = g_unloading_wallet_set.insert(name);
assert(it.second);
}
// The wallet can be in use so it's not possible to explicitly unload here.
// Notify the unload intent so that all remaining shared pointers are
// released.
wallet->NotifyUnload();
// Time to ditch our shared_ptr and wait for ReleaseWallet call.
wallet.reset();
{
WAIT_LOCK(g_wallet_release_mutex, lock);
while (g_unloading_wallet_set.count(name) == 1) {
g_wallet_release_cv.wait(lock);
}
}
}
static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10;
namespace {
std::shared_ptr<CWallet>
LoadWalletInternal(interfaces::Chain &chain, const std::string &name,
std::optional<bool> load_on_start,
const DatabaseOptions &options, DatabaseStatus &status,
bilingual_str &error, std::vector<bilingual_str> &warnings) {
try {
- if (!MakeWalletDatabase(name, options, status, error)) {
+ std::unique_ptr<WalletDatabase> database =
+ MakeWalletDatabase(name, options, status, error);
+ if (!database) {
error = Untranslated("Wallet file verification failed.") +
Untranslated(" ") + error;
return nullptr;
}
std::shared_ptr<CWallet> wallet =
- CWallet::CreateWalletFromFile(chain, name, error, warnings);
+ CWallet::Create(chain, name, std::move(database),
+ options.create_flags, error, warnings);
if (!wallet) {
error = Untranslated("Wallet loading failed.") + Untranslated(" ") +
error;
return nullptr;
}
AddWallet(wallet);
wallet->postInitProcess();
// Write the wallet setting
UpdateWalletSetting(chain, name, load_on_start, warnings);
return wallet;
} catch (const std::runtime_error &e) {
error = Untranslated(e.what());
return nullptr;
}
}
} // namespace
std::shared_ptr<CWallet>
LoadWallet(interfaces::Chain &chain, const std::string &name,
std::optional<bool> load_on_start, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error,
std::vector<bilingual_str> &warnings) {
auto result = WITH_LOCK(g_loading_wallet_mutex,
return g_loading_wallet_set.insert(name));
if (!result.second) {
error = Untranslated("Wallet already being loading.");
return nullptr;
}
auto wallet = LoadWalletInternal(chain, name, load_on_start, options,
status, error, warnings);
WITH_LOCK(g_loading_wallet_mutex, g_loading_wallet_set.erase(result.first));
return wallet;
}
std::shared_ptr<CWallet>
CreateWallet(interfaces::Chain &chain, const std::string &name,
std::optional<bool> load_on_start, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error,
std::vector<bilingual_str> &warnings) {
uint64_t wallet_creation_flags = options.create_flags;
const SecureString &passphrase = options.create_passphrase;
// Indicate that the wallet is actually supposed to be blank and not just
// blank to make it encrypted
bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET);
// Born encrypted wallets need to be created blank first.
if (!passphrase.empty()) {
wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET;
}
// Check the wallet file location
if (fs::symlink_status(
fs::absolute(name.empty() ? "wallet.dat" : name, GetWalletDir()))
.type() != fs::file_not_found) {
error = strprintf(Untranslated("Wallet %s already exists."), name);
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
// Wallet::Verify will check if we're trying to create a wallet with a
// duplicate name.
- if (!MakeWalletDatabase(name, options, status, error)) {
+ std::unique_ptr<WalletDatabase> database =
+ MakeWalletDatabase(name, options, status, error);
+ if (!database) {
error = Untranslated("Wallet file verification failed.") +
Untranslated(" ") + error;
status = DatabaseStatus::FAILED_VERIFY;
return nullptr;
}
// Do not allow a passphrase when private keys are disabled
if (!passphrase.empty() &&
(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
error = Untranslated(
"Passphrase provided but private keys are disabled. A passphrase "
"is only used to encrypt private keys, so cannot be used for "
"wallets with private keys disabled.");
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
// Make the wallet
- std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(
- chain, name, error, warnings, wallet_creation_flags);
+ std::shared_ptr<CWallet> wallet =
+ CWallet::Create(chain, name, std::move(database), wallet_creation_flags,
+ error, warnings);
if (!wallet) {
error =
Untranslated("Wallet creation failed.") + Untranslated(" ") + error;
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
// Encrypt the wallet
if (!passphrase.empty() &&
!(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (!wallet->EncryptWallet(passphrase)) {
error =
Untranslated("Error: Wallet created but failed to encrypt.");
status = DatabaseStatus::FAILED_ENCRYPT;
return nullptr;
}
if (!create_blank) {
// Unlock the wallet
if (!wallet->Unlock(passphrase)) {
error = Untranslated(
"Error: Wallet was encrypted but could not be unlocked");
status = DatabaseStatus::FAILED_ENCRYPT;
return nullptr;
}
// Set a seed for the wallet
{
LOCK(wallet->cs_wallet);
if (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
wallet->SetupDescriptorScriptPubKeyMans();
} else {
for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) {
if (!spk_man->SetupGeneration()) {
error = Untranslated(
"Unable to generate initial keys");
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
}
}
}
}
// Relock the wallet
wallet->Lock();
}
}
AddWallet(wallet);
wallet->postInitProcess();
// Write the wallet settings
UpdateWalletSetting(chain, name, load_on_start, warnings);
status = DatabaseStatus::SUCCESS;
return wallet;
}
/** @defgroup mapWallet
*
* @{
*/
std::string COutput::ToString() const {
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i,
nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
const CChainParams &CWallet::GetChainParams() const {
// Get CChainParams from interfaces::Chain, unless wallet doesn't have a
// chain (i.e. bitcoin-wallet), in which case return global Params()
return m_chain ? m_chain->params() : Params();
}
const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const {
AssertLockHeld(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end()) {
return nullptr;
}
return &(it->second);
}
void CWallet::UpgradeKeyMetadata() {
if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
return;
}
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return;
}
spk_man->UpgradeKeyMetadata();
SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
bool CWallet::Unlock(const SecureString &strWalletPassphrase,
bool accept_no_keys) {
CCrypter crypter;
CKeyingMaterial _vMasterKey;
{
LOCK(cs_wallet);
for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
if (!crypter.SetKeyFromPassphrase(
strWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey,
_vMasterKey)) {
// try another master key
continue;
}
if (Unlock(_vMasterKey, accept_no_keys)) {
// Now that we've unlocked, upgrade the key metadata
UpgradeKeyMetadata();
return true;
}
}
}
return false;
}
bool CWallet::ChangeWalletPassphrase(
const SecureString &strOldWalletPassphrase,
const SecureString &strNewWalletPassphrase) {
bool fWasLocked = IsLocked();
LOCK(cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial _vMasterKey;
for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
if (!crypter.SetKeyFromPassphrase(
strOldWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) {
return false;
}
if (Unlock(_vMasterKey)) {
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations = static_cast<unsigned int>(
pMasterKey.second.nDeriveIterations *
(100 / ((double)(GetTimeMillis() - nStartTime))));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations =
(pMasterKey.second.nDeriveIterations +
static_cast<unsigned int>(
pMasterKey.second.nDeriveIterations * 100 /
double(GetTimeMillis() - nStartTime))) /
2;
if (pMasterKey.second.nDeriveIterations < 25000) {
pMasterKey.second.nDeriveIterations = 25000;
}
WalletLogPrintf(
"Wallet passphrase changed to an nDeriveIterations of %i\n",
pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(
strNewWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(_vMasterKey,
pMasterKey.second.vchCryptedKey)) {
return false;
}
WalletBatch(*database).WriteMasterKey(pMasterKey.first,
pMasterKey.second);
if (fWasLocked) {
Lock();
}
return true;
}
}
return false;
}
void CWallet::chainStateFlushed(const CBlockLocator &loc) {
WalletBatch batch(*database);
batch.WriteBestBlock(loc);
}
void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch *batch_in,
bool fExplicit) {
LOCK(cs_wallet);
if (nWalletVersion >= nVersion) {
return;
}
// When doing an explicit upgrade, if we pass the max version permitted,
// upgrade all the way.
if (fExplicit && nVersion > nWalletMaxVersion) {
nVersion = FEATURE_LATEST;
}
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion) {
nWalletMaxVersion = nVersion;
}
WalletBatch *batch = batch_in ? batch_in : new WalletBatch(*database);
if (nWalletVersion > 40000) {
batch->WriteMinVersion(nWalletVersion);
}
if (!batch_in) {
delete batch;
}
}
bool CWallet::SetMaxVersion(int nVersion) {
LOCK(cs_wallet);
// Cannot downgrade below current version
if (nWalletVersion > nVersion) {
return false;
}
nWalletMaxVersion = nVersion;
return true;
}
std::set<TxId> CWallet::GetConflicts(const TxId &txid) const {
std::set<TxId> result;
AssertLockHeld(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end()) {
return result;
}
const CWalletTx &wtx = it->second;
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
for (const CTxIn &txin : wtx.tx->vin) {
if (mapTxSpends.count(txin.prevout) <= 1) {
// No conflict if zero or one spends.
continue;
}
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator _it = range.first; _it != range.second;
++_it) {
result.insert(_it->second);
}
}
return result;
}
bool CWallet::HasWalletSpend(const TxId &txid) const {
AssertLockHeld(cs_wallet);
auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
return (iter != mapTxSpends.end() && iter->first.GetTxId() == txid);
}
void CWallet::Flush() {
database->Flush();
}
void CWallet::Close() {
database->Close();
}
void CWallet::SyncMetaData(
std::pair<TxSpends::iterator, TxSpends::iterator> range) {
// We want all the wallet transactions in range to have the same metadata as
// the oldest (smallest nOrderPos).
// So: find smallest nOrderPos:
int nMinOrderPos = std::numeric_limits<int>::max();
const CWalletTx *copyFrom = nullptr;
for (TxSpends::iterator it = range.first; it != range.second; ++it) {
const CWalletTx *wtx = &mapWallet.at(it->second);
if (wtx->nOrderPos < nMinOrderPos) {
nMinOrderPos = wtx->nOrderPos;
copyFrom = wtx;
}
}
if (!copyFrom) {
return;
}
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it) {
const TxId &txid = it->second;
CWalletTx *copyTo = &mapWallet.at(txid);
if (copyFrom == copyTo) {
continue;
}
assert(
copyFrom &&
"Oldest wallet transaction in range assumed to have been found.");
if (!copyFrom->IsEquivalentTo(*copyTo)) {
continue;
}
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied
// on purpose.
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
// nOrderPos not copied on purpose cached members not copied on purpose.
}
}
/**
* Outpoint is spent if any non-conflicted transaction, spends it:
*/
bool CWallet::IsSpent(const COutPoint &outpoint) const {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range =
mapTxSpends.equal_range(outpoint);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
const TxId &wtxid = it->second;
std::map<TxId, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
int depth = mit->second.GetDepthInMainChain();
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) {
// Spent
return true;
}
}
}
return false;
}
void CWallet::AddToSpends(const COutPoint &outpoint, const TxId &wtxid) {
mapTxSpends.insert(std::make_pair(outpoint, wtxid));
setLockedCoins.erase(outpoint);
std::pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
}
void CWallet::AddToSpends(const TxId &wtxid) {
auto it = mapWallet.find(wtxid);
assert(it != mapWallet.end());
CWalletTx &thisTx = it->second;
// Coinbases don't spend anything!
if (thisTx.IsCoinBase()) {
return;
}
for (const CTxIn &txin : thisTx.tx->vin) {
AddToSpends(txin.prevout, wtxid);
}
}
bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) {
if (IsCrypted()) {
return false;
}
CKeyingMaterial _vMasterKey;
_vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000,
kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations = static_cast<unsigned int>(
2500000 / double(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
kMasterKey.nDeriveIterations,
kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations =
(kMasterKey.nDeriveIterations +
static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 /
double(GetTimeMillis() - nStartTime))) /
2;
if (kMasterKey.nDeriveIterations < 25000) {
kMasterKey.nDeriveIterations = 25000;
}
WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n",
kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
kMasterKey.nDeriveIterations,
kMasterKey.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) {
return false;
}
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
WalletBatch *encrypted_batch = new WalletBatch(*database);
if (!encrypted_batch->TxnBegin()) {
delete encrypted_batch;
encrypted_batch = nullptr;
return false;
}
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
for (const auto &spk_man_pair : m_spk_managers) {
auto spk_man = spk_man_pair.second.get();
if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) {
encrypted_batch->TxnAbort();
delete encrypted_batch;
encrypted_batch = nullptr;
// 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);
}
}
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true);
if (!encrypted_batch->TxnCommit()) {
delete encrypted_batch;
encrypted_batch = nullptr;
// 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 encrypted_batch;
encrypted_batch = nullptr;
Lock();
Unlock(strWalletPassphrase);
// If we are using descriptors, make new descriptors with a new seed
if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) &&
!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) {
SetupDescriptorScriptPubKeyMans();
} else if (auto spk_man = GetLegacyScriptPubKeyMan()) {
// if we are using HD, replace the HD seed with a new one
if (spk_man->IsHDEnabled()) {
if (!spk_man->SetupGeneration(true)) {
return false;
}
}
}
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might
// keep bits of the unencrypted private key in slack space in the
// database file.
database->Rewrite();
// BDB seems to have a bad habit of writing old data into
// slack space in .dat files; that is bad if the old data is
// unencrypted private keys. So:
database->ReloadDbEnv();
}
NotifyStatusChanged(this);
return true;
}
DBErrors CWallet::ReorderTransactions() {
LOCK(cs_wallet);
WalletBatch batch(*database);
// Old wallets didn't have any defined order for transactions. Probably a
// bad idea to change the output of this.
// First: get all CWalletTx into a sorted-by-time
// multimap.
TxItems txByTime;
for (auto &entry : mapWallet) {
CWalletTx *wtx = &entry.second;
txByTime.insert(std::make_pair(wtx->nTimeReceived, wtx));
}
nOrderPosNext = 0;
std::vector<int64_t> nOrderPosOffsets;
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) {
CWalletTx *const pwtx = (*it).second;
int64_t &nOrderPos = pwtx->nOrderPos;
if (nOrderPos == -1) {
nOrderPos = nOrderPosNext++;
nOrderPosOffsets.push_back(nOrderPos);
if (!batch.WriteTx(*pwtx)) {
return DBErrors::LOAD_FAIL;
}
} else {
int64_t nOrderPosOff = 0;
for (const int64_t &nOffsetStart : nOrderPosOffsets) {
if (nOrderPos >= nOffsetStart) {
++nOrderPosOff;
}
}
nOrderPos += nOrderPosOff;
nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
if (!nOrderPosOff) {
continue;
}
// Since we're changing the order, write it back.
if (!batch.WriteTx(*pwtx)) {
return DBErrors::LOAD_FAIL;
}
}
}
batch.WriteOrderPosNext(nOrderPosNext);
return DBErrors::LOAD_OK;
}
int64_t CWallet::IncOrderPosNext(WalletBatch *batch) {
AssertLockHeld(cs_wallet);
int64_t nRet = nOrderPosNext++;
if (batch) {
batch->WriteOrderPosNext(nOrderPosNext);
} else {
WalletBatch(*database).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
void CWallet::MarkDirty() {
LOCK(cs_wallet);
for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
item.second.MarkDirty();
}
}
void CWallet::SetSpentKeyState(WalletBatch &batch, const TxId &txid,
unsigned int n, bool used,
std::set<CTxDestination> &tx_destinations) {
AssertLockHeld(cs_wallet);
const CWalletTx *srctx = GetWalletTx(txid);
if (!srctx) {
return;
}
CTxDestination dst;
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
if (IsMine(dst)) {
if (used && !GetDestData(dst, "used", nullptr)) {
// p for "present", opposite of absent (null)
if (AddDestData(batch, dst, "used", "p")) {
tx_destinations.insert(dst);
}
} else if (!used && GetDestData(dst, "used", nullptr)) {
EraseDestData(batch, dst, "used");
}
}
}
}
bool CWallet::IsSpentKey(const TxId &txid, unsigned int n) const {
AssertLockHeld(cs_wallet);
const CWalletTx *srctx = GetWalletTx(txid);
if (srctx) {
assert(srctx->tx->vout.size() > n);
CTxDestination dest;
if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) {
return false;
}
if (GetDestData(dest, "used", nullptr)) {
return true;
}
if (IsLegacy()) {
LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan();
assert(spk_man != nullptr);
for (const auto &keyid :
GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) {
PKHash pkh_dest(keyid);
if (GetDestData(pkh_dest, "used", nullptr)) {
return true;
}
}
}
}
return false;
}
CWalletTx *CWallet::AddToWallet(CTransactionRef tx,
const CWalletTx::Confirmation &confirm,
const UpdateWalletTxFn &update_wtx,
bool fFlushOnClose) {
LOCK(cs_wallet);
WalletBatch batch(*database, "r+", fFlushOnClose);
const TxId &txid = tx->GetId();
if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// Mark used destinations
std::set<CTxDestination> tx_destinations;
for (const CTxIn &txin : tx->vin) {
const COutPoint &op = txin.prevout;
SetSpentKeyState(batch, op.GetTxId(), op.GetN(), true,
tx_destinations);
}
MarkDestinationsDirty(tx_destinations);
}
// Inserts only if not already there, returns tx inserted or tx found.
auto ret =
mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid),
std::forward_as_tuple(this, tx));
CWalletTx &wtx = (*ret.first).second;
bool fInsertedNew = ret.second;
bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew);
if (fInsertedNew) {
wtx.m_confirm = confirm;
wtx.nTimeReceived = chain().getAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&batch);
wtx.m_it_wtxOrdered =
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(txid);
}
if (!fInsertedNew) {
if (confirm.status != wtx.m_confirm.status) {
wtx.m_confirm.status = confirm.status;
wtx.m_confirm.nIndex = confirm.nIndex;
wtx.m_confirm.hashBlock = confirm.hashBlock;
wtx.m_confirm.block_height = confirm.block_height;
fUpdated = true;
} else {
assert(wtx.m_confirm.nIndex == confirm.nIndex);
assert(wtx.m_confirm.hashBlock == confirm.hashBlock);
assert(wtx.m_confirm.block_height == confirm.block_height);
}
}
//// debug print
WalletLogPrintf("AddToWallet %s %s%s\n", txid.ToString(),
(fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if ((fInsertedNew || fUpdated) && !batch.WriteTx(wtx)) {
return nullptr;
}
// Break debit/credit balance caches:
wtx.MarkDirty();
// Notify UI of new or updated transaction.
NotifyTransactionChanged(this, txid, fInsertedNew ? CT_NEW : CT_UPDATED);
#if defined(HAVE_SYSTEM)
// Notify an external script when a wallet transaction comes in or is
// updated.
std::string strCmd = gArgs.GetArg("-walletnotify", "");
if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s", txid.GetHex());
#ifndef WIN32
// Substituting the wallet name isn't currently supported on windows
// because windows shell escaping has not been implemented yet:
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875
// A few ways it could be implemented in the future are described in:
// https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094
boost::replace_all(strCmd, "%w", ShellEscape(GetName()));
#endif
std::thread t(runCommand, strCmd);
// Thread runs free.
t.detach();
}
#endif
return &wtx;
}
bool CWallet::LoadToWallet(const TxId &txid, const UpdateWalletTxFn &fill_wtx) {
const auto &ins =
mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid),
std::forward_as_tuple(this, nullptr));
CWalletTx &wtx = ins.first->second;
if (!fill_wtx(wtx, ins.second)) {
return false;
}
// If wallet doesn't have a chain (e.g wallet-tool), don't bother to update
// txn.
if (HaveChain()) {
std::optional<int> block_height =
chain().getBlockHeight(wtx.m_confirm.hashBlock);
if (block_height) {
// Update cached block height variable since it not stored in the
// serialized transaction.
wtx.m_confirm.block_height = *block_height;
} else if (wtx.isConflicted() || wtx.isConfirmed()) {
// If tx block (or conflicting block) was reorged out of chain
// while the wallet was shutdown, change tx status to UNCONFIRMED
// and reset block height, hash, and index. ABANDONED tx don't have
// associated blocks and don't need to be updated. The case where a
// transaction was reorged out while online and then reconfirmed
// while offline is covered by the rescan logic.
wtx.setUnconfirmed();
wtx.m_confirm.hashBlock = BlockHash();
wtx.m_confirm.block_height = 0;
wtx.m_confirm.nIndex = 0;
}
}
if (/* insertion took place */ ins.second) {
wtx.m_it_wtxOrdered =
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
AddToSpends(txid);
for (const CTxIn &txin : wtx.tx->vin) {
auto it = mapWallet.find(txin.prevout.GetTxId());
if (it != mapWallet.end()) {
CWalletTx &prevtx = it->second;
if (prevtx.isConflicted()) {
MarkConflicted(prevtx.m_confirm.hashBlock,
prevtx.m_confirm.block_height, wtx.GetId());
}
}
}
return true;
}
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx,
CWalletTx::Confirmation confirm,
bool fUpdate) {
const CTransaction &tx = *ptx;
AssertLockHeld(cs_wallet);
if (!confirm.hashBlock.IsNull()) {
for (const CTxIn &txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator>
range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetId()) {
WalletLogPrintf(
"Transaction %s (in block %s) conflicts with wallet "
"transaction %s (both spend %s:%i)\n",
tx.GetId().ToString(), confirm.hashBlock.ToString(),
range.first->second.ToString(),
range.first->first.GetTxId().ToString(),
range.first->first.GetN());
MarkConflicted(confirm.hashBlock, confirm.block_height,
range.first->second);
}
range.first++;
}
}
}
bool fExisted = mapWallet.count(tx.GetId()) != 0;
if (fExisted && !fUpdate) {
return false;
}
if (fExisted || IsMine(tx) || IsFromMe(tx)) {
/**
* Check if any keys in the wallet keypool that were supposed to be
* unused have appeared in a new transaction. If so, remove those keys
* from the keypool. This can happen when restoring an old wallet backup
* that does not contain the mostly recently created transactions from
* newer versions of the wallet.
*/
// loop though all outputs
for (const CTxOut &txout : tx.vout) {
for (const auto &spk_man_pair : m_spk_managers) {
spk_man_pair.second->MarkUnusedAddresses(txout.scriptPubKey);
}
}
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
return AddToWallet(MakeTransactionRef(tx), confirm,
/* update_wtx= */ nullptr,
/* fFlushOnClose= */ false);
}
return false;
}
bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const {
LOCK(cs_wallet);
const CWalletTx *wtx = GetWalletTx(txid);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 &&
!wtx->InMempool();
}
void CWallet::MarkInputsDirty(const CTransactionRef &tx) {
for (const CTxIn &txin : tx->vin) {
auto it = mapWallet.find(txin.prevout.GetTxId());
if (it != mapWallet.end()) {
it->second.MarkDirty();
}
}
}
bool CWallet::AbandonTransaction(const TxId &txid) {
LOCK(cs_wallet);
WalletBatch batch(*database, "r+");
std::set<TxId> todo;
std::set<TxId> done;
// Can't mark abandoned if confirmed or in mempool
auto it = mapWallet.find(txid);
assert(it != mapWallet.end());
CWalletTx &origtx = it->second;
if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
return false;
}
todo.insert(txid);
while (!todo.empty()) {
const TxId now = *todo.begin();
todo.erase(now);
done.insert(now);
it = mapWallet.find(now);
assert(it != mapWallet.end());
CWalletTx &wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be.
assert(currentconfirm <= 0);
// If (currentconfirm < 0) {Tx and spends are already conflicted, no
// need to abandon}
if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can
// be in mempool.
assert(!wtx.InMempool());
wtx.setAbandoned();
wtx.MarkDirty();
batch.WriteTx(wtx);
NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet
// that spend them abandoned too.
TxSpends::const_iterator iter =
mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
}
iter++;
}
// If a transaction changes 'conflicted' state, that changes the
// balance available of the outputs it spends. So force those to be
// recomputed.
MarkInputsDirty(wtx.tx);
}
}
return true;
}
void CWallet::MarkConflicted(const BlockHash &hashBlock, int conflicting_height,
const TxId &txid) {
LOCK(cs_wallet);
int conflictconfirms =
(m_last_block_processed_height - conflicting_height + 1) * -1;
// If number of conflict confirms cannot be determined, this means that the
// block is still unknown or not yet part of the main chain, for example
// when loading the wallet during a reindex. Do nothing in that case.
if (conflictconfirms >= 0) {
return;
}
// Do not flush the wallet here for performance reasons.
WalletBatch batch(*database, "r+", false);
std::set<TxId> todo;
std::set<TxId> done;
todo.insert(txid);
while (!todo.empty()) {
const TxId now = *todo.begin();
todo.erase(now);
done.insert(now);
auto it = mapWallet.find(now);
assert(it != mapWallet.end());
CWalletTx &wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
wtx.m_confirm.nIndex = 0;
wtx.m_confirm.hashBlock = hashBlock;
wtx.m_confirm.block_height = conflicting_height;
wtx.setConflicted();
wtx.MarkDirty();
batch.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet
// that spend them conflicted too.
TxSpends::const_iterator iter =
mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
}
iter++;
}
// If a transaction changes 'conflicted' state, that changes the
// balance available of the outputs it spends. So force those to be
// recomputed.
MarkInputsDirty(wtx.tx);
}
}
}
void CWallet::SyncTransaction(const CTransactionRef &ptx,
CWalletTx::Confirmation confirm, bool update_tx) {
if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx)) {
// Not one of ours
return;
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be
// recomputed, also:
MarkInputsDirty(ptx);
}
void CWallet::transactionAddedToMempool(const CTransactionRef &tx) {
LOCK(cs_wallet);
SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block_height */ 0,
BlockHash(), /* nIndex */ 0});
auto it = mapWallet.find(tx->GetId());
if (it != mapWallet.end()) {
it->second.fInMempool = true;
}
}
void CWallet::transactionRemovedFromMempool(const CTransactionRef &tx,
MemPoolRemovalReason reason) {
LOCK(cs_wallet);
auto it = mapWallet.find(tx->GetId());
if (it != mapWallet.end()) {
it->second.fInMempool = false;
}
// Handle transactions that were removed from the mempool because they
// conflict with transactions in a newly connected block.
if (reason == MemPoolRemovalReason::CONFLICT) {
// Call SyncNotifications, so external -walletnotify notifications will
// be triggered for these transactions. Set Status::UNCONFIRMED instead
// of Status::CONFLICTED for a few reasons:
//
// 1. The transactionRemovedFromMempool callback does not currently
// provide the conflicting block's hash and height, and for backwards
// compatibility reasons it may not be not safe to store conflicted
// wallet transactions with a null block hash. See
// https://github.com/bitcoin/bitcoin/pull/18600#discussion_r420195993.
// 2. For most of these transactions, the wallet's internal conflict
// detection in the blockConnected handler will subsequently call
// MarkConflicted and update them with CONFLICTED status anyway. This
// applies to any wallet transaction that has inputs spent in the
// block, or that has ancestors in the wallet with inputs spent by
// the block.
// 3. Longstanding behavior since the sync implementation in
// https://github.com/bitcoin/bitcoin/pull/9371 and the prior sync
// implementation before that was to mark these transactions
// unconfirmed rather than conflicted.
//
// Nothing described above should be seen as an unchangeable requirement
// when improving this code in the future. The wallet's heuristics for
// distinguishing between conflicted and unconfirmed transactions are
// imperfect, and could be improved in general, see
// https://github.com/bitcoin-core/bitcoin-devwiki/wiki/Wallet-Transaction-Conflict-Tracking
SyncTransaction(tx,
{CWalletTx::Status::UNCONFIRMED, /* block height */ 0,
BlockHash(), /* index */ 0});
}
}
void CWallet::blockConnected(const CBlock &block, int height) {
const BlockHash &block_hash = block.GetHash();
LOCK(cs_wallet);
m_last_block_processed_height = height;
m_last_block_processed = block_hash;
for (size_t index = 0; index < block.vtx.size(); index++) {
SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height,
block_hash, int(index)});
transactionRemovedFromMempool(block.vtx[index],
MemPoolRemovalReason::BLOCK);
}
}
void CWallet::blockDisconnected(const CBlock &block, int height) {
LOCK(cs_wallet);
// At block disconnection, this will change an abandoned transaction to
// be unconfirmed, whether or not the transaction is added back to the
// mempool. User may have to call abandontransaction again. It may be
// addressed in the future with a stickier abandoned state or even removing
// abandontransaction call.
m_last_block_processed_height = height - 1;
m_last_block_processed = block.hashPrevBlock;
for (const CTransactionRef &ptx : block.vtx) {
SyncTransaction(ptx,
{CWalletTx::Status::UNCONFIRMED, /* block_height */ 0,
BlockHash(), /* nIndex */ 0});
}
}
void CWallet::updatedBlockTip() {
m_best_block_time = GetTime();
}
void CWallet::BlockUntilSyncedToCurrentChain() const {
AssertLockNotHeld(cs_wallet);
// Skip the queue-draining stuff if we know we're caught up with
// chainActive.Tip(), otherwise put a callback in the validation interface
// queue and wait for the queue to drain enough to execute it (indicating we
// are caught up at least with the time we entered this function).
const BlockHash last_block_hash =
WITH_LOCK(cs_wallet, return m_last_block_processed);
chain().waitForNotificationsIfTipChanged(last_block_hash);
}
isminetype CWallet::IsMine(const CTxIn &txin) const {
AssertLockHeld(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator mi =
mapWallet.find(txin.prevout.GetTxId());
if (mi != mapWallet.end()) {
const CWalletTx &prev = (*mi).second;
if (txin.prevout.GetN() < prev.tx->vout.size()) {
return IsMine(prev.tx->vout[txin.prevout.GetN()]);
}
}
return ISMINE_NO;
}
// Note that this function doesn't distinguish between a 0-valued input, and a
// not-"is mine" (according to the filter) input.
Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const {
LOCK(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator mi =
mapWallet.find(txin.prevout.GetTxId());
if (mi != mapWallet.end()) {
const CWalletTx &prev = (*mi).second;
if (txin.prevout.GetN() < prev.tx->vout.size()) {
if (IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter) {
return prev.tx->vout[txin.prevout.GetN()].nValue;
}
}
}
return Amount::zero();
}
isminetype CWallet::IsMine(const CTxOut &txout) const {
AssertLockHeld(cs_wallet);
return IsMine(txout.scriptPubKey);
}
isminetype CWallet::IsMine(const CTxDestination &dest) const {
AssertLockHeld(cs_wallet);
return IsMine(GetScriptForDestination(dest));
}
isminetype CWallet::IsMine(const CScript &script) const {
AssertLockHeld(cs_wallet);
isminetype result = ISMINE_NO;
for (const auto &spk_man_pair : m_spk_managers) {
result = std::max(result, spk_man_pair.second->IsMine(script));
}
return result;
}
Amount CWallet::GetCredit(const CTxOut &txout,
const isminefilter &filter) const {
if (!MoneyRange(txout.nValue)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
LOCK(cs_wallet);
return (IsMine(txout) & filter) ? txout.nValue : Amount::zero();
}
bool CWallet::IsChange(const CTxOut &txout) const {
return IsChange(txout.scriptPubKey);
}
bool CWallet::IsChange(const CScript &script) const {
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book is
// change. That assumption is likely to break when we implement
// multisignature wallets that return change back into a
// multi-signature-protected address; a better way of identifying which
// outputs are 'the send' and which are 'the change' will need to be
// implemented (maybe extend CWalletTx to remember which output, if any, was
// change).
AssertLockHeld(cs_wallet);
if (IsMine(script)) {
CTxDestination address;
if (!ExtractDestination(script, address)) {
return true;
}
if (!FindAddressBookEntry(address)) {
return true;
}
}
return false;
}
Amount CWallet::GetChange(const CTxOut &txout) const {
AssertLockHeld(cs_wallet);
if (!MoneyRange(txout.nValue)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
return (IsChange(txout) ? txout.nValue : Amount::zero());
}
bool CWallet::IsMine(const CTransaction &tx) const {
AssertLockHeld(cs_wallet);
for (const CTxOut &txout : tx.vout) {
if (IsMine(txout)) {
return true;
}
}
return false;
}
bool CWallet::IsFromMe(const CTransaction &tx) const {
return GetDebit(tx, ISMINE_ALL) > Amount::zero();
}
Amount CWallet::GetDebit(const CTransaction &tx,
const isminefilter &filter) const {
Amount nDebit = Amount::zero();
for (const CTxIn &txin : tx.vin) {
nDebit += GetDebit(txin, filter);
if (!MoneyRange(nDebit)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nDebit;
}
bool CWallet::IsAllFromMe(const CTransaction &tx,
const isminefilter &filter) const {
LOCK(cs_wallet);
for (const CTxIn &txin : tx.vin) {
auto mi = mapWallet.find(txin.prevout.GetTxId());
if (mi == mapWallet.end()) {
// Any unknown inputs can't be from us.
return false;
}
const CWalletTx &prev = (*mi).second;
if (txin.prevout.GetN() >= prev.tx->vout.size()) {
// Invalid input!
return false;
}
if (!(IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter)) {
return false;
}
}
return true;
}
Amount CWallet::GetCredit(const CTransaction &tx,
const isminefilter &filter) const {
Amount nCredit = Amount::zero();
for (const CTxOut &txout : tx.vout) {
nCredit += GetCredit(txout, filter);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nCredit;
}
Amount CWallet::GetChange(const CTransaction &tx) const {
LOCK(cs_wallet);
Amount nChange = Amount::zero();
for (const CTxOut &txout : tx.vout) {
nChange += GetChange(txout);
if (!MoneyRange(nChange)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nChange;
}
bool CWallet::IsHDEnabled() const {
// All Active ScriptPubKeyMans must be HD for this to be true
bool result = true;
for (const auto &spk_man : GetActiveScriptPubKeyMans()) {
result &= spk_man->IsHDEnabled();
}
return result;
}
bool CWallet::CanGetAddresses(bool internal) const {
LOCK(cs_wallet);
if (m_spk_managers.empty()) {
return false;
}
for (OutputType t : OUTPUT_TYPES) {
auto spk_man = GetScriptPubKeyMan(t, internal);
if (spk_man && spk_man->CanGetAddresses(internal)) {
return true;
}
}
return false;
}
void CWallet::SetWalletFlag(uint64_t flags) {
LOCK(cs_wallet);
m_wallet_flags |= flags;
if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) {
throw std::runtime_error(std::string(__func__) +
": writing wallet flags failed");
}
}
void CWallet::UnsetWalletFlag(uint64_t flag) {
WalletBatch batch(*database);
UnsetWalletFlagWithDB(batch, flag);
}
void CWallet::UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag) {
LOCK(cs_wallet);
m_wallet_flags &= ~flag;
if (!batch.WriteWalletFlags(m_wallet_flags)) {
throw std::runtime_error(std::string(__func__) +
": writing wallet flags failed");
}
}
void CWallet::UnsetBlankWalletFlag(WalletBatch &batch) {
UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
}
bool CWallet::IsWalletFlagSet(uint64_t flag) const {
return (m_wallet_flags & flag);
}
bool CWallet::LoadWalletFlags(uint64_t flags) {
LOCK(cs_wallet);
if (((flags & KNOWN_WALLET_FLAGS) >> 32) ^ (flags >> 32)) {
// contains unknown non-tolerable wallet flags
return false;
}
m_wallet_flags = flags;
return true;
}
bool CWallet::AddWalletFlags(uint64_t flags) {
LOCK(cs_wallet);
// We should never be writing unknown non-tolerable wallet flags
assert(((flags & KNOWN_WALLET_FLAGS) >> 32) == (flags >> 32));
if (!WalletBatch(*database).WriteWalletFlags(flags)) {
throw std::runtime_error(std::string(__func__) +
": writing wallet flags failed");
}
return LoadWalletFlags(flags);
}
int64_t CWalletTx::GetTxTime() const {
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout,
bool use_max_sig) const {
// Fill in dummy signatures for fee calculation.
const CScript &scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
std::unique_ptr<SigningProvider> provider =
GetSolvingProvider(scriptPubKey);
if (!provider) {
// We don't know about this scriptpbuKey;
return false;
}
if (!ProduceSignature(*provider,
use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR
: DUMMY_SIGNATURE_CREATOR,
scriptPubKey, sigdata)) {
return false;
}
UpdateInput(tx_in, sigdata);
return true;
}
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71
// bytes)
bool CWallet::DummySignTx(CMutableTransaction &txNew,
const std::vector<CTxOut> &txouts,
bool use_max_sig) const {
// Fill in dummy signatures for fee calculation.
int nIn = 0;
for (const auto &txout : txouts) {
if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) {
return false;
}
nIn++;
}
return true;
}
bool CWallet::ImportScripts(const std::set<CScript> scripts,
int64_t timestamp) {
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
return spk_man->ImportScripts(scripts, timestamp);
}
bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map,
const int64_t timestamp) {
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
return spk_man->ImportPrivKeys(privkey_map, timestamp);
}
bool CWallet::ImportPubKeys(
const std::vector<CKeyID> &ordered_pubkeys,
const std::map<CKeyID, CPubKey> &pubkey_map,
const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins,
const bool add_keypool, const bool internal, const int64_t timestamp) {
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
return spk_man->ImportPubKeys(ordered_pubkeys, pubkey_map, key_origins,
add_keypool, internal, timestamp);
}
bool CWallet::ImportScriptPubKeys(const std::string &label,
const std::set<CScript> &script_pub_keys,
const bool have_solving_data,
const bool apply_label,
const int64_t timestamp) {
auto spk_man = GetLegacyScriptPubKeyMan();
if (!spk_man) {
return false;
}
LOCK(spk_man->cs_KeyStore);
if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data,
timestamp)) {
return false;
}
if (apply_label) {
WalletBatch batch(*database);
for (const CScript &script : script_pub_keys) {
CTxDestination dest;
ExtractDestination(script, dest);
if (IsValidDestination(dest)) {
SetAddressBookWithDB(batch, dest, label, "receive");
}
}
}
return true;
}
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx,
const CWallet *wallet, bool use_max_sig) {
std::vector<CTxOut> txouts;
for (auto &input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.GetTxId());
// Can not estimate size without knowing the input details
if (mi == wallet->mapWallet.end()) {
return -1;
}
assert(input.prevout.GetN() < mi->second.tx->vout.size());
txouts.emplace_back(mi->second.tx->vout[input.prevout.GetN()]);
}
return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
}
// txouts needs to be in the order of tx.vin
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx,
const CWallet *wallet,
const std::vector<CTxOut> &txouts,
bool use_max_sig) {
CMutableTransaction txNew(tx);
if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
return -1;
}
return GetSerializeSize(txNew, PROTOCOL_VERSION);
}
int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *wallet,
bool use_max_sig) {
CMutableTransaction txn;
txn.vin.push_back(CTxIn(COutPoint()));
if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) {
return -1;
}
return GetSerializeSize(txn.vin[0], PROTOCOL_VERSION);
}
void CWalletTx::GetAmounts(std::list<COutputEntry> &listReceived,
std::list<COutputEntry> &listSent, Amount &nFee,
const isminefilter &filter) const {
nFee = Amount::zero();
listReceived.clear();
listSent.clear();
// Compute fee:
Amount nDebit = GetDebit(filter);
// debit>0 means we signed/sent this transaction.
if (nDebit > Amount::zero()) {
Amount nValueOut = tx->GetValueOut();
nFee = (nDebit - nValueOut);
}
LOCK(pwallet->cs_wallet);
// Sent/received.
for (unsigned int i = 0; i < tx->vout.size(); ++i) {
const CTxOut &txout = tx->vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > Amount::zero()) {
// Don't report 'change' txouts
if (pwallet->IsChange(txout)) {
continue;
}
} else if (!(fIsMine & filter)) {
continue;
}
// In either case, we need to get the destination address.
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address) &&
!txout.scriptPubKey.IsUnspendable()) {
pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown "
"transaction type found, txid %s\n",
this->GetId().ToString());
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent"
// entry.
if (nDebit > Amount::zero()) {
listSent.push_back(output);
}
// If we are receiving the output, add it as a "received" entry.
if (fIsMine & filter) {
listReceived.push_back(output);
}
}
}
/**
* Scan active chain for relevant transactions after importing keys. This should
* be called whenever new keys are added to the wallet, with the oldest key
* creation time.
*
* @return Earliest timestamp that could be successfully scanned from. Timestamp
* returned will be higher than startTime if relevant blocks could not be read.
*/
int64_t CWallet::RescanFromTime(int64_t startTime,
const WalletRescanReserver &reserver,
bool update) {
// Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs
// to be scanned.
int start_height = 0;
BlockHash start_block;
bool start = chain().findFirstBlockWithTimeAndHeight(
startTime - TIMESTAMP_WINDOW, 0,
FoundBlock().hash(start_block).height(start_height));
WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__,
start ? WITH_LOCK(cs_wallet, return GetLastBlockHeight()) -
start_height + 1
: 0);
if (start) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
ScanResult result = ScanForWalletTransactions(
start_block, start_height, {} /* max_height */, reserver, update);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
CHECK_NONFATAL(chain().findBlock(result.last_failed_block,
FoundBlock().maxTime(time_max)));
return time_max + TIMESTAMP_WINDOW + 1;
}
}
return startTime;
}
/**
* Scan the block chain (starting in start_block) for transactions from or to
* us. If fUpdate is true, found transactions that already exist in the wallet
* will be updated.
*
* @param[in] start_block Scan starting block. If block is not on the active
* chain, the scan will return SUCCESS immediately.
* @param[in] start_height Height of start_block
* @param[in] max_height Optional max scanning height. If unset there is
* no maximum and scanning can continue to the tip
*
* @return ScanResult returning scan information and indicating success or
* failure. Return status will be set to SUCCESS if scan was
* successful. FAILURE if a complete rescan was not possible (due to
* pruning or corruption). USER_ABORT if the rescan was aborted before
* it could complete.
*
* @pre Caller needs to make sure start_block (and the optional stop_block) are
* on the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
CWallet::ScanResult CWallet::ScanForWalletTransactions(
const BlockHash &start_block, int start_height,
std::optional<int> max_height, const WalletRescanReserver &reserver,
bool fUpdate) {
int64_t nNow = GetTime();
int64_t start_time = GetTimeMillis();
assert(reserver.isReserved());
BlockHash block_hash = start_block;
ScanResult result;
WalletLogPrintf("Rescan started from block %s...\n",
start_block.ToString());
fAbortRescan = false;
// Show rescan progress in GUI as dialog or on splashscreen, if -rescan on
// startup.
ShowProgress(
strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0);
BlockHash tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
BlockHash end_hash = tip_hash;
if (max_height) {
chain().findAncestorByHeight(tip_hash, *max_height,
FoundBlock().hash(end_hash));
}
double progress_begin = chain().guessVerificationProgress(block_hash);
double progress_end = chain().guessVerificationProgress(end_hash);
double progress_current = progress_begin;
int block_height = start_height;
while (!fAbortRescan && !chain().shutdownRequested()) {
if (progress_end - progress_begin > 0.0) {
m_scanning_progress = (progress_current - progress_begin) /
(progress_end - progress_begin);
} else {
// avoid divide-by-zero for single block scan range (i.e. start and
// stop hashes are equal)
m_scanning_progress = 0;
}
if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) {
ShowProgress(
strprintf("%s " + _("Rescanning...").translated,
GetDisplayName()),
std::max(1, std::min(99, (int)(m_scanning_progress * 100))));
}
if (GetTime() >= nNow + 60) {
nNow = GetTime();
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n",
block_height, progress_current);
}
CBlock block;
bool next_block;
BlockHash next_block_hash;
bool reorg = false;
if (chain().findBlock(block_hash, FoundBlock().data(block)) &&
!block.IsNull()) {
LOCK(cs_wallet);
next_block = chain().findNextBlock(
block_hash, block_height, FoundBlock().hash(next_block_hash),
&reorg);
if (reorg) {
// Abort scan if current block is no longer active, to prevent
// marking transactions as coming from the wrong block.
// TODO: This should return success instead of failure, see
// https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
break;
}
for (size_t posInBlock = 0; posInBlock < block.vtx.size();
++posInBlock) {
CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED,
block_height, block_hash,
posInBlock);
SyncTransaction(block.vtx[posInBlock],
{CWalletTx::Status::CONFIRMED, block_height,
block_hash, int(posInBlock)},
fUpdate);
}
// scan succeeded, record block as most recent successfully
// scanned
result.last_scanned_block = block_hash;
result.last_scanned_height = block_height;
} else {
// could not scan block, keep scanning but record this block as
// the most recent failure
result.last_failed_block = block_hash;
result.status = ScanResult::FAILURE;
next_block = chain().findNextBlock(
block_hash, block_height, FoundBlock().hash(next_block_hash),
&reorg);
}
if (max_height && block_height >= *max_height) {
break;
}
{
if (!next_block || reorg) {
// break successfully when rescan has reached the tip, or
// previous block is no longer on the chain due to a reorg
break;
}
// increment block and verification progress
block_hash = next_block_hash;
++block_height;
progress_current = chain().guessVerificationProgress(block_hash);
// handle updated tip hash
const BlockHash prev_tip_hash = tip_hash;
tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash());
if (!max_height && prev_tip_hash != tip_hash) {
// in case the tip has changed, update progress max
progress_end = chain().guessVerificationProgress(tip_hash);
}
}
}
// Hide progress dialog in GUI.
ShowProgress(
strprintf("%s " + _("Rescanning...").translated, GetDisplayName()),
100);
if (block_height && fAbortRescan) {
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n",
block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else if (block_height && chain().shutdownRequested()) {
WalletLogPrintf(
"Rescan interrupted by shutdown request at block %d. Progress=%f\n",
block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else {
WalletLogPrintf("Rescan completed in %15dms\n",
GetTimeMillis() - start_time);
}
return result;
}
void CWallet::ReacceptWalletTransactions() {
// If transactions aren't being broadcasted, don't let them into local
// mempool either.
if (!fBroadcastTransactions) {
return;
}
std::map<int64_t, CWalletTx *> mapSorted;
// Sort pending wallet transactions based on their initial wallet insertion
// order.
for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
const TxId &wtxid = item.first;
CWalletTx &wtx = item.second;
assert(wtx.GetId() == wtxid);
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
}
// Try to add wallet transactions to memory pool.
for (const std::pair<const int64_t, CWalletTx *> &item : mapSorted) {
CWalletTx &wtx = *(item.second);
std::string unused_err_string;
wtx.SubmitMemoryPoolAndRelay(unused_err_string, false);
}
}
bool CWalletTx::SubmitMemoryPoolAndRelay(std::string &err_string, bool relay) {
// Can't relay if wallet is not broadcasting
if (!pwallet->GetBroadcastTransactions()) {
return false;
}
// Don't relay abandoned transactions
if (isAbandoned()) {
return false;
}
// Don't try to submit coinbase transactions. These would fail anyway but
// would cause log spam.
if (IsCoinBase()) {
return false;
}
// Don't try to submit conflicted or confirmed transactions.
if (GetDepthInMainChain() != 0) {
return false;
}
// Submit transaction to mempool for relay
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n",
GetId().ToString());
// We must set fInMempool here - while it will be re-set to true by the
// entered-mempool callback, if we did not there would be a race where a
// user could call sendmoney in a loop and hit spurious out of funds errors
// because we think that this newly generated transaction's change is
// unavailable as we're not yet aware that it is in the mempool.
//
// Irrespective of the failure reason, un-marking fInMempool
// out-of-order is incorrect - it should be unmarked when
// TransactionRemovedFromMempool fires.
bool ret = pwallet->chain().broadcastTransaction(
GetConfig(), tx, pwallet->m_default_max_tx_fee, relay, err_string);
fInMempool |= ret;
return ret;
}
std::set<TxId> CWalletTx::GetConflicts() const {
std::set<TxId> result;
if (pwallet != nullptr) {
const TxId &txid = GetId();
result = pwallet->GetConflicts(txid);
result.erase(txid);
}
return result;
}
Amount CWalletTx::GetCachableAmount(AmountType type, const isminefilter &filter,
bool recalculate) const {
auto &amount = m_amounts[type];
if (recalculate || !amount.m_cached[filter]) {
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter)
: pwallet->GetCredit(*tx, filter));
m_is_cache_empty = false;
}
return amount.m_value[filter];
}
Amount CWalletTx::GetDebit(const isminefilter &filter) const {
if (tx->vin.empty()) {
return Amount::zero();
}
Amount debit = Amount::zero();
if (filter & ISMINE_SPENDABLE) {
debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE);
}
if (filter & ISMINE_WATCH_ONLY) {
debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY);
}
return debit;
}
Amount CWalletTx::GetCredit(const isminefilter &filter) const {
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsImmatureCoinBase()) {
return Amount::zero();
}
Amount credit = Amount::zero();
if (filter & ISMINE_SPENDABLE) {
// GetBalance can assume transactions in mapWallet won't change.
credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE);
}
if (filter & ISMINE_WATCH_ONLY) {
credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY);
}
return credit;
}
Amount CWalletTx::GetImmatureCredit(bool fUseCache) const {
if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
}
return Amount::zero();
}
Amount CWalletTx::GetAvailableCredit(bool fUseCache,
const isminefilter &filter) const {
if (pwallet == nullptr) {
return Amount::zero();
}
// Avoid caching ismine for NO or ALL cases (could remove this check and
// simplify in the future).
bool allow_cache =
(filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsImmatureCoinBase()) {
return Amount::zero();
}
if (fUseCache && allow_cache &&
m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
return m_amounts[AVAILABLE_CREDIT].m_value[filter];
}
bool allow_used_addresses =
(filter & ISMINE_USED) ||
!pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
Amount nCredit = Amount::zero();
const TxId &txid = GetId();
for (uint32_t i = 0; i < tx->vout.size(); i++) {
if (!pwallet->IsSpent(COutPoint(txid, i)) &&
(allow_used_addresses || !pwallet->IsSpentKey(txid, i))) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, filter);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(std::string(__func__) +
" : value out of range");
}
}
}
if (allow_cache) {
m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit);
m_is_cache_empty = false;
}
return nCredit;
}
Amount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const {
if (IsImmatureCoinBase() && IsInMainChain()) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY,
!fUseCache);
}
return Amount::zero();
}
Amount CWalletTx::GetChange() const {
if (fChangeCached) {
return nChangeCached;
}
nChangeCached = pwallet->GetChange(*tx);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::InMempool() const {
return fInMempool;
}
bool CWalletTx::IsTrusted() const {
std::set<TxId> trusted_parents;
LOCK(pwallet->cs_wallet);
return pwallet->IsTrusted(*this, trusted_parents);
}
bool CWallet::IsTrusted(const CWalletTx &wtx,
std::set<TxId> &trusted_parents) const {
AssertLockHeld(cs_wallet);
// Quick answer in most cases
TxValidationState state;
if (!chain().contextualCheckTransactionForCurrentBlock(*wtx.tx, state)) {
return false;
}
int nDepth = wtx.GetDepthInMainChain();
if (nDepth >= 1) {
return true;
}
if (nDepth < 0) {
return false;
}
// using wtx's cached debit
if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) {
return false;
}
// Don't trust unconfirmed transactions from us unless they are in the
// mempool.
if (!wtx.InMempool()) {
return false;
}
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn &txin : wtx.tx->vin) {
// Transactions not sent by us: not trusted
const CWalletTx *parent = GetWalletTx(txin.prevout.GetTxId());
if (parent == nullptr) {
return false;
}
const CTxOut &parentOut = parent->tx->vout[txin.prevout.GetN()];
// Check that this specific input being spent is trusted
if (IsMine(parentOut) != ISMINE_SPENDABLE) {
return false;
}
// If we've already trusted this parent, continue
if (trusted_parents.count(parent->GetId())) {
continue;
}
// Recurse to check that the parent is also trusted
if (!IsTrusted(*parent, trusted_parents)) {
return false;
}
trusted_parents.insert(parent->GetId());
}
return true;
}
bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const {
CMutableTransaction tx1{*this->tx};
CMutableTransaction tx2{*_tx.tx};
for (auto &txin : tx1.vin) {
txin.scriptSig = CScript();
}
for (auto &txin : tx2.vin) {
txin.scriptSig = CScript();
}
return CTransaction(tx1) == CTransaction(tx2);
}
// Rebroadcast transactions from the wallet. We do this on a random timer
// to slightly obfuscate which transactions come from our wallet.
//
// Ideally, we'd only resend transactions that we think should have been
// mined in the most recent block. Any transaction that wasn't in the top
// blockweight of transactions in the mempool shouldn't have been mined,
// and so is probably just sitting in the mempool waiting to be confirmed.
// Rebroadcasting does nothing to speed up confirmation and only damages
// privacy.
void CWallet::ResendWalletTransactions() {
// During reindex, importing and IBD, old wallet transactions become
// unconfirmed. Don't resend them as that would spam other nodes.
if (!chain().isReadyToBroadcast()) {
return;
}
// Do this infrequently and randomly to avoid giving away that these are our
// transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) {
return;
}
bool fFirst = (nNextResend == 0);
// resend 12-36 hours from now, ~1 day on average.
nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60);
if (fFirst) {
return;
}
int submitted_tx_count = 0;
{ // cs_wallet scope
LOCK(cs_wallet);
// Relay transactions
for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
CWalletTx &wtx = item.second;
// Attempt to rebroadcast all txes more than 5 minutes older than
// the last block. SubmitMemoryPoolAndRelay() will not rebroadcast
// any confirmed or conflicting txs.
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) {
continue;
}
std::string unused_err_string;
if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) {
++submitted_tx_count;
}
}
} // cs_wallet
if (submitted_tx_count > 0) {
WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__,
submitted_tx_count);
}
}
/** @} */ // end of mapWallet
void MaybeResendWalletTxs() {
for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) {
pwallet->ResendWalletTransactions();
}
}
/**
* @defgroup Actions
*
* @{
*/
CWallet::Balance CWallet::GetBalance(const int min_depth,
bool avoid_reuse) const {
Balance ret;
isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
LOCK(cs_wallet);
std::set<TxId> trusted_parents;
for (const auto &entry : mapWallet) {
const CWalletTx &wtx = entry.second;
const bool is_trusted{IsTrusted(wtx, trusted_parents)};
const int tx_depth{wtx.GetDepthInMainChain()};
const Amount tx_credit_mine{wtx.GetAvailableCredit(
/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
const Amount tx_credit_watchonly{wtx.GetAvailableCredit(
/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly;
}
if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
ret.m_mine_untrusted_pending += tx_credit_mine;
ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
}
ret.m_mine_immature += wtx.GetImmatureCredit();
ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
}
return ret;
}
Amount CWallet::GetAvailableBalance(const CCoinControl *coinControl) const {
LOCK(cs_wallet);
Amount balance = Amount::zero();
std::vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
for (const COutput &out : vCoins) {
if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue;
}
}
return balance;
}
void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe,
const CCoinControl *coinControl,
const Amount nMinimumAmount,
const Amount nMaximumAmount,
const Amount nMinimumSumAmount,
const uint64_t nMaximumCount) const {
AssertLockHeld(cs_wallet);
vCoins.clear();
Amount nTotal = Amount::zero();
// Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we
// always allow), or we default to avoiding, and only in the case where a
// coin control object is provided, and has the avoid address reuse flag set
// to false, do we allow already used addresses
bool allow_used_addresses =
!IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) ||
(coinControl && !coinControl->m_avoid_address_reuse);
const int min_depth = {coinControl ? coinControl->m_min_depth
: DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth
: DEFAULT_MAX_DEPTH};
std::set<TxId> trusted_parents;
for (const auto &entry : mapWallet) {
const TxId &wtxid = entry.first;
const CWalletTx &wtx = entry.second;
TxValidationState state;
if (!chain().contextualCheckTransactionForCurrentBlock(*wtx.tx,
state)) {
continue;
}
if (wtx.IsImmatureCoinBase()) {
continue;
}
int nDepth = wtx.GetDepthInMainChain();
if (nDepth < 0) {
continue;
}
// We should not consider coins which aren't at least in our mempool.
// It's possible for these to be conflicted via ancestors which we may
// never be able to detect.
if (nDepth == 0 && !wtx.InMempool()) {
continue;
}
bool safeTx = IsTrusted(wtx, trusted_parents);
// Bitcoin-ABC: Removed check that prevents consideration of coins from
// transactions that are replacing other transactions. This check based
// on wtx.mapValue.count("replaces_txid") which was not being set
// anywhere.
// Similarly, we should not consider coins from transactions that have
// been replaced. In the example above, we would want to prevent
// creation of a transaction A' spending an output of A, because if
// transaction B were initially confirmed, conflicting with A and A', we
// wouldn't want to the user to create a transaction D intending to
// replace A', but potentially resulting in a scenario where A, A', and
// D could all be accepted (instead of just B and D, or just A and A'
// like the user would want).
// Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set
// in the wallet code.
if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) {
safeTx = false;
}
if (fOnlySafe && !safeTx) {
continue;
}
if (nDepth < min_depth || nDepth > max_depth) {
continue;
}
for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) {
// Only consider selected coins if add_inputs is false
if (coinControl && !coinControl->m_add_inputs &&
!coinControl->IsSelected(COutPoint(entry.first, i))) {
continue;
}
if (wtx.tx->vout[i].nValue < nMinimumAmount ||
wtx.tx->vout[i].nValue > nMaximumAmount) {
continue;
}
const COutPoint outpoint(wtxid, i);
if (coinControl && coinControl->HasSelected() &&
!coinControl->fAllowOtherInputs &&
!coinControl->IsSelected(outpoint)) {
continue;
}
if (IsLockedCoin(outpoint)) {
continue;
}
if (IsSpent(outpoint)) {
continue;
}
isminetype mine = IsMine(wtx.tx->vout[i]);
if (mine == ISMINE_NO) {
continue;
}
if (!allow_used_addresses && IsSpentKey(wtxid, i)) {
continue;
}
std::unique_ptr<SigningProvider> provider =
GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
bool solvable =
provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey)
: false;
bool spendable =
((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
(((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) &&
(coinControl && coinControl->fAllowWatchOnly && solvable));
vCoins.push_back(
COutput(&wtx, i, nDepth, spendable, solvable, safeTx,
(coinControl && coinControl->fAllowWatchOnly)));
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
nTotal += wtx.tx->vout[i].nValue;
if (nTotal >= nMinimumSumAmount) {
return;
}
}
// Checks the maximum number of UTXO's.
if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
return;
}
}
}
}
std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const {
AssertLockHeld(cs_wallet);
std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins;
AvailableCoins(availableCoins);
for (const auto &coin : availableCoins) {
CTxDestination address;
if ((coin.fSpendable ||
(IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
coin.fSolvable)) &&
ExtractDestination(
FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey,
address)) {
result[address].emplace_back(std::move(coin));
}
}
std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins);
// Include watch-only for LegacyScriptPubKeyMan wallets without private keys
const bool include_watch_only =
GetLegacyScriptPubKeyMan() &&
IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
const isminetype is_mine_filter =
include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
for (const auto &output : lockedCoins) {
auto it = mapWallet.find(output.GetTxId());
if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain();
if (depth >= 0 && output.GetN() < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.GetN()]) == is_mine_filter) {
CTxDestination address;
if (ExtractDestination(
FindNonChangeParentOutput(*it->second.tx, output.GetN())
.scriptPubKey,
address)) {
result[address].emplace_back(
&it->second, output.GetN(), depth, true /* spendable */,
true /* solvable */, false /* safe */);
}
}
}
}
return result;
}
const CTxOut &CWallet::FindNonChangeParentOutput(const CTransaction &tx,
int output) const {
AssertLockHeld(cs_wallet);
const CTransaction *ptx = &tx;
int n = output;
while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
const COutPoint &prevout = ptx->vin[0].prevout;
auto it = mapWallet.find(prevout.GetTxId());
if (it == mapWallet.end() ||
it->second.tx->vout.size() <= prevout.GetN() ||
!IsMine(it->second.tx->vout[prevout.GetN()])) {
break;
}
ptx = it->second.tx.get();
n = prevout.GetN();
}
return ptx->vout[n];
}
bool CWallet::SelectCoinsMinConf(
const Amount nTargetValue, const CoinEligibilityFilter &eligibility_filter,
std::vector<OutputGroup> groups, std::set<CInputCoin> &setCoinsRet,
Amount &nValueRet, const CoinSelectionParams &coin_selection_params,
bool &bnb_used) const {
setCoinsRet.clear();
nValueRet = Amount::zero();
std::vector<OutputGroup> utxo_pool;
if (coin_selection_params.use_bnb) {
// Get long term estimate
CCoinControl temp;
temp.m_confirm_target = 1008;
CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp);
// Calculate cost of change
Amount cost_of_change = chain().relayDustFee().GetFee(
coin_selection_params.change_spend_size) +
coin_selection_params.effective_fee.GetFee(
coin_selection_params.change_output_size);
// Filter by the min conf specs and add to utxo_pool and calculate
// effective value
for (OutputGroup &group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) {
continue;
}
if (coin_selection_params.m_subtract_fee_outputs) {
// Set the effective feerate to 0 as we don't want to use the
// effective value since the fees will be deducted from the
// output
group.SetFees(CFeeRate(Amount::zero()) /* effective_feerate */,
long_term_feerate);
} else {
group.SetFees(coin_selection_params.effective_fee,
long_term_feerate);
}
OutputGroup pos_group = group.GetPositiveOnlyGroup();
if (pos_group.effective_value > Amount::zero()) {
utxo_pool.push_back(pos_group);
}
}
// Calculate the fees for things that aren't inputs
Amount not_input_fees = coin_selection_params.effective_fee.GetFee(
coin_selection_params.tx_noinputs_size);
bnb_used = true;
return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change,
setCoinsRet, nValueRet, not_input_fees);
} else {
// Filter by the min conf specs and add to utxo_pool
for (const OutputGroup &group : groups) {
if (!group.EligibleForSpending(eligibility_filter)) {
continue;
}
utxo_pool.push_back(group);
}
bnb_used = false;
return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
}
}
bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins,
const Amount nTargetValue,
std::set<CInputCoin> &setCoinsRet, Amount &nValueRet,
const CCoinControl &coin_control,
CoinSelectionParams &coin_selection_params,
bool &bnb_used) const {
std::vector<COutput> vCoins(vAvailableCoins);
Amount value_to_select = nTargetValue;
// Default to bnb was not used. If we use it, we set it later
bnb_used = false;
// coin control -> return all selected outputs (we want all selected to go
// into the transaction for sure)
if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) {
for (const COutput &out : vCoins) {
if (!out.fSpendable) {
continue;
}
nValueRet += out.tx->tx->vout[out.i].nValue;
setCoinsRet.insert(out.GetInputCoin());
}
return (nValueRet >= nTargetValue);
}
// Calculate value from preset inputs and store them.
std::set<CInputCoin> setPresetCoins;
Amount nValueFromPresetInputs = Amount::zero();
std::vector<COutPoint> vPresetInputs;
coin_control.ListSelected(vPresetInputs);
for (const COutPoint &outpoint : vPresetInputs) {
std::map<TxId, CWalletTx>::const_iterator it =
mapWallet.find(outpoint.GetTxId());
if (it != mapWallet.end()) {
const CWalletTx &wtx = it->second;
// Clearly invalid input, fail
if (wtx.tx->vout.size() <= outpoint.GetN()) {
return false;
}
// Just to calculate the marginal byte size
CInputCoin coin(wtx.tx, outpoint.GetN(),
wtx.GetSpendSize(outpoint.GetN(), false));
nValueFromPresetInputs += coin.txout.nValue;
if (coin.m_input_bytes <= 0) {
// Not solvable, can't estimate size for fee
return false;
}
coin.effective_value =
coin.txout.nValue -
coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
if (coin_selection_params.use_bnb) {
value_to_select -= coin.effective_value;
} else {
value_to_select -= coin.txout.nValue;
}
setPresetCoins.insert(coin);
} else {
return false; // TODO: Allow non-wallet inputs
}
}
// Remove preset inputs from vCoins
for (std::vector<COutput>::iterator it = vCoins.begin();
it != vCoins.end() && coin_control.HasSelected();) {
if (setPresetCoins.count(it->GetInputCoin())) {
it = vCoins.erase(it);
} else {
++it;
}
}
size_t max_ancestors{0};
size_t max_descendants{0};
chain().getPackageLimits(max_ancestors, max_descendants);
bool fRejectLongChains = gArgs.GetBoolArg(
"-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
// form groups from remaining coins; note that preset coins will not
// automatically have their associated (same address) coins included
if (coin_control.m_avoid_partial_spends &&
vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
// Cases where we have 11+ outputs all pointing to the same destination
// may result in privacy leaks as they will potentially be
// deterministically sorted. We solve that by explicitly shuffling the
// outputs before processing
Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext());
}
std::vector<OutputGroup> groups = GroupOutputs(
vCoins, !coin_control.m_avoid_partial_spends, max_ancestors);
bool res =
value_to_select <= Amount::zero() ||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0),
groups, setCoinsRet, nValueRet,
coin_selection_params, bnb_used) ||
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0),
groups, setCoinsRet, nValueRet,
coin_selection_params, bnb_used) ||
(m_spend_zero_conf_change &&
SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2),
groups, setCoinsRet, nValueRet,
coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change &&
SelectCoinsMinConf(
value_to_select,
CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors / 3),
std::min((size_t)4, max_descendants / 3)),
groups, setCoinsRet, nValueRet, coin_selection_params,
bnb_used)) ||
(m_spend_zero_conf_change &&
SelectCoinsMinConf(value_to_select,
CoinEligibilityFilter(0, 1, max_ancestors / 2,
max_descendants / 2),
groups, setCoinsRet, nValueRet,
coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change &&
SelectCoinsMinConf(value_to_select,
CoinEligibilityFilter(0, 1, max_ancestors - 1,
max_descendants - 1),
groups, setCoinsRet, nValueRet,
coin_selection_params, bnb_used)) ||
(m_spend_zero_conf_change && !fRejectLongChains &&
SelectCoinsMinConf(
value_to_select,
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()),
groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used));
// Because SelectCoinsMinConf clears the setCoinsRet, we now add the
// possible inputs to the coinset.
util::insert(setCoinsRet, setPresetCoins);
// Add preset inputs to the total value selected.
nValueRet += nValueFromPresetInputs;
return res;
}
bool CWallet::SignTransaction(CMutableTransaction &tx) const {
AssertLockHeld(cs_wallet);
// Build coins map
std::map<COutPoint, Coin> coins;
for (auto &input : tx.vin) {
auto mi = mapWallet.find(input.prevout.GetTxId());
if (mi == mapWallet.end() ||
input.prevout.GetN() >= mi->second.tx->vout.size()) {
return false;
}
const CWalletTx &wtx = mi->second;
coins[input.prevout] =
Coin(wtx.tx->vout[input.prevout.GetN()], wtx.m_confirm.block_height,
wtx.IsCoinBase());
}
std::map<int, std::string> input_errors;
return SignTransaction(tx, coins, SigHashType().withForkId(), input_errors);
}
bool CWallet::SignTransaction(CMutableTransaction &tx,
const std::map<COutPoint, Coin> &coins,
SigHashType sighash,
std::map<int, std::string> &input_errors) const {
// Try to sign with all ScriptPubKeyMans
for (ScriptPubKeyMan *spk_man : GetAllScriptPubKeyMans()) {
// spk_man->SignTransaction will return true if the transaction is
// complete, so we can exit early and return true if that happens
if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
return true;
}
}
// At this point, one input was not fully signed otherwise we would have
// exited already
// When there are no available providers for the remaining inputs, use the
// legacy provider so we can get proper error messages.
auto legacy_spk_man = GetLegacyScriptPubKeyMan();
if (legacy_spk_man &&
legacy_spk_man->SignTransaction(tx, coins, sighash, input_errors)) {
return true;
}
return false;
}
TransactionError CWallet::FillPSBT(PartiallySignedTransaction &psbtx,
bool &complete, SigHashType sighash_type,
bool sign, bool bip32derivs) const {
LOCK(cs_wallet);
// Get all of the previous transactions
for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) {
const CTxIn &txin = psbtx.tx->vin[i];
PSBTInput &input = psbtx.inputs.at(i);
if (PSBTInputSigned(input)) {
continue;
}
// If we have no utxo, grab it from the wallet.
if (input.utxo.IsNull()) {
const TxId &txid = txin.prevout.GetTxId();
const auto it = mapWallet.find(txid);
if (it != mapWallet.end()) {
const CWalletTx &wtx = it->second;
CTxOut utxo = wtx.tx->vout[txin.prevout.GetN()];
// Update UTXOs from the wallet.
input.utxo = utxo;
}
}
}
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan *spk_man : GetAllScriptPubKeyMans()) {
TransactionError res =
spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs);
if (res != TransactionError::OK) {
return res;
}
}
// Complete if every input is now signed
complete = true;
for (const auto &input : psbtx.inputs) {
complete &= PSBTInputSigned(input);
}
return TransactionError::OK;
}
SigningResult CWallet::SignMessage(const std::string &message,
const PKHash &pkhash,
std::string &str_sig) const {
SignatureData sigdata;
CScript script_pub_key = GetScriptForDestination(pkhash);
for (const auto &spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) {
return spk_man_pair.second->SignMessage(message, pkhash, str_sig);
}
}
return SigningResult::PRIVATE_KEY_NOT_AVAILABLE;
}
bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet,
int &nChangePosInOut, bilingual_str &error,
bool lockUnspents,
const std::set<int> &setSubtractFeeFromOutputs,
CCoinControl coinControl) {
std::vector<CRecipient> vecSend;
// Turn the txout set into a CRecipient vector.
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
const CTxOut &txOut = tx.vout[idx];
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue,
setSubtractFeeFromOutputs.count(idx) == 1};
vecSend.push_back(recipient);
}
coinControl.fAllowOtherInputs = true;
for (const CTxIn &txin : tx.vin) {
coinControl.Select(txin.prevout);
}
// Acquire the locks to prevent races to the new locked unspents between the
// CreateTransaction call and LockCoin calls (when lockUnspents is true).
LOCK(cs_wallet);
CTransactionRef tx_new;
if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error,
coinControl, false)) {
return false;
}
if (nChangePosInOut != -1) {
tx.vout.insert(tx.vout.begin() + nChangePosInOut,
tx_new->vout[nChangePosInOut]);
}
// Copy output sizes from new transaction; they may have had the fee
// subtracted from them.
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
tx.vout[idx].nValue = tx_new->vout[idx].nValue;
}
// Add new txins (keeping original txin scriptSig/order)
for (const CTxIn &txin : tx_new->vin) {
if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
}
if (lockUnspents) {
LockCoin(txin.prevout);
}
}
return true;
}
static bool IsCurrentForAntiFeeSniping(interfaces::Chain &chain,
const BlockHash &block_hash) {
if (chain.isInitialBlockDownload()) {
return false;
}
// in seconds
constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60;
int64_t block_time;
CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time)));
if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) {
return false;
}
return true;
}
/**
* Return a height-based locktime for new transactions (uses the height of the
* current chain tip unless we are not synced with the current chain
*/
static uint32_t GetLocktimeForNewTransaction(interfaces::Chain &chain,
const BlockHash &block_hash,
int block_height) {
uint32_t locktime;
// Discourage fee sniping.
//
// For a large miner the value of the transactions in the best block and
// the mempool can exceed the cost of deliberately attempting to mine two
// blocks to orphan the current best block. By setting nLockTime such that
// only the next block can include the transaction, we discourage this
// practice as the height restricted and limited blocksize gives miners
// considering fee sniping fewer options for pulling off this attack.
//
// A simple way to think about this is from the wallet's point of view we
// always want the blockchain to move forward. By setting nLockTime this
// way we're basically making the statement that we only want this
// transaction to appear in the next block; we don't want to potentially
// encourage reorgs by allowing transactions to appear at lower heights
// than the next block in forks of the best chain.
//
// Of course, the subsidy is high enough, and transaction volume low
// enough, that fee sniping isn't a problem yet, but by implementing a fix
// now we ensure code won't be written that makes assumptions about
// nLockTime that preclude a fix later.
if (IsCurrentForAntiFeeSniping(chain, block_hash)) {
locktime = block_height;
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations,
// have better privacy.
if (GetRandInt(10) == 0) {
locktime = std::max(0, int(locktime) - GetRandInt(100));
}
} else {
// If our chain is lagging behind, we can't discourage fee sniping nor
// help the privacy of high-latency transactions. To avoid leaking a
// potentially unique "nLockTime fingerprint", set nLockTime to a
// constant.
locktime = 0;
}
assert(locktime < LOCKTIME_THRESHOLD);
return locktime;
}
OutputType
CWallet::TransactionChangeType(const std::optional<OutputType> &change_type,
const std::vector<CRecipient> &vecSend) {
// If -changetype is specified, always use that change type.
if (change_type) {
return *change_type;
}
// if m_default_address_type is legacy, use legacy address as change.
if (m_default_address_type == OutputType::LEGACY) {
return OutputType::LEGACY;
}
// else use m_default_address_type for change
return m_default_address_type;
}
bool CWallet::CreateTransactionInternal(const std::vector<CRecipient> &vecSend,
CTransactionRef &tx, Amount &nFeeRet,
int &nChangePosInOut,
bilingual_str &error,
const CCoinControl &coin_control,
bool sign) {
Amount nValue = Amount::zero();
const OutputType change_type = TransactionChangeType(
coin_control.m_change_type ? *coin_control.m_change_type
: m_default_change_type,
vecSend);
ReserveDestination reservedest(this, change_type);
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
for (const auto &recipient : vecSend) {
if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) {
error = _("Transaction amounts must not be negative");
return false;
}
nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount) {
nSubtractFeeFromAmount++;
}
}
if (vecSend.empty()) {
error = _("Transaction must have at least one recipient");
return false;
}
CMutableTransaction txNew;
{
std::set<CInputCoin> setCoins;
LOCK(cs_wallet);
txNew.nLockTime = GetLocktimeForNewTransaction(
chain(), GetLastBlockHash(), GetLastBlockHeight());
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coin_control);
// Parameters for coin selection, init with dummy
CoinSelectionParams coin_selection_params;
// Create change script that will be used if we need change
// TODO: pass in scriptChange instead of reservedest so
// change transaction isn't always pay-to-bitcoin-address
CScript scriptChange;
// coin control: send change to custom address
if (!boost::get<CNoDestination>(&coin_control.destChange)) {
scriptChange = GetScriptForDestination(coin_control.destChange);
// no coin control: send change to newly generated address
} else {
// Note: We use a new key here to keep it from being obvious
// which side is the change.
// The drawback is that by not reusing a previous key, the
// change may be lost if a backup is restored, if the backup
// doesn't have the new private key for the change. If we
// reused the old key, it would be possible to add code to look
// for and rediscover unknown transactions that were written
// with keys of ours to recover post-backup change.
// Reserve a new key pair from key pool. If it fails, provide a
// dummy destination in case we don't need change.
CTxDestination dest;
if (!reservedest.GetReservedDestination(dest, true)) {
error = _("Transaction needs a change address, but we can't "
"generate it. Please call keypoolrefill first.");
}
scriptChange = GetScriptForDestination(dest);
// A valid destination implies a change script (and
// vice-versa). An empty change script will abort later, if the
// change keypool ran out, but change is required.
CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty());
}
CTxOut change_prototype_txout(Amount::zero(), scriptChange);
coin_selection_params.change_output_size =
GetSerializeSize(change_prototype_txout);
// Get the fee rate to use effective values in coin selection
CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control);
// Do not, ever, assume that it's fine to change the fee rate if the
// user has explicitly provided one
if (coin_control.m_feerate &&
nFeeRateNeeded > *coin_control.m_feerate) {
error = strprintf(_("Fee rate (%s) is lower than the minimum fee "
"rate setting (%s)"),
coin_control.m_feerate->ToString(),
nFeeRateNeeded.ToString());
return false;
}
nFeeRet = Amount::zero();
bool pick_new_inputs = true;
Amount nValueIn = Amount::zero();
// BnB selector is the only selector used when this is true.
// That should only happen on the first pass through the loop.
coin_selection_params.use_bnb = true;
// If we are doing subtract fee from recipient, don't use effective
// values
coin_selection_params.m_subtract_fee_outputs =
nSubtractFeeFromAmount != 0;
// Start with no fee and loop until there is enough fee
while (true) {
nChangePosInOut = nChangePosRequest;
txNew.vin.clear();
txNew.vout.clear();
bool fFirst = true;
Amount nValueToSelect = nValue;
if (nSubtractFeeFromAmount == 0) {
nValueToSelect += nFeeRet;
}
// vouts to the payees
if (!coin_selection_params.m_subtract_fee_outputs) {
// Static size overhead + outputs vsize. 4 nVersion, 4
// nLocktime, 1 input count, 1 output count
coin_selection_params.tx_noinputs_size = 10;
}
// vouts to the payees
for (const auto &recipient : vecSend) {
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
if (recipient.fSubtractFeeFromAmount) {
assert(nSubtractFeeFromAmount != 0);
// Subtract fee equally from each selected recipient.
txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount);
// First receiver pays the remainder not divisible by output
// count.
if (fFirst) {
fFirst = false;
txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount);
}
}
// Include the fee cost for outputs. Note this is only used for
// BnB right now
if (!coin_selection_params.m_subtract_fee_outputs) {
coin_selection_params.tx_noinputs_size +=
::GetSerializeSize(txout, PROTOCOL_VERSION);
}
if (IsDust(txout, chain().relayDustFee())) {
if (recipient.fSubtractFeeFromAmount &&
nFeeRet > Amount::zero()) {
if (txout.nValue < Amount::zero()) {
error = _("The transaction amount is too small to "
"pay the fee");
} else {
error = _("The transaction amount is too small to "
"send after the fee has been deducted");
}
} else {
error = _("Transaction amount too small");
}
return false;
}
txNew.vout.push_back(txout);
}
// Choose coins to use
bool bnb_used = false;
if (pick_new_inputs) {
nValueIn = Amount::zero();
setCoins.clear();
int change_spend_size = CalculateMaximumSignedInputSize(
change_prototype_txout, this);
// If the wallet doesn't know how to sign change output, assume
// p2pkh as lower-bound to allow BnB to do it's thing
if (change_spend_size == -1) {
coin_selection_params.change_spend_size =
DUMMY_P2PKH_INPUT_SIZE;
} else {
coin_selection_params.change_spend_size =
size_t(change_spend_size);
}
coin_selection_params.effective_fee = nFeeRateNeeded;
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins,
nValueIn, coin_control, coin_selection_params,
bnb_used)) {
// If BnB was used, it was the first pass. No longer the
// first pass and continue loop with knapsack.
if (bnb_used) {
coin_selection_params.use_bnb = false;
continue;
} else {
error = _("Insufficient funds");
return false;
}
}
} else {
bnb_used = false;
}
const Amount nChange = nValueIn - nValueToSelect;
if (nChange > Amount::zero()) {
// Fill a vout to ourself.
CTxOut newTxOut(nChange, scriptChange);
// Never create dust outputs; if we would, just add the dust to
// the fee.
// The nChange when BnB is used is always going to go to fees.
if (IsDust(newTxOut, chain().relayDustFee()) || bnb_used) {
nChangePosInOut = -1;
nFeeRet += nChange;
} else {
if (nChangePosInOut == -1) {
// Insert change txn at random position:
nChangePosInOut = GetRandInt(txNew.vout.size() + 1);
} else if ((unsigned int)nChangePosInOut >
txNew.vout.size()) {
error = _("Change index out of range");
return false;
}
std::vector<CTxOut>::iterator position =
txNew.vout.begin() + nChangePosInOut;
txNew.vout.insert(position, newTxOut);
}
} else {
nChangePosInOut = -1;
}
// Dummy fill vin for maximum size estimation
//
for (const auto &coin : setCoins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript()));
}
CTransaction txNewConst(txNew);
int nBytes = CalculateMaximumSignedTxSize(
txNewConst, this, coin_control.fAllowWatchOnly);
if (nBytes < 0) {
error = _("Signing transaction failed");
return false;
}
Amount nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control);
if (nFeeRet >= nFeeNeeded) {
// Reduce fee to only the needed amount if possible. This
// prevents potential overpayment in fees if the coins selected
// to meet nFeeNeeded result in a transaction that requires less
// fee than the prior iteration.
// If we have no change and a big enough excess fee, then try to
// construct transaction again only without picking new inputs.
// We now know we only need the smaller fee (because of reduced
// tx size) and so we should add a change output. Only try this
// once.
if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 &&
pick_new_inputs) {
// Add 2 as a buffer in case increasing # of outputs changes
// compact size
unsigned int tx_size_with_change =
nBytes + coin_selection_params.change_output_size + 2;
Amount fee_needed_with_change =
GetMinimumFee(*this, tx_size_with_change, coin_control);
Amount minimum_value_for_change = GetDustThreshold(
change_prototype_txout, chain().relayDustFee());
if (nFeeRet >=
fee_needed_with_change + minimum_value_for_change) {
pick_new_inputs = false;
nFeeRet = fee_needed_with_change;
continue;
}
}
// If we have change output already, just increase it
if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 &&
nSubtractFeeFromAmount == 0) {
Amount extraFeePaid = nFeeRet - nFeeNeeded;
std::vector<CTxOut>::iterator change_position =
txNew.vout.begin() + nChangePosInOut;
change_position->nValue += extraFeePaid;
nFeeRet -= extraFeePaid;
}
// Done, enough fee included.
break;
} else if (!pick_new_inputs) {
// This shouldn't happen, we should have had enough excess fee
// to pay for the new output and still meet nFeeNeeded.
// Or we should have just subtracted fee from recipients and
// nFeeNeeded should not have changed.
error = _("Transaction fee and change calculation failed");
return false;
}
// Try to reduce change to include necessary fee.
if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
Amount additionalFeeNeeded = nFeeNeeded - nFeeRet;
std::vector<CTxOut>::iterator change_position =
txNew.vout.begin() + nChangePosInOut;
// Only reduce change if remaining amount is still a large
// enough output.
if (change_position->nValue >=
MIN_FINAL_CHANGE + additionalFeeNeeded) {
change_position->nValue -= additionalFeeNeeded;
nFeeRet += additionalFeeNeeded;
// Done, able to increase fee from change.
break;
}
}
// If subtracting fee from recipients, we now know what fee we
// need to subtract, we have no reason to reselect inputs.
if (nSubtractFeeFromAmount > 0) {
pick_new_inputs = false;
}
// Include more fee and try again.
nFeeRet = nFeeNeeded;
coin_selection_params.use_bnb = false;
continue;
}
// Give up if change keypool ran out and change is required
if (scriptChange.empty() && nChangePosInOut != -1) {
return false;
}
// Shuffle selected coins and fill in final vin
txNew.vin.clear();
std::vector<CInputCoin> selected_coins(setCoins.begin(),
setCoins.end());
Shuffle(selected_coins.begin(), selected_coins.end(),
FastRandomContext());
// Note how the sequence number is set to non-maxint so that
// the nLockTime set above actually works.
for (const auto &coin : selected_coins) {
txNew.vin.push_back(
CTxIn(coin.outpoint, CScript(),
std::numeric_limits<uint32_t>::max() - 1));
}
if (sign && !SignTransaction(txNew)) {
error = _("Signing transaction failed");
return false;
}
// Return the constructed transaction data.
tx = MakeTransactionRef(std::move(txNew));
// Limit size.
if (tx->GetTotalSize() > MAX_STANDARD_TX_SIZE) {
error = _("Transaction too large");
return false;
}
}
if (nFeeRet > m_default_max_tx_fee) {
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
return false;
}
if (gArgs.GetBoolArg("-walletrejectlongchains",
DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
if (!chain().checkChainLimits(tx)) {
error = _("Transaction has too long of a mempool chain");
return false;
}
}
// Before we return success, we assume any change key will be used to
// prevent accidental re-use.
reservedest.KeepDestination();
return true;
}
bool CWallet::CreateTransaction(const std::vector<CRecipient> &vecSend,
CTransactionRef &tx, Amount &nFeeRet,
int &nChangePosInOut, bilingual_str &error,
const CCoinControl &coin_control, bool sign) {
int nChangePosIn = nChangePosInOut;
CTransactionRef tx2 = tx;
bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut,
error, coin_control, sign);
// try with avoidpartialspends unless it's enabled already
if (res &&
nFeeRet >
Amount::zero() /* 0 means non-functional fee rate estimation */
&& m_max_aps_fee > (-1 * SATOSHI) &&
!coin_control.m_avoid_partial_spends) {
CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true;
Amount nFeeRet2;
int nChangePosInOut2 = nChangePosIn;
// fired and forgotten; if an error occurs, we discard the results
bilingual_str error2;
if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2,
error2, tmp_cc, sign)) {
// if fee of this alternative one is within the range of the max
// fee, we use this one
const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
WalletLogPrintf(
"Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet,
nFeeRet2, use_aps ? "grouped" : "non-grouped");
if (use_aps) {
tx = tx2;
nFeeRet = nFeeRet2;
nChangePosInOut = nChangePosInOut2;
}
}
}
return res;
}
void CWallet::CommitTransaction(
CTransactionRef tx, mapValue_t mapValue,
std::vector<std::pair<std::string, std::string>> orderForm) {
LOCK(cs_wallet);
WalletLogPrintfToBeContinued("CommitTransaction:\n%s", tx->ToString());
// Add tx to wallet, because if it has change it's also ours, otherwise just
// for transaction history.
AddToWallet(tx, {}, [&](CWalletTx &wtx, bool new_tx) {
CHECK_NONFATAL(wtx.mapValue.empty());
CHECK_NONFATAL(wtx.vOrderForm.empty());
wtx.mapValue = std::move(mapValue);
wtx.vOrderForm = std::move(orderForm);
wtx.fTimeReceivedIsTxTime = true;
wtx.fFromMe = true;
return true;
});
// Notify that old coins are spent.
for (const CTxIn &txin : tx->vin) {
CWalletTx &coin = mapWallet.at(txin.prevout.GetTxId());
coin.MarkDirty();
NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED);
}
// Get the inserted-CWalletTx from mapWallet so that the
// fInMempool flag is cached properly
CWalletTx &wtx = mapWallet.at(tx->GetId());
if (!fBroadcastTransactions) {
// Don't submit tx to the mempool
return;
}
std::string err_string;
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast "
"immediately, %s\n",
err_string);
// TODO: if we expect the failure to be long term or permanent, instead
// delete wtx from the wallet and return failure.
}
}
DBErrors CWallet::LoadWallet(bool &fFirstRunRet) {
LOCK(cs_wallet);
fFirstRunRet = false;
DBErrors nLoadWalletRet = WalletBatch(*database, "cr+").LoadWallet(this);
if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
if (database->Rewrite("\x04pool")) {
for (const auto &spk_man_pair : m_spk_managers) {
spk_man_pair.second->RewriteDB();
}
}
}
// This wallet is in its first run if there are no ScriptPubKeyMans and it
// isn't blank or no privkeys
fFirstRunRet = m_spk_managers.empty() &&
!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
if (fFirstRunRet) {
assert(m_external_spk_managers.empty());
assert(m_internal_spk_managers.empty());
}
if (nLoadWalletRet != DBErrors::LOAD_OK) {
return nLoadWalletRet;
}
return DBErrors::LOAD_OK;
}
DBErrors CWallet::ZapSelectTx(std::vector<TxId> &txIdsIn,
std::vector<TxId> &txIdsOut) {
AssertLockHeld(cs_wallet);
DBErrors nZapSelectTxRet =
WalletBatch(*database, "cr+").ZapSelectTx(txIdsIn, txIdsOut);
for (const TxId &txid : txIdsOut) {
const auto &it = mapWallet.find(txid);
wtxOrdered.erase(it->second.m_it_wtxOrdered);
for (const auto &txin : it->second.tx->vin) {
mapTxSpends.erase(txin.prevout);
}
mapWallet.erase(it);
NotifyTransactionChanged(this, txid, CT_DELETED);
}
if (nZapSelectTxRet == DBErrors::NEED_REWRITE) {
if (database->Rewrite("\x04pool")) {
for (const auto &spk_man_pair : m_spk_managers) {
spk_man_pair.second->RewriteDB();
}
}
}
if (nZapSelectTxRet != DBErrors::LOAD_OK) {
return nZapSelectTxRet;
}
MarkDirty();
return DBErrors::LOAD_OK;
}
bool CWallet::SetAddressBookWithDB(WalletBatch &batch,
const CTxDestination &address,
const std::string &strName,
const std::string &strPurpose) {
bool fUpdated = false;
bool is_mine;
{
LOCK(cs_wallet);
std::map<CTxDestination, CAddressBookData>::iterator mi =
m_address_book.find(address);
fUpdated = (mi != m_address_book.end() && !mi->second.IsChange());
m_address_book[address].SetLabel(strName);
// Update purpose only if requested.
if (!strPurpose.empty()) {
m_address_book[address].purpose = strPurpose;
}
is_mine = IsMine(address) != ISMINE_NO;
}
NotifyAddressBookChanged(this, address, strName, is_mine, strPurpose,
(fUpdated ? CT_UPDATED : CT_NEW));
if (!strPurpose.empty() && !batch.WritePurpose(address, strPurpose)) {
return false;
}
return batch.WriteName(address, strName);
}
bool CWallet::SetAddressBook(const CTxDestination &address,
const std::string &strName,
const std::string &strPurpose) {
WalletBatch batch(*database);
return SetAddressBookWithDB(batch, address, strName, strPurpose);
}
bool CWallet::DelAddressBook(const CTxDestination &address) {
bool is_mine;
WalletBatch batch(*database);
{
LOCK(cs_wallet);
// If we want to delete receiving addresses, we need to take care that
// DestData "used" (and possibly newer DestData) gets preserved (and the
// "deleted" address transformed into a change entry instead of actually
// being deleted)
// NOTE: This isn't a problem for sending addresses because they never
// have any DestData yet! When adding new DestData, it should be
// considered here whether to retain or delete it (or move it?).
if (IsMine(address)) {
WalletLogPrintf(
"%s called with IsMine address, NOT SUPPORTED. Please "
"report this bug! %s\n",
__func__, PACKAGE_BUGREPORT);
return false;
}
// Delete destdata tuples associated with address
for (const std::pair<const std::string, std::string> &item :
m_address_book[address].destdata) {
batch.EraseDestData(address, item.first);
}
m_address_book.erase(address);
is_mine = IsMine(address) != ISMINE_NO;
}
NotifyAddressBookChanged(this, address, "", is_mine, "", CT_DELETED);
batch.ErasePurpose(address);
return batch.EraseName(address);
}
size_t CWallet::KeypoolCountExternalKeys() const {
AssertLockHeld(cs_wallet);
unsigned int count = 0;
for (auto spk_man : GetActiveScriptPubKeyMans()) {
count += spk_man->KeypoolCountExternalKeys();
}
return count;
}
unsigned int CWallet::GetKeyPoolSize() const {
AssertLockHeld(cs_wallet);
unsigned int count = 0;
for (auto spk_man : GetActiveScriptPubKeyMans()) {
count += spk_man->GetKeyPoolSize();
}
return count;
}
bool CWallet::TopUpKeyPool(unsigned int kpSize) {
LOCK(cs_wallet);
bool res = true;
for (auto spk_man : GetActiveScriptPubKeyMans()) {
res &= spk_man->TopUp(kpSize);
}
return res;
}
bool CWallet::GetNewDestination(const OutputType type, const std::string label,
CTxDestination &dest, std::string &error) {
LOCK(cs_wallet);
error.clear();
bool result = false;
auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
if (spk_man) {
spk_man->TopUp();
result = spk_man->GetNewDestination(type, dest, error);
} else {
error = strprintf("Error: No %s addresses available.",
FormatOutputType(type));
}
if (result) {
SetAddressBook(dest, label, "receive");
}
return result;
}
bool CWallet::GetNewChangeDestination(const OutputType type,
CTxDestination &dest,
std::string &error) {
LOCK(cs_wallet);
error.clear();
ReserveDestination reservedest(this, type);
if (!reservedest.GetReservedDestination(dest, true)) {
error = _("Error: Keypool ran out, please call keypoolrefill first")
.translated;
return false;
}
reservedest.KeepDestination();
return true;
}
int64_t CWallet::GetOldestKeyPoolTime() const {
LOCK(cs_wallet);
int64_t oldestKey = std::numeric_limits<int64_t>::max();
for (const auto &spk_man_pair : m_spk_managers) {
oldestKey =
std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime());
}
return oldestKey;
}
void CWallet::MarkDestinationsDirty(
const std::set<CTxDestination> &destinations) {
for (auto &entry : mapWallet) {
CWalletTx &wtx = entry.second;
if (wtx.m_is_cache_empty) {
continue;
}
for (size_t i = 0; i < wtx.tx->vout.size(); i++) {
CTxDestination dst;
if (ExtractDestination(wtx.tx->vout[i].scriptPubKey, dst) &&
destinations.count(dst)) {
wtx.MarkDirty();
break;
}
}
}
}
std::map<CTxDestination, Amount> CWallet::GetAddressBalances() const {
std::map<CTxDestination, Amount> balances;
LOCK(cs_wallet);
std::set<TxId> trusted_parents;
for (const auto &walletEntry : mapWallet) {
const CWalletTx &wtx = walletEntry.second;
if (!IsTrusted(wtx, trusted_parents)) {
continue;
}
if (wtx.IsImmatureCoinBase()) {
continue;
}
int nDepth = wtx.GetDepthInMainChain();
if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) {
continue;
}
for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) {
CTxDestination addr;
if (!IsMine(wtx.tx->vout[i])) {
continue;
}
if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) {
continue;
}
Amount n = IsSpent(COutPoint(walletEntry.first, i))
? Amount::zero()
: wtx.tx->vout[i].nValue;
balances[addr] += n;
}
}
return balances;
}
std::set<std::set<CTxDestination>> CWallet::GetAddressGroupings() const {
AssertLockHeld(cs_wallet);
std::set<std::set<CTxDestination>> groupings;
std::set<CTxDestination> grouping;
for (const auto &walletEntry : mapWallet) {
const CWalletTx &wtx = walletEntry.second;
if (wtx.tx->vin.size() > 0) {
bool any_mine = false;
// Group all input addresses with each other.
for (const auto &txin : wtx.tx->vin) {
CTxDestination address;
// If this input isn't mine, ignore it.
if (!IsMine(txin)) {
continue;
}
if (!ExtractDestination(mapWallet.at(txin.prevout.GetTxId())
.tx->vout[txin.prevout.GetN()]
.scriptPubKey,
address)) {
continue;
}
grouping.insert(address);
any_mine = true;
}
// Group change with input addresses.
if (any_mine) {
for (const auto &txout : wtx.tx->vout) {
if (IsChange(txout)) {
CTxDestination txoutAddr;
if (!ExtractDestination(txout.scriptPubKey,
txoutAddr)) {
continue;
}
grouping.insert(txoutAddr);
}
}
}
if (grouping.size() > 0) {
groupings.insert(grouping);
grouping.clear();
}
}
// Group lone addrs by themselves.
for (const auto &txout : wtx.tx->vout) {
if (IsMine(txout)) {
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address)) {
continue;
}
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
}
// A set of pointers to groups of addresses.
std::set<std::set<CTxDestination> *> uniqueGroupings;
// Map addresses to the unique group containing it.
std::map<CTxDestination, std::set<CTxDestination> *> setmap;
for (std::set<CTxDestination> _grouping : groupings) {
// Make a set of all the groups hit by this new group.
std::set<std::set<CTxDestination> *> hits;
std::map<CTxDestination, std::set<CTxDestination> *>::iterator it;
for (const CTxDestination &address : _grouping) {
if ((it = setmap.find(address)) != setmap.end()) {
hits.insert((*it).second);
}
}
// Merge all hit groups into a new single group and delete old groups.
std::set<CTxDestination> *merged =
new std::set<CTxDestination>(_grouping);
for (std::set<CTxDestination> *hit : hits) {
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// Update setmap.
for (const CTxDestination &element : *merged) {
setmap[element] = merged;
}
}
std::set<std::set<CTxDestination>> ret;
for (const std::set<CTxDestination> *uniqueGrouping : uniqueGroupings) {
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
std::set<CTxDestination>
CWallet::GetLabelAddresses(const std::string &label) const {
LOCK(cs_wallet);
std::set<CTxDestination> result;
for (const std::pair<const CTxDestination, CAddressBookData> &item :
m_address_book) {
if (item.second.IsChange()) {
continue;
}
const CTxDestination &address = item.first;
const std::string &strName = item.second.GetLabel();
if (strName == label) {
result.insert(address);
}
}
return result;
}
bool ReserveDestination::GetReservedDestination(CTxDestination &dest,
bool internal) {
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
if (!m_spk_man) {
return false;
}
if (nIndex == -1) {
m_spk_man->TopUp();
CKeyPool keypool;
if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex,
keypool)) {
return false;
}
fInternal = keypool.fInternal;
}
dest = address;
return true;
}
void ReserveDestination::KeepDestination() {
if (nIndex != -1) {
m_spk_man->KeepDestination(nIndex, type);
}
nIndex = -1;
address = CNoDestination();
}
void ReserveDestination::ReturnDestination() {
if (nIndex != -1) {
m_spk_man->ReturnDestination(nIndex, fInternal, address);
}
nIndex = -1;
address = CNoDestination();
}
void CWallet::LockCoin(const COutPoint &output) {
AssertLockHeld(cs_wallet);
setLockedCoins.insert(output);
}
void CWallet::UnlockCoin(const COutPoint &output) {
AssertLockHeld(cs_wallet);
setLockedCoins.erase(output);
}
void CWallet::UnlockAllCoins() {
AssertLockHeld(cs_wallet);
setLockedCoins.clear();
}
bool CWallet::IsLockedCoin(const COutPoint &outpoint) const {
AssertLockHeld(cs_wallet);
return setLockedCoins.count(outpoint) > 0;
}
void CWallet::ListLockedCoins(std::vector<COutPoint> &vOutpts) const {
AssertLockHeld(cs_wallet);
for (COutPoint outpoint : setLockedCoins) {
vOutpts.push_back(outpoint);
}
}
/** @} */ // end of Actions
void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const {
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan();
assert(spk_man != nullptr);
LOCK(spk_man->cs_KeyStore);
// Get birth times for keys with metadata.
for (const auto &entry : spk_man->mapKeyMetadata) {
if (entry.second.nCreateTime) {
mapKeyBirth[entry.first] = entry.second.nCreateTime;
}
}
// map in which we'll infer heights of other keys
std::map<CKeyID, const CWalletTx::Confirmation *> mapKeyFirstBlock;
CWalletTx::Confirmation max_confirm;
// the tip can be reorganized; use a 144-block safety margin
max_confirm.block_height =
GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0;
CHECK_NONFATAL(chain().findAncestorByHeight(
GetLastBlockHash(), max_confirm.block_height,
FoundBlock().hash(max_confirm.hashBlock)));
for (const CKeyID &keyid : spk_man->GetKeys()) {
if (mapKeyBirth.count(keyid) == 0) {
mapKeyFirstBlock[keyid] = &max_confirm;
}
}
// If there are no such keys, we're done.
if (mapKeyFirstBlock.empty()) {
return;
}
// Find first block that affects those keys, if there are any left.
for (const auto &entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
if (wtx.m_confirm.status == CWalletTx::CONFIRMED) {
// ... which are already in a block
for (const CTxOut &txout : wtx.tx->vout) {
// Iterate over all their outputs...
for (const auto &keyid :
GetAffectedKeys(txout.scriptPubKey, *spk_man)) {
// ... and all their affected keys.
auto rit = mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() &&
wtx.m_confirm.block_height <
rit->second->block_height) {
rit->second = &wtx.m_confirm;
}
}
}
}
}
// Extract block timestamps for those keys.
for (const auto &entry : mapKeyFirstBlock) {
int64_t block_time;
CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock,
FoundBlock().time(block_time)));
// block times can be 2h off
mapKeyBirth[entry.first] = block_time - TIMESTAMP_WINDOW;
}
}
/**
* Compute smart timestamp for a transaction being added to the wallet.
*
* Logic:
* - If sending a transaction, assign its timestamp to the current time.
* - If receiving a transaction outside a block, assign its timestamp to the
* current time.
* - If receiving a block with a future timestamp, assign all its (not already
* known) transactions' timestamps to the current time.
* - If receiving a block with a past timestamp, before the most recent known
* transaction (that we care about), assign all its (not already known)
* transactions' timestamps to the same timestamp as that most-recent-known
* transaction.
* - If receiving a block with a past timestamp, but after the most recent known
* transaction, assign all its (not already known) transactions' timestamps to
* the block time.
*
* For more information see CWalletTx::nTimeSmart,
* https://bitcointalk.org/?topic=54527, or
* https://github.com/bitcoin/bitcoin/pull/1393.
*/
unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const {
unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) {
int64_t blocktime;
if (chain().findBlock(wtx.m_confirm.hashBlock,
FoundBlock().time(blocktime))) {
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
// Tolerate times up to the last timestamp in the wallet not more
// than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
const TxItems &txOrdered = wtxOrdered;
for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
CWalletTx *const pwtx = it->second;
if (pwtx == &wtx) {
continue;
}
int64_t nSmartTime;
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime) {
nSmartTime = pwtx->nTimeReceived;
}
if (nSmartTime <= latestTolerated) {
latestEntry = nSmartTime;
if (nSmartTime > latestNow) {
latestNow = nSmartTime;
}
break;
}
}
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else {
WalletLogPrintf("%s: found %s in block %s not in index\n", __func__,
wtx.GetId().ToString(),
wtx.m_confirm.hashBlock.ToString());
}
}
return nTimeSmart;
}
bool CWallet::AddDestData(WalletBatch &batch, const CTxDestination &dest,
const std::string &key, const std::string &value) {
if (boost::get<CNoDestination>(&dest)) {
return false;
}
m_address_book[dest].destdata.insert(std::make_pair(key, value));
return batch.WriteDestData(dest, key, value);
}
bool CWallet::EraseDestData(WalletBatch &batch, const CTxDestination &dest,
const std::string &key) {
if (!m_address_book[dest].destdata.erase(key)) {
return false;
}
return batch.EraseDestData(dest, key);
}
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key,
const std::string &value) {
m_address_book[dest].destdata.insert(std::make_pair(key, value));
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key,
std::string *value) const {
std::map<CTxDestination, CAddressBookData>::const_iterator i =
m_address_book.find(dest);
if (i != m_address_book.end()) {
CAddressBookData::StringMap::const_iterator j =
i->second.destdata.find(key);
if (j != i->second.destdata.end()) {
if (value) {
*value = j->second;
}
return true;
}
}
return false;
}
std::vector<std::string>
CWallet::GetDestValues(const std::string &prefix) const {
std::vector<std::string> values;
for (const auto &address : m_address_book) {
for (const auto &data : address.second.destdata) {
if (!data.first.compare(0, prefix.size(), prefix)) {
values.emplace_back(data.second);
}
}
}
return values;
}
std::unique_ptr<WalletDatabase>
MakeWalletDatabase(const std::string &name, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error_string) {
// Do some checking on wallet path. It should be either a:
//
// 1. Path where a directory can be created.
// 2. Path to an existing directory.
// 3. Path to a symlink to a directory.
// 4. For backwards compatibility, the name of a data file in -walletdir.
const fs::path &wallet_path = fs::absolute(name, GetWalletDir());
fs::file_type path_type = fs::symlink_status(wallet_path).type();
if (!(path_type == fs::file_not_found || path_type == fs::directory_file ||
(path_type == fs::symlink_file && fs::is_directory(wallet_path)) ||
(path_type == fs::regular_file &&
fs::path(name).filename() == name))) {
error_string = Untranslated(
strprintf("Invalid -wallet path '%s'. -wallet path should point to "
"a directory where wallet.dat and "
"database/log.?????????? files can be stored, a location "
"where such a directory could be created, "
"or (for backwards compatibility) the name of an "
"existing data file in -walletdir (%s)",
name, GetWalletDir()));
status = DatabaseStatus::FAILED_BAD_PATH;
return nullptr;
}
return MakeDatabase(wallet_path, options, status, error_string);
}
-std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(
- interfaces::Chain &chain, const std::string &name, bilingual_str &error,
- std::vector<bilingual_str> &warnings, uint64_t wallet_creation_flags) {
- fs::path path = fs::absolute(name, GetWalletDir());
- const std::string walletFile = WalletDataFilePath(path).string();
+std::shared_ptr<CWallet>
+CWallet::Create(interfaces::Chain &chain, const std::string &name,
+ std::unique_ptr<WalletDatabase> database,
+ uint64_t wallet_creation_flags, bilingual_str &error,
+ std::vector<bilingual_str> &warnings) {
+ const std::string &walletFile = database->Filename();
chain.initMessage(_("Loading wallet...").translated);
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
// TODO: Can't use std::make_shared because we need a custom deleter but
// should be possible to use std::allocate_shared.
std::shared_ptr<CWallet> walletInstance(
- new CWallet(&chain, name, CreateWalletDatabase(path)), ReleaseWallet);
+ new CWallet(&chain, name, std::move(database)), ReleaseWallet);
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DBErrors::LOAD_OK) {
if (nLoadWalletRet == DBErrors::CORRUPT) {
error =
strprintf(_("Error loading %s: Wallet corrupted"), walletFile);
return nullptr;
}
if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) {
warnings.push_back(
strprintf(_("Error reading %s! All keys read correctly, but "
"transaction data or address book entries might be "
"missing or incorrect."),
walletFile));
} else if (nLoadWalletRet == DBErrors::TOO_NEW) {
error = strprintf(
_("Error loading %s: Wallet requires newer version of %s"),
walletFile, PACKAGE_NAME);
return nullptr;
} else if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
error = strprintf(
_("Wallet needed to be rewritten: restart %s to complete"),
PACKAGE_NAME);
return nullptr;
} else {
error = strprintf(_("Error loading %s"), walletFile);
return nullptr;
}
}
if (fFirstRun) {
// Ensure this wallet.dat can only be opened by clients supporting
// HD with chain split and expects no default key.
walletInstance->SetMinVersion(FEATURE_LATEST);
walletInstance->AddWalletFlags(wallet_creation_flags);
// Only create LegacyScriptPubKeyMan when not descriptor wallet
if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
walletInstance->SetupLegacyScriptPubKeyMan();
}
if (!(wallet_creation_flags &
(WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
LOCK(walletInstance->cs_wallet);
if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
walletInstance->SetupDescriptorScriptPubKeyMans();
// SetupDescriptorScriptPubKeyMans already calls SetupGeneration
// for us so we don't need to call SetupGeneration separately
} else {
// Legacy wallets need SetupGeneration here.
for (auto spk_man :
walletInstance->GetActiveScriptPubKeyMans()) {
if (!spk_man->SetupGeneration()) {
error = _("Unable to generate initial keys");
return nullptr;
}
}
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
} else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
// Make it impossible to disable private keys after creation
error = strprintf(_("Error loading %s: Private keys can only be "
"disabled during creation"),
walletFile);
return nullptr;
} else if (walletInstance->IsWalletFlagSet(
WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) {
if (spk_man->HavePrivateKeys()) {
warnings.push_back(
strprintf(_("Warning: Private keys detected in wallet {%s} "
"with disabled private keys"),
walletFile));
}
}
}
if (gArgs.IsArgSet("-mintxfee")) {
Amount n = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) ||
n == Amount::zero()) {
error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""));
return nullptr;
}
if (n > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") +
_("This is the minimum transaction fee you pay "
"on every transaction."));
}
walletInstance->m_min_fee = CFeeRate(n);
}
if (gArgs.IsArgSet("-maxapsfee")) {
const std::string max_aps_fee{gArgs.GetArg("-maxapsfee", "")};
Amount n = Amount::zero();
if (max_aps_fee == "-1") {
n = -1 * SATOSHI;
} else if (!ParseMoney(max_aps_fee, n)) {
error = AmountErrMsg("maxapsfee", max_aps_fee);
return nullptr;
}
if (n > HIGH_APS_FEE) {
warnings.push_back(
AmountHighWarn("-maxapsfee") + Untranslated(" ") +
_("This is the maximum transaction fee you pay (in addition to"
" the normal fee) to prioritize partial spend avoidance over"
" regular coin selection."));
}
walletInstance->m_max_aps_fee = n;
}
if (gArgs.IsArgSet("-fallbackfee")) {
Amount nFeePerK = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
error =
strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"),
gArgs.GetArg("-fallbackfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-fallbackfee") +
Untranslated(" ") +
_("This is the transaction fee you may pay when "
"fee estimates are not available."));
}
walletInstance->m_fallback_fee = CFeeRate(nFeePerK);
}
// Disable fallback fee in case value was set to 0, enable if non-null value
walletInstance->m_allow_fallback_fee =
walletInstance->m_fallback_fee.GetFeePerK() != Amount::zero();
if (gArgs.IsArgSet("-paytxfee")) {
Amount nFeePerK = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) {
error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") +
_("This is the transaction fee you will pay if "
"you send a transaction."));
}
walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000);
if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) {
error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' "
"(must be at least %s)"),
gArgs.GetArg("-paytxfee", ""),
chain.relayMinFee().ToString());
return nullptr;
}
}
if (gArgs.IsArgSet("-maxtxfee")) {
Amount nMaxFee = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) {
error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""));
return nullptr;
}
if (nMaxFee > HIGH_MAX_TX_FEE) {
warnings.push_back(_("-maxtxfee is set very high! Fees this large "
"could be paid on a single transaction."));
}
if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) {
error = strprintf(
_("Invalid amount for -maxtxfee=<amount>: '%s' (must be at "
"least the minrelay fee of %s to prevent stuck "
"transactions)"),
gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString());
return nullptr;
}
walletInstance->m_default_max_tx_fee = nMaxFee;
}
if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) {
warnings.push_back(
AmountHighWarn("-minrelaytxfee") + Untranslated(" ") +
_("The wallet will avoid paying less than the minimum relay fee."));
}
walletInstance->m_spend_zero_conf_change =
gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
walletInstance->m_default_address_type = DEFAULT_ADDRESS_TYPE;
walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n",
GetTimeMillis() - nStart);
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
LOCK(walletInstance->cs_wallet);
// Register wallet with validationinterface. It's done before rescan to
// avoid missing block connections between end of rescan and validation
// subscribing. Because of wallet lock being hold, block connection
// notifications are going to be pending on the validation-side until lock
// release. It's likely to have block processing duplicata (if rescan block
// range overlaps with notification one) but we guarantee at least than
// wallet state is correct after notifications delivery. This is temporary
// until rescan and notifications delivery are unified under same interface.
walletInstance->m_chain_notifications_handler =
walletInstance->chain().handleNotifications(walletInstance);
int rescan_height = 0;
if (!gArgs.GetBoolArg("-rescan", false)) {
WalletBatch batch(*walletInstance->database);
CBlockLocator locator;
if (batch.ReadBestBlock(locator)) {
if (const std::optional<int> fork_height =
chain.findLocatorFork(locator)) {
rescan_height = *fork_height;
}
}
}
const std::optional<int> tip_height = chain.getHeight();
if (tip_height) {
walletInstance->m_last_block_processed =
chain.getBlockHash(*tip_height);
walletInstance->m_last_block_processed_height = *tip_height;
} else {
walletInstance->m_last_block_processed.SetNull();
walletInstance->m_last_block_processed_height = -1;
}
if (tip_height && *tip_height != rescan_height) {
// We can't rescan beyond non-pruned blocks, stop and throw an error.
// This might happen if a user uses an old wallet within a pruned node
// or if they ran -disablewallet for a longer time, then decided to
// re-enable
if (chain.havePruned()) {
// Exit early and print an error.
// If a block is pruned after this check, we will load the wallet,
// but fail the rescan with a generic error.
int block_height = *tip_height;
while (block_height > 0 &&
chain.haveBlockOnDisk(block_height - 1) &&
rescan_height != block_height) {
--block_height;
}
if (rescan_height != block_height) {
error = _("Prune: last wallet synchronisation goes beyond "
"pruned data. You need to -reindex (download the "
"whole blockchain again in case of pruned node)");
return nullptr;
}
}
chain.initMessage(_("Rescanning...").translated);
walletInstance->WalletLogPrintf(
"Rescanning last %i blocks (from block %i)...\n",
*tip_height - rescan_height, rescan_height);
// No need to read and scan block if block was created before our wallet
// birthday (as adjusted for block time variability)
std::optional<int64_t> time_first_key;
for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) {
time_first_key = time;
}
}
if (time_first_key) {
if (std::optional<int> first_block =
chain.findFirstBlockWithTimeAndHeight(
*time_first_key - TIMESTAMP_WINDOW, rescan_height,
nullptr)) {
rescan_height = *first_block;
}
}
{
WalletRescanReserver reserver(*walletInstance);
if (!reserver.reserve() ||
(ScanResult::SUCCESS !=
walletInstance
->ScanForWalletTransactions(
chain.getBlockHash(rescan_height), rescan_height,
{} /* max height */, reserver, true /* update */)
.status)) {
error = _("Failed to rescan the wallet during initialization");
return nullptr;
}
}
walletInstance->chainStateFlushed(chain.getTipLocator());
walletInstance->database->IncrementUpdateCounter();
}
{
LOCK(cs_wallets);
for (auto &load_wallet : g_load_wallet_fns) {
load_wallet(interfaces::MakeWallet(walletInstance));
}
}
walletInstance->SetBroadcastTransactions(
gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n",
walletInstance->GetKeyPoolSize());
walletInstance->WalletLogPrintf("mapWallet.size() = %u\n",
walletInstance->mapWallet.size());
walletInstance->WalletLogPrintf("m_address_book.size() = %u\n",
walletInstance->m_address_book.size());
return walletInstance;
}
const CAddressBookData *
CWallet::FindAddressBookEntry(const CTxDestination &dest,
bool allow_change) const {
const auto &address_book_it = m_address_book.find(dest);
if (address_book_it == m_address_book.end()) {
return nullptr;
}
if ((!allow_change) && address_book_it->second.IsChange()) {
return nullptr;
}
return &address_book_it->second;
}
bool CWallet::UpgradeWallet(int version, bilingual_str &error,
std::vector<bilingual_str> &warnings) {
int prev_version = GetVersion();
int nMaxVersion = version;
// The -upgradewallet without argument case
if (nMaxVersion == 0) {
WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
nMaxVersion = FEATURE_LATEST;
// permanently upgrade the wallet immediately
SetMinVersion(FEATURE_LATEST);
} else {
WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
}
if (nMaxVersion < GetVersion()) {
error = _("Cannot downgrade wallet");
return false;
}
SetMaxVersion(nMaxVersion);
LOCK(cs_wallet);
// Do not upgrade versions to any version between HD_SPLIT and
// FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT
int max_version = GetVersion();
if (!CanSupportFeature(FEATURE_HD_SPLIT) &&
max_version >= FEATURE_HD_SPLIT &&
max_version < FEATURE_PRE_SPLIT_KEYPOOL) {
error = _("Cannot upgrade a non HD split wallet without upgrading to "
"support pre split keypool. Please use version 200300 or no "
"version specified.");
return false;
}
for (auto spk_man : GetActiveScriptPubKeyMans()) {
if (!spk_man->Upgrade(prev_version, error)) {
return false;
}
}
return true;
}
void CWallet::postInitProcess() {
LOCK(cs_wallet);
// Add wallet transactions that aren't already in a block to mempool.
// Do this here as mempool requires genesis block to be loaded.
ReacceptWalletTransactions();
// Update wallet transactions with current mempool transactions.
chain().requestMempoolTransactions(*this);
}
bool CWallet::BackupWallet(const std::string &strDest) const {
return database->Backup(strDest);
}
CKeyPool::CKeyPool() {
nTime = GetTime();
fInternal = false;
m_pre_split = false;
}
CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) {
nTime = GetTime();
vchPubKey = vchPubKeyIn;
fInternal = internalIn;
m_pre_split = false;
}
int CWalletTx::GetDepthInMainChain() const {
assert(pwallet != nullptr);
AssertLockHeld(pwallet->cs_wallet);
if (isUnconfirmed() || isAbandoned()) {
return 0;
}
return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) *
(isConflicted() ? -1 : 1);
}
int CWalletTx::GetBlocksToMaturity() const {
if (!IsCoinBase()) {
return 0;
}
int chain_depth = GetDepthInMainChain();
// coinbase tx should not be conflicted
assert(chain_depth >= 0);
return std::max(0, (COINBASE_MATURITY + 1) - chain_depth);
}
bool CWalletTx::IsImmatureCoinBase() const {
// note GetBlocksToMaturity is 0 for non-coinbase tx
return GetBlocksToMaturity() > 0;
}
std::vector<OutputGroup>
CWallet::GroupOutputs(const std::vector<COutput> &outputs, bool single_coin,
const size_t max_ancestors) const {
std::vector<OutputGroup> groups;
std::map<CTxDestination, OutputGroup> gmap;
std::set<CTxDestination> full_groups;
for (const auto &output : outputs) {
if (output.fSpendable) {
CTxDestination dst;
CInputCoin input_coin = output.GetInputCoin();
size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetId(), ancestors,
descendants);
if (!single_coin &&
ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey,
dst)) {
auto it = gmap.find(dst);
if (it != gmap.end()) {
// Limit output groups to no more than
// OUTPUT_GROUP_MAX_ENTRIES number of entries, to protect
// against inadvertently creating a too-large transaction
// when using -avoidpartialspends to prevent breaking
// consensus or surprising users with a very high amount of
// fees.
if (it->second.m_outputs.size() >=
OUTPUT_GROUP_MAX_ENTRIES) {
groups.push_back(it->second);
it->second = OutputGroup{};
full_groups.insert(dst);
}
it->second.Insert(input_coin, output.nDepth,
output.tx->IsFromMe(ISMINE_ALL),
ancestors, descendants);
} else {
gmap[dst].Insert(input_coin, output.nDepth,
output.tx->IsFromMe(ISMINE_ALL), ancestors,
descendants);
}
} else {
groups.emplace_back(input_coin, output.nDepth,
output.tx->IsFromMe(ISMINE_ALL), ancestors,
descendants);
}
}
}
if (!single_coin) {
for (auto &it : gmap) {
auto &group = it.second;
if (full_groups.count(it.first) > 0) {
// Make this unattractive as we want coin selection to avoid it
// if possible
group.m_ancestors = max_ancestors - 1;
}
groups.push_back(group);
}
}
return groups;
}
bool CWallet::IsCrypted() const {
return HasEncryptionKeys();
}
bool CWallet::IsLocked() const {
if (!IsCrypted()) {
return false;
}
LOCK(cs_wallet);
return vMasterKey.empty();
}
bool CWallet::Lock() {
if (!IsCrypted()) {
return false;
}
{
LOCK(cs_wallet);
vMasterKey.clear();
}
NotifyStatusChanged(this);
return true;
}
bool CWallet::Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys) {
{
LOCK(cs_wallet);
for (const auto &spk_man_pair : m_spk_managers) {
if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn,
accept_no_keys)) {
return false;
}
}
vMasterKey = vMasterKeyIn;
}
NotifyStatusChanged(this);
return true;
}
std::set<ScriptPubKeyMan *> CWallet::GetActiveScriptPubKeyMans() const {
std::set<ScriptPubKeyMan *> spk_mans;
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
auto spk_man = GetScriptPubKeyMan(t, internal);
if (spk_man) {
spk_mans.insert(spk_man);
}
}
}
return spk_mans;
}
std::set<ScriptPubKeyMan *> CWallet::GetAllScriptPubKeyMans() const {
std::set<ScriptPubKeyMan *> spk_mans;
for (const auto &spk_man_pair : m_spk_managers) {
spk_mans.insert(spk_man_pair.second.get());
}
return spk_mans;
}
ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const OutputType &type,
bool internal) const {
const std::map<OutputType, ScriptPubKeyMan *> &spk_managers =
internal ? m_internal_spk_managers : m_external_spk_managers;
std::map<OutputType, ScriptPubKeyMan *>::const_iterator it =
spk_managers.find(type);
if (it == spk_managers.end()) {
WalletLogPrintf(
"%s scriptPubKey Manager for output type %d does not exist\n",
internal ? "Internal" : "External", static_cast<int>(type));
return nullptr;
}
return it->second;
}
std::set<ScriptPubKeyMan *>
CWallet::GetScriptPubKeyMans(const CScript &script,
SignatureData &sigdata) const {
std::set<ScriptPubKeyMan *> spk_mans;
for (const auto &spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
spk_mans.insert(spk_man_pair.second.get());
}
}
return spk_mans;
}
ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const CScript &script) const {
SignatureData sigdata;
for (const auto &spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
return spk_man_pair.second.get();
}
}
return nullptr;
}
ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const uint256 &id) const {
if (m_spk_managers.count(id) > 0) {
return m_spk_managers.at(id).get();
}
return nullptr;
}
std::unique_ptr<SigningProvider>
CWallet::GetSolvingProvider(const CScript &script) const {
SignatureData sigdata;
return GetSolvingProvider(script, sigdata);
}
std::unique_ptr<SigningProvider>
CWallet::GetSolvingProvider(const CScript &script,
SignatureData &sigdata) const {
for (const auto &spk_man_pair : m_spk_managers) {
if (spk_man_pair.second->CanProvide(script, sigdata)) {
return spk_man_pair.second->GetSolvingProvider(script);
}
}
return nullptr;
}
LegacyScriptPubKeyMan *CWallet::GetLegacyScriptPubKeyMan() const {
if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
return nullptr;
}
// Legacy wallets only have one ScriptPubKeyMan which is a
// LegacyScriptPubKeyMan. Everything in m_internal_spk_managers and
// m_external_spk_managers point to the same legacyScriptPubKeyMan.
auto it = m_internal_spk_managers.find(OutputType::LEGACY);
if (it == m_internal_spk_managers.end()) {
return nullptr;
}
return dynamic_cast<LegacyScriptPubKeyMan *>(it->second);
}
LegacyScriptPubKeyMan *CWallet::GetOrCreateLegacyScriptPubKeyMan() {
SetupLegacyScriptPubKeyMan();
return GetLegacyScriptPubKeyMan();
}
void CWallet::SetupLegacyScriptPubKeyMan() {
if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() ||
!m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
return;
}
auto spk_manager =
std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this));
for (const auto &type : OUTPUT_TYPES) {
m_internal_spk_managers[type] = spk_manager.get();
m_external_spk_managers[type] = spk_manager.get();
}
m_spk_managers[spk_manager->GetID()] = std::move(spk_manager);
}
const CKeyingMaterial &CWallet::GetEncryptionKey() const {
return vMasterKey;
}
bool CWallet::HasEncryptionKeys() const {
return !mapMasterKeys.empty();
}
void CWallet::ConnectScriptPubKeyManNotifiers() {
for (const auto &spk_man : GetActiveScriptPubKeyMans()) {
spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged);
spk_man->NotifyCanGetAddressesChanged.connect(
NotifyCanGetAddressesChanged);
}
}
void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id,
WalletDescriptor &desc) {
auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(
new DescriptorScriptPubKeyMan(*this, desc));
m_spk_managers[id] = std::move(spk_manager);
}
void CWallet::SetupDescriptorScriptPubKeyMans() {
AssertLockHeld(cs_wallet);
// Make a seed
CKey seed_key;
seed_key.MakeNewKey(true);
CPubKey seed = seed_key.GetPubKey();
assert(seed_key.VerifyPubKey(seed));
// Get the extended key
CExtKey master_key;
master_key.SetSeed(seed_key.begin(), seed_key.size());
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
auto spk_manager =
std::make_unique<DescriptorScriptPubKeyMan>(*this, internal);
if (IsCrypted()) {
if (IsLocked()) {
throw std::runtime_error(
std::string(__func__) +
": Wallet is locked, cannot setup new descriptors");
}
if (!spk_manager->CheckDecryptionKey(vMasterKey) &&
!spk_manager->Encrypt(vMasterKey, nullptr)) {
throw std::runtime_error(
std::string(__func__) +
": Could not encrypt new descriptors");
}
}
spk_manager->SetupDescriptorGeneration(master_key, t);
uint256 id = spk_manager->GetID();
m_spk_managers[id] = std::move(spk_manager);
AddActiveScriptPubKeyMan(id, t, internal);
}
}
}
void CWallet::AddActiveScriptPubKeyMan(uint256 id, OutputType type,
bool internal) {
WalletBatch batch(*database);
if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id,
internal)) {
throw std::runtime_error(std::string(__func__) +
": writing active ScriptPubKeyMan id failed");
}
LoadActiveScriptPubKeyMan(id, type, internal);
}
void CWallet::LoadActiveScriptPubKeyMan(uint256 id, OutputType type,
bool internal) {
WalletLogPrintf(
"Setting spkMan to active: id = %s, type = %d, internal = %d\n",
id.ToString(), static_cast<int>(type), static_cast<int>(internal));
auto &spk_mans =
internal ? m_internal_spk_managers : m_external_spk_managers;
auto spk_man = m_spk_managers.at(id).get();
spk_man->SetInternal(internal);
spk_mans[type] = spk_man;
NotifyCanGetAddressesChanged();
}
bool CWallet::IsLegacy() const {
if (m_internal_spk_managers.count(OutputType::LEGACY) == 0) {
return false;
}
auto spk_man = dynamic_cast<LegacyScriptPubKeyMan *>(
m_internal_spk_managers.at(OutputType::LEGACY));
return spk_man != nullptr;
}
DescriptorScriptPubKeyMan *
CWallet::GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const {
for (auto &spk_man_pair : m_spk_managers) {
// Try to downcast to DescriptorScriptPubKeyMan then check if the
// descriptors match
DescriptorScriptPubKeyMan *spk_manager =
dynamic_cast<DescriptorScriptPubKeyMan *>(
spk_man_pair.second.get());
if (spk_manager != nullptr && spk_manager->HasWalletDescriptor(desc)) {
return spk_manager;
}
}
return nullptr;
}
ScriptPubKeyMan *
CWallet::AddWalletDescriptor(WalletDescriptor &desc,
const FlatSigningProvider &signing_provider,
const std::string &label) {
if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
WalletLogPrintf(
"Cannot add WalletDescriptor to a non-descriptor wallet\n");
return nullptr;
}
LOCK(cs_wallet);
auto new_spk_man = std::make_unique<DescriptorScriptPubKeyMan>(*this, desc);
// If we already have this descriptor, remove it from the maps but add the
// existing cache to desc
auto old_spk_man = GetDescriptorScriptPubKeyMan(desc);
if (old_spk_man) {
WalletLogPrintf("Update existing descriptor: %s\n",
desc.descriptor->ToString());
{
LOCK(old_spk_man->cs_desc_man);
new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache);
}
// Remove from maps of active spkMans
auto old_spk_man_id = old_spk_man->GetID();
for (bool internal : {false, true}) {
for (OutputType t : OUTPUT_TYPES) {
auto active_spk_man = GetScriptPubKeyMan(t, internal);
if (active_spk_man &&
active_spk_man->GetID() == old_spk_man_id) {
if (internal) {
m_internal_spk_managers.erase(t);
} else {
m_external_spk_managers.erase(t);
}
break;
}
}
}
m_spk_managers.erase(old_spk_man_id);
}
// Add the private keys to the descriptor
for (const auto &entry : signing_provider.keys) {
const CKey &key = entry.second;
new_spk_man->AddDescriptorKey(key, key.GetPubKey());
}
// Top up key pool, the manager will generate new scriptPubKeys internally
new_spk_man->TopUp();
// Apply the label if necessary
// Note: we disable labels for ranged descriptors
if (!desc.descriptor->IsRange()) {
auto script_pub_keys = new_spk_man->GetScriptPubKeys();
if (script_pub_keys.empty()) {
WalletLogPrintf(
"Could not generate scriptPubKeys (cache is empty)\n");
return nullptr;
}
CTxDestination dest;
if (ExtractDestination(script_pub_keys.at(0), dest)) {
SetAddressBook(dest, label, "receive");
}
}
// Save the descriptor to memory
auto ret = new_spk_man.get();
m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man);
// Save the descriptor to DB
ret->WriteDescriptor();
return ret;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index cd3bd5c3c..bc6b1d10c 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,1637 +1,1637 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2018-2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_WALLET_WALLET_H
#define BITCOIN_WALLET_WALLET_H
#include <amount.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <outputtype.h>
#include <primitives/blockhash.h>
#include <psbt.h>
#include <tinyformat.h>
#include <util/message.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
#include <util/translation.h>
#include <util/ui_change_type.h>
#include <validationinterface.h>
#include <wallet/coinselection.h>
#include <wallet/crypter.h>
#include <wallet/rpcwallet.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include <boost/signals2/signal.hpp>
using LoadWalletFn =
std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>;
struct bilingual_str;
//! Explicitly unload and delete the wallet.
//! Blocks the current thread after signaling the unload intent so that all
//! wallet clients release the wallet.
//! Note that, when blocking is not required, the wallet is implicitly unloaded
//! by the shared pointer deleter.
void UnloadWallet(std::shared_ptr<CWallet> &&wallet);
bool AddWallet(const std::shared_ptr<CWallet> &wallet);
bool RemoveWallet(const std::shared_ptr<CWallet> &wallet,
std::optional<bool> load_on_start,
std::vector<bilingual_str> &warnings);
bool RemoveWallet(const std::shared_ptr<CWallet> &wallet,
std::optional<bool> load_on_start);
std::vector<std::shared_ptr<CWallet>> GetWallets();
std::shared_ptr<CWallet> GetWallet(const std::string &name);
std::shared_ptr<CWallet>
LoadWallet(interfaces::Chain &chain, const std::string &name,
std::optional<bool> load_on_start, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error,
std::vector<bilingual_str> &warnings);
std::shared_ptr<CWallet>
CreateWallet(interfaces::Chain &chain, const std::string &name,
std::optional<bool> load_on_start, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error,
std::vector<bilingual_str> &warnings);
std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet);
std::unique_ptr<WalletDatabase>
MakeWalletDatabase(const std::string &name, const DatabaseOptions &options,
DatabaseStatus &status, bilingual_str &error);
//! -paytxfee default
constexpr Amount DEFAULT_PAY_TX_FEE = Amount::zero();
//! -fallbackfee default
static const Amount DEFAULT_FALLBACK_FEE = Amount::zero();
//! -mintxfee default
static const Amount DEFAULT_TRANSACTION_MINFEE_PER_KB = 1000 * SATOSHI;
/**
* maximum fee increase allowed to do partial spend avoidance, even for nodes
* with this feature disabled by default
*
* A value of -1 disables this feature completely.
* A value of 0 (current default) means to attempt to do partial spend
* avoidance, and use its results if the fees remain *unchanged* A value > 0
* means to do partial spend avoidance if the fee difference against a regular
* coin selection instance is in the range [0..value].
*/
static const Amount DEFAULT_MAX_AVOIDPARTIALSPEND_FEE = Amount::zero();
//! discourage APS fee higher than this amount
constexpr Amount HIGH_APS_FEE{COIN / 10000};
//! minimum recommended increment for BIP 125 replacement txs
static const Amount WALLET_INCREMENTAL_RELAY_FEE(5000 * SATOSHI);
//! Default for -spendzeroconfchange
static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
//! Default for -walletrejectlongchains
static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false;
static const bool DEFAULT_WALLETBROADCAST = true;
static const bool DEFAULT_DISABLE_WALLET = false;
//! -maxtxfee default
constexpr Amount DEFAULT_TRANSACTION_MAXFEE{COIN / 10};
//! Discourage users to set fees higher than this amount (in satoshis) per kB
constexpr Amount HIGH_TX_FEE_PER_KB{COIN / 100};
//! -maxtxfee will warn if called with a higher fee than this amount (in
//! satoshis)
constexpr Amount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB};
//! Pre-calculated constants for input size estimation
static constexpr size_t DUMMY_P2PKH_INPUT_SIZE = 148;
class CChainParams;
class CCoinControl;
class COutput;
class CScript;
class CTxMemPool;
class CWalletTx;
class ReserveDestination;
//! Default for -addresstype
constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::LEGACY};
static constexpr uint64_t KNOWN_WALLET_FLAGS =
WALLET_FLAG_AVOID_REUSE | WALLET_FLAG_BLANK_WALLET |
WALLET_FLAG_KEY_ORIGIN_METADATA | WALLET_FLAG_DISABLE_PRIVATE_KEYS |
WALLET_FLAG_DESCRIPTORS;
static constexpr uint64_t MUTABLE_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE;
static const std::map<std::string, WalletFlags> WALLET_FLAG_MAP{
{"avoid_reuse", WALLET_FLAG_AVOID_REUSE},
{"blank", WALLET_FLAG_BLANK_WALLET},
{"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA},
{"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS},
{"descriptor_wallet", WALLET_FLAG_DESCRIPTORS},
};
extern const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS;
/**
* A wrapper to reserve an address from a wallet
*
* ReserveDestination is used to reserve an address.
* It is currently only used inside of CreateTransaction.
*
* Instantiating a ReserveDestination does not reserve an address. To do so,
* GetReservedDestination() needs to be called on the object. Once an address
* has been reserved, call KeepDestination() on the ReserveDestination object to
* make sure it is not returned. Call ReturnDestination() to return the address
* so it can be re-used (for example, if the address was used in a new
* transaction and that transaction was not completed and needed to be aborted).
*
* If an address is reserved and KeepDestination() is not called, then the
* address will be returned when the ReserveDestination goes out of scope.
*/
class ReserveDestination {
protected:
//! The wallet to reserve from
const CWallet *const pwallet;
//! The ScriptPubKeyMan to reserve from. Based on type when
//! GetReservedDestination is called
ScriptPubKeyMan *m_spk_man{nullptr};
OutputType const type;
//! The index of the address's key in the keypool
int64_t nIndex{-1};
//! The destination
CTxDestination address;
//! Whether this is from the internal (change output) keypool
bool fInternal{false};
public:
//! Construct a ReserveDestination object. This does NOT reserve an address
//! yet
explicit ReserveDestination(CWallet *_pwallet, OutputType _type)
: pwallet(_pwallet), type(_type) {}
ReserveDestination(const ReserveDestination &) = delete;
ReserveDestination &operator=(const ReserveDestination &) = delete;
//! Destructor. If a key has been reserved and not KeepKey'ed, it will be
//! returned to the keypool
~ReserveDestination() { ReturnDestination(); }
//! Reserve an address
bool GetReservedDestination(CTxDestination &pubkey, bool internal);
//! Return reserved address
void ReturnDestination();
//! Keep the address. Do not return it's key to the keypool when this object
//! goes out of scope
void KeepDestination();
};
/** Address book data */
class CAddressBookData {
private:
bool m_change{true};
std::string m_label;
public:
std::string purpose;
CAddressBookData() : purpose("unknown") {}
typedef std::map<std::string, std::string> StringMap;
StringMap destdata;
bool IsChange() const { return m_change; }
const std::string &GetLabel() const { return m_label; }
void SetLabel(const std::string &label) {
m_change = false;
m_label = label;
}
};
struct CRecipient {
CScript scriptPubKey;
Amount nAmount;
bool fSubtractFeeFromAmount;
};
typedef std::map<std::string, std::string> mapValue_t;
static inline void ReadOrderPos(int64_t &nOrderPos, mapValue_t &mapValue) {
if (!mapValue.count("n")) {
// TODO: calculate elsewhere
nOrderPos = -1;
return;
}
nOrderPos = atoi64(mapValue["n"]);
}
static inline void WriteOrderPos(const int64_t &nOrderPos,
mapValue_t &mapValue) {
if (nOrderPos == -1) {
return;
}
mapValue["n"] = ToString(nOrderPos);
}
struct COutputEntry {
CTxDestination destination;
Amount amount;
int vout;
};
/**
* Legacy class used for deserializing vtxPrev for backwards compatibility.
* vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3,
* but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs.
* These need to get deserialized for field alignment when deserializing
* a CWalletTx, but the deserialized values are discarded.
*/
class CMerkleTx {
public:
template <typename Stream> void Unserialize(Stream &s) {
CTransactionRef tx;
BlockHash hashBlock;
std::vector<uint256> vMerkleBranch;
int nIndex = 0;
s >> tx >> hashBlock >> vMerkleBranch >> nIndex;
}
};
// Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *pwallet,
bool use_max_sig = false);
/**
* A transaction with a bunch of additional info that only the owner cares
* about. It includes any unrecorded transactions needed to link it back to the
* block chain.
*/
class CWalletTx {
private:
const CWallet *const pwallet;
/**
* Constant used in hashBlock to indicate tx has been abandoned, only used
* at serialization/deserialization to avoid ambiguity with conflicted.
*/
static constexpr const uint256 &ABANDON_HASH = uint256::ONE;
public:
/**
* Key/value map with information about the transaction.
*
* The following keys can be read and written through the map and are
* serialized in the wallet database:
*
* "comment", "to" - comment strings provided to sendtoaddress,
* and sendmany wallet RPCs
* "replaces_txid" - txid (as HexStr) of transaction replaced by
* bumpfee on transaction created by bumpfee
* "replaced_by_txid" - txid (as HexStr) of transaction created by
* bumpfee on transaction replaced by bumpfee
* "from", "message" - obsolete fields that could be set in UI prior to
* 2011 (removed in commit 4d9b223)
*
* The following keys are serialized in the wallet database, but shouldn't
* be read or written through the map (they will be temporarily added and
* removed from the map during serialization):
*
* "fromaccount" - serialized strFromAccount value
* "n" - serialized nOrderPos value
* "timesmart" - serialized nTimeSmart value
* "spent" - serialized vfSpent value that existed prior to
* 2014 (removed in commit 93a18a3)
*/
mapValue_t mapValue;
std::vector<std::pair<std::string, std::string>> vOrderForm;
unsigned int fTimeReceivedIsTxTime;
//! time received by this node
unsigned int nTimeReceived;
/**
* Stable timestamp that never changes, and reflects the order a transaction
* was added to the wallet. Timestamp is based on the block time for a
* transaction added as part of a block, or else the time when the
* transaction was received if it wasn't part of a block, with the timestamp
* adjusted in both cases so timestamp order matches the order transactions
* were added to the wallet. More details can be found in
* CWallet::ComputeTimeSmart().
*/
unsigned int nTimeSmart;
/**
* From me flag is set to 1 for transactions that were created by the wallet
* on this bitcoin node, and set to 0 for transactions that were created
* externally and came in through the network or sendrawtransaction RPC.
*/
bool fFromMe;
//! position in ordered transaction list
int64_t nOrderPos;
std::multimap<int64_t, CWalletTx *>::const_iterator m_it_wtxOrdered;
// memory only
enum AmountType {
DEBIT,
CREDIT,
IMMATURE_CREDIT,
AVAILABLE_CREDIT,
AMOUNTTYPE_ENUM_ELEMENTS
};
Amount GetCachableAmount(AmountType type, const isminefilter &filter,
bool recalculate = false) const;
mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS];
/**
* This flag is true if all m_amounts caches are empty. This is particularly
* useful in places where MarkDirty is conditionally called and the
* condition can be expensive and thus can be skipped if the flag is true.
* See MarkDestinationsDirty.
*/
mutable bool m_is_cache_empty{true};
mutable bool fChangeCached;
mutable bool fInMempool;
mutable Amount nChangeCached;
CWalletTx(const CWallet *wallet, CTransactionRef arg)
: pwallet(wallet), tx(std::move(arg)) {
Init();
}
void Init() {
mapValue.clear();
vOrderForm.clear();
fTimeReceivedIsTxTime = false;
nTimeReceived = 0;
nTimeSmart = 0;
fFromMe = false;
fChangeCached = false;
fInMempool = false;
nChangeCached = Amount::zero();
nOrderPos = -1;
m_confirm = Confirmation{};
}
CTransactionRef tx;
/**
* New transactions start as UNCONFIRMED. At BlockConnected,
* they will transition to CONFIRMED. In case of reorg, at
* BlockDisconnected, they roll back to UNCONFIRMED. If we detect a
* conflicting transaction at block connection, we update conflicted tx and
* its dependencies as CONFLICTED. If tx isn't confirmed and outside of
* mempool, the user may switch it to ABANDONED by using the
* abandontransaction call. This last status may be override by a CONFLICTED
* or CONFIRMED transition.
*/
enum Status { UNCONFIRMED, CONFIRMED, CONFLICTED, ABANDONED };
/**
* Confirmation includes tx status and a triplet of {block height/block
* hash/tx index in block} at which tx has been confirmed. All three are set
* to 0 if tx is unconfirmed or abandoned. Meaning of these fields changes
* with CONFLICTED state where they instead point to block hash and block
* height of the deepest conflicting tx.
*/
struct Confirmation {
Status status;
int block_height;
BlockHash hashBlock;
int nIndex;
Confirmation(Status s = UNCONFIRMED, int b = 0,
BlockHash h = BlockHash(), int i = 0)
: status(s), block_height(b), hashBlock(h), nIndex(i) {}
};
Confirmation m_confirm;
template <typename Stream> void Serialize(Stream &s) const {
mapValue_t mapValueCopy = mapValue;
mapValueCopy["fromaccount"] = "";
WriteOrderPos(nOrderPos, mapValueCopy);
if (nTimeSmart) {
mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart);
}
//! Used to be vMerkleBranch
std::vector<char> dummy_vector1;
//! Used to be vtxPrev
std::vector<char> dummy_vector2;
//! Used to be fSpent
bool dummy_bool = false;
uint256 serializedHash =
isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock;
int serializedIndex =
isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex;
s << tx << serializedHash << dummy_vector1 << serializedIndex
<< dummy_vector2 << mapValueCopy << vOrderForm
<< fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool;
}
template <typename Stream> void Unserialize(Stream &s) {
Init();
//! Used to be vMerkleBranch
std::vector<uint256> dummy_vector1;
//! Used to be vtxPrev
std::vector<CMerkleTx> dummy_vector2;
//! Used to be fSpent
bool dummy_bool;
int serializedIndex;
s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >>
dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >>
nTimeReceived >> fFromMe >> dummy_bool;
/*
* At serialization/deserialization, an nIndex == -1 means that
* hashBlock refers to the earliest block in the chain we know this or
* any in-wallet ancestor conflicts with. If nIndex == -1 and hashBlock
* is ABANDON_HASH, it means transaction is abandoned. In same context,
* an nIndex >= 0 refers to a confirmed transaction (if hashBlock set)
* or unconfirmed one. Older clients interpret nIndex == -1 as
* unconfirmed for backward compatibility (pre-commit 9ac63d6).
*/
if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) {
setAbandoned();
} else if (serializedIndex == -1) {
setConflicted();
} else if (!m_confirm.hashBlock.IsNull()) {
m_confirm.nIndex = serializedIndex;
setConfirmed();
}
ReadOrderPos(nOrderPos, mapValue);
nTimeSmart = mapValue.count("timesmart")
? (unsigned int)atoi64(mapValue["timesmart"])
: 0;
mapValue.erase("fromaccount");
mapValue.erase("spent");
mapValue.erase("n");
mapValue.erase("timesmart");
}
void SetTx(CTransactionRef arg) { tx = std::move(arg); }
//! make sure balances are recalculated
void MarkDirty() {
m_amounts[DEBIT].Reset();
m_amounts[CREDIT].Reset();
m_amounts[IMMATURE_CREDIT].Reset();
m_amounts[AVAILABLE_CREDIT].Reset();
fChangeCached = false;
m_is_cache_empty = true;
}
//! filter decides which addresses will count towards the debit
Amount GetDebit(const isminefilter &filter) const;
Amount GetCredit(const isminefilter &filter) const;
Amount GetImmatureCredit(bool fUseCache = true) const;
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The
// annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid
// having to resolve the issue of member access into incomplete type
// CWallet.
Amount GetAvailableCredit(bool fUseCache = true,
const isminefilter &filter = ISMINE_SPENDABLE)
const NO_THREAD_SAFETY_ANALYSIS;
Amount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const;
Amount GetChange() const;
// Get the marginal bytes if spending the specified output from this
// transaction
int GetSpendSize(unsigned int out, bool use_max_sig = false) const {
return CalculateMaximumSignedInputSize(tx->vout[out], pwallet,
use_max_sig);
}
void GetAmounts(std::list<COutputEntry> &listReceived,
std::list<COutputEntry> &listSent, Amount &nFee,
const isminefilter &filter) const;
bool IsFromMe(const isminefilter &filter) const {
return GetDebit(filter) > Amount::zero();
}
// True if only scriptSigs are different
bool IsEquivalentTo(const CWalletTx &tx) const;
bool InMempool() const;
bool IsTrusted() const;
int64_t GetTxTime() const;
// Pass this transaction to node for mempool insertion and relay to peers if
// flag set to true
bool SubmitMemoryPoolAndRelay(std::string &err_string, bool relay);
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
// "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to
// resolve the issue of member access into incomplete type CWallet. Note
// that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)"
// in place.
std::set<TxId> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
/**
* Return depth of transaction in blockchain:
* <0 : conflicts with a transaction this deep in the blockchain
* 0 : in memory pool, waiting to be included in a block
* >=1 : this many blocks deep in the main chain
*/
// TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
// annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
// "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to
// resolve the issue of member access into incomplete type CWallet. Note
// that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)"
// in place.
int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS;
bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
/**
* @return number of blocks to maturity for this transaction:
* 0 : is not a coinbase transaction, or is a mature coinbase transaction
* >0 : is a coinbase transaction which matures in this many blocks
*/
int GetBlocksToMaturity() const;
bool isAbandoned() const {
return m_confirm.status == CWalletTx::ABANDONED;
}
void setAbandoned() {
m_confirm.status = CWalletTx::ABANDONED;
m_confirm.hashBlock = BlockHash();
m_confirm.block_height = 0;
m_confirm.nIndex = 0;
}
bool isConflicted() const {
return m_confirm.status == CWalletTx::CONFLICTED;
}
void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; }
bool isUnconfirmed() const {
return m_confirm.status == CWalletTx::UNCONFIRMED;
}
void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; }
bool isConfirmed() const {
return m_confirm.status == CWalletTx::CONFIRMED;
}
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
TxId GetId() const { return tx->GetId(); }
bool IsCoinBase() const { return tx->IsCoinBase(); }
bool IsImmatureCoinBase() const;
// Disable copying of CWalletTx objects to prevent bugs where instances get
// copied in and out of the mapWallet map, and fields are updated in the
// wrong copy.
CWalletTx(CWalletTx const &) = delete;
void operator=(CWalletTx const &x) = delete;
};
class COutput {
public:
const CWalletTx *tx;
int i;
int nDepth;
/**
* Pre-computed estimated size of this output as a fully-signed input in a
* transaction. Can be -1 if it could not be calculated.
*/
int nInputBytes;
/** Whether we have the private keys to spend this output */
bool fSpendable;
/** Whether we know how to spend this output, ignoring the lack of keys */
bool fSolvable;
/**
* Whether to use the maximum sized, 72 byte signature when calculating the
* size of the input spend. This should only be set when watch-only outputs
* are allowed.
*/
bool use_max_sig;
/**
* Whether this output is considered safe to spend. Unconfirmed transactions
* from outside keys are considered unsafe and will not be used to fund new
* spending transactions.
*/
bool fSafe;
COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn,
bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) {
tx = txIn;
i = iIn;
nDepth = nDepthIn;
fSpendable = fSpendableIn;
fSolvable = fSolvableIn;
fSafe = fSafeIn;
nInputBytes = -1;
use_max_sig = use_max_sig_in;
// If known and signable by the given wallet, compute nInputBytes
// Failure will keep this value -1
if (fSpendable && tx) {
nInputBytes = tx->GetSpendSize(i, use_max_sig);
}
}
std::string ToString() const;
inline CInputCoin GetInputCoin() const {
return CInputCoin(tx->tx, i, nInputBytes);
}
};
struct CoinSelectionParams {
bool use_bnb = true;
size_t change_output_size = 0;
size_t change_spend_size = 0;
CFeeRate effective_fee = CFeeRate(Amount::zero());
size_t tx_noinputs_size = 0;
//! Indicate that we are subtracting the fee from outputs
bool m_subtract_fee_outputs = false;
CoinSelectionParams(bool use_bnb_, size_t change_output_size_,
size_t change_spend_size_, CFeeRate effective_fee_,
size_t tx_noinputs_size_)
: use_bnb(use_bnb_), change_output_size(change_output_size_),
change_spend_size(change_spend_size_), effective_fee(effective_fee_),
tx_noinputs_size(tx_noinputs_size_) {}
CoinSelectionParams() {}
};
// forward declarations for ScanForWalletTransactions/RescanFromTime
class WalletRescanReserver;
/**
* A CWallet maintains a set of transactions and balances, and provides the
* ability to create new transactions.
*/
class CWallet final : public WalletStorage,
public interfaces::Chain::Notifications {
private:
CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet);
bool Unlock(const CKeyingMaterial &vMasterKeyIn,
bool accept_no_keys = false);
std::atomic<bool> fAbortRescan{false};
// controlled by WalletRescanReserver
std::atomic<bool> fScanningWallet{false};
std::atomic<int64_t> m_scanning_start{0};
std::atomic<double> m_scanning_progress{0};
friend class WalletRescanReserver;
//! the current wallet version: clients below this version are not able to
//! load the wallet
int nWalletVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
//! the maximum wallet format version: memory-only variable that specifies
//! to what version this wallet may be upgraded
int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE;
int64_t nNextResend = 0;
bool fBroadcastTransactions = false;
// Local time that the tip block was received. Used to schedule wallet
// rebroadcasts.
std::atomic<int64_t> m_best_block_time{0};
/**
* Used to keep track of spent outpoints, and detect and report conflicts
* (double-spends or mutated transactions where the mutant gets mined).
*/
typedef std::multimap<COutPoint, TxId> TxSpends;
TxSpends mapTxSpends GUARDED_BY(cs_wallet);
void AddToSpends(const COutPoint &outpoint, const TxId &wtxid)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void AddToSpends(const TxId &wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* 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 *pIndex == nullptr, 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 abandoned is an indication that it is not safe to be
* considered abandoned. Abandoned state should probably be more carefully
* tracked via different posInBlock signals or by checking mempool presence
* when necessary.
*/
bool AddToWalletIfInvolvingMe(const CTransactionRef &tx,
CWalletTx::Confirmation confirm, bool fUpdate)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Mark a transaction (and its in-wallet descendants) as conflicting with a
* particular block.
*/
void MarkConflicted(const BlockHash &hashBlock, int conflicting_height,
const TxId &txid);
/**
* Mark a transaction's inputs dirty, thus forcing the outputs to be
* recomputed
*/
void MarkInputsDirty(const CTransactionRef &tx)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Used by
* TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions.
* Should be called with non-zero block_hash and posInBlock if this is for a
* transaction that is included in a block.
*/
void SyncTransaction(const CTransactionRef &tx,
CWalletTx::Confirmation confirm, bool update_tx = true)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::atomic<uint64_t> m_wallet_flags{0};
bool SetAddressBookWithDB(WalletBatch &batch, const CTxDestination &address,
const std::string &strName,
const std::string &strPurpose);
//! Unsets a wallet flag and saves it to disk
void UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag);
//! Unset the blank wallet flag and saves it to disk
void UnsetBlankWalletFlag(WalletBatch &batch) override;
/** Interface for accessing chain state. */
interfaces::Chain *m_chain;
/** Wallet name: relative directory name or "" for default wallet. */
std::string m_name;
/** Internal database handle. */
std::unique_ptr<WalletDatabase> database;
/**
* The following is used to keep track of how far behind the wallet is
* from the chain sync, and to allow clients to block on us being caught up.
*
* Processed hash is a pointer on node's tip and doesn't imply that the
* wallet has scanned sequentially all blocks up to this one.
*/
BlockHash m_last_block_processed GUARDED_BY(cs_wallet);
/* Height of last block processed is used by wallet to know depth of
* transactions without relying on Chain interface beyond asynchronous
* updates. For safety, we initialize it to -1. Height is a pointer on
* node's tip and doesn't imply that the wallet has scanned sequentially all
* blocks up to this one.
*/
int m_last_block_processed_height GUARDED_BY(cs_wallet) = -1;
bool CreateTransactionInternal(const std::vector<CRecipient> &vecSend,
CTransactionRef &tx, Amount &nFeeRet,
int &nChangePosInOut, bilingual_str &error,
const CCoinControl &coin_control, bool sign);
std::map<OutputType, ScriptPubKeyMan *> m_external_spk_managers;
std::map<OutputType, ScriptPubKeyMan *> m_internal_spk_managers;
// Indexed by a unique identifier produced by each ScriptPubKeyMan using
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal
// structure
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
public:
/*
* Main wallet lock.
* This lock protects all the fields added by CWallet.
*/
mutable RecursiveMutex cs_wallet;
/**
* Get database handle used by this wallet. Ideally this function would not
* be necessary.
*/
WalletDatabase &GetDBHandle() { return *database; }
WalletDatabase &GetDatabase() override { return *database; }
/**
* 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 Amount nTargetValue,
std::set<CInputCoin> &setCoinsRet, Amount &nValueRet,
const CCoinControl &coin_control,
CoinSelectionParams &coin_selection_params,
bool &bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Get a name for this wallet for logging/debugging purposes.
*/
const std::string &GetName() const { return m_name; }
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
MasterKeyMap mapMasterKeys;
unsigned int nMasterKeyMaxID = 0;
/** Construct wallet with specified name and database implementation. */
CWallet(interfaces::Chain *chain, const std::string &name,
std::unique_ptr<WalletDatabase> _database)
: m_chain(chain), m_name(name), database(std::move(_database)) {}
~CWallet() {
// Should not have slots connected at this point.
assert(NotifyUnload.empty());
}
/* Returns the chain params used by this wallet. */
const CChainParams &GetChainParams() const override;
bool IsCrypted() const;
bool IsLocked() const override;
bool Lock();
/** Interface to assert chain access */
bool HaveChain() const { return m_chain ? true : false; }
std::map<TxId, CWalletTx> mapWallet GUARDED_BY(cs_wallet);
typedef std::multimap<int64_t, CWalletTx *> TxItems;
TxItems wtxOrdered;
int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0;
uint64_t nAccountingEntryNumber = 0;
std::map<CTxDestination, CAddressBookData>
m_address_book GUARDED_BY(cs_wallet);
const CAddressBookData *
FindAddressBookEntry(const CTxDestination &,
bool allow_change = false) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet);
/** Registered interfaces::Chain::Notifications handler. */
std::unique_ptr<interfaces::Handler> m_chain_notifications_handler;
/** Interface for accessing chain state. */
interfaces::Chain &chain() const {
assert(m_chain);
return *m_chain;
}
const CWalletTx *GetWalletTx(const TxId &txid) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsTrusted(const CWalletTx &wtx, std::set<TxId> &trusted_parents) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! check whether we are allowed to upgrade (or already support) to the
//! named feature
bool CanSupportFeature(enum WalletFeature wf) const override
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
AssertLockHeld(cs_wallet);
return nWalletMaxVersion >= wf;
}
/**
* populate vCoins with vector of available COutputs.
*/
void AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe = true,
const CCoinControl *coinControl = nullptr,
const Amount nMinimumAmount = SATOSHI,
const Amount nMaximumAmount = MAX_MONEY,
const Amount nMinimumSumAmount = MAX_MONEY,
const uint64_t nMaximumCount = 0) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Return list of available coins and locked coins grouped by non-change
* output address.
*/
std::map<CTxDestination, std::vector<COutput>> ListCoins() const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Find non-change parent output.
*/
const CTxOut &FindNonChangeParentOutput(const CTransaction &tx,
int output) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Shuffle and select coins until nTargetValue is reached while avoiding
* small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is
* assembled.
*/
bool SelectCoinsMinConf(const Amount nTargetValue,
const CoinEligibilityFilter &eligibility_filter,
std::vector<OutputGroup> groups,
std::set<CInputCoin> &setCoinsRet,
Amount &nValueRet,
const CoinSelectionParams &coin_selection_params,
bool &bnb_used) const;
bool IsSpent(const COutPoint &outpoint) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Whether this or any UTXO with the same CTxDestination has been spent.
bool IsSpentKey(const TxId &txid, unsigned int n) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetSpentKeyState(WalletBatch &batch, const TxId &txid, unsigned int n,
bool used, std::set<CTxDestination> &tx_destinations)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput> &outputs,
bool single_coin,
const size_t max_ancestors) const;
bool IsLockedCoin(const COutPoint &outpoint) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LockCoin(const COutPoint &output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void UnlockCoin(const COutPoint &output)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ListLockedCoins(std::vector<COutPoint> &vOutpts) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/*
* Rescan abort properties
*/
void AbortRescan() { fAbortRescan = true; }
bool IsAbortingRescan() const { return fAbortRescan; }
bool IsScanning() const { return fScanningWallet; }
int64_t ScanningDuration() const {
return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0;
}
double ScanningProgress() const {
return fScanningWallet ? double(m_scanning_progress) : 0;
}
//! Upgrade stored CKeyMetadata objects to store key origin info as
//! KeyOriginInfo
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
AssertLockHeld(cs_wallet);
nWalletVersion = nVersion;
nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion);
return true;
}
/**
* Adds a destination data tuple to the store, and saves it to disk
* When adding new fields, take care to consider how DelAddressBook should
* handle it!
*/
bool AddDestData(WalletBatch &batch, const CTxDestination &dest,
const std::string &key, const std::string &value)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Erases a destination data tuple in the store and on disk
bool EraseDestData(WalletBatch &batch, const CTxDestination &dest,
const std::string &key)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a destination data tuple to the store, without saving it to disk
void LoadDestData(const CTxDestination &dest, const std::string &key,
const std::string &value)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Look up a destination data tuple in the store, return true if found
//! false otherwise
bool GetDestData(const CTxDestination &dest, const std::string &key,
std::string *value) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Get all destination values matching a prefix.
std::vector<std::string> GetDestValues(const std::string &prefix) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Holds a timestamp at which point the wallet is scheduled (externally) to
//! be relocked. Caller must arrange for actual relocking to occur via
//! Lock().
int64_t nRelockTime GUARDED_BY(cs_wallet){0};
// Used to prevent concurrent calls to walletpassphrase RPC.
Mutex m_unlock_mutex;
bool Unlock(const SecureString &strWalletPassphrase,
bool accept_no_keys = false);
bool ChangeWalletPassphrase(const SecureString &strOldWalletPassphrase,
const SecureString &strNewWalletPassphrase);
bool EncryptWallet(const SecureString &strWalletPassphrase);
void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
unsigned int ComputeTimeSmart(const CWalletTx &wtx) const;
/**
* Increment the next transaction order id
* @return next transaction order id
*/
int64_t IncOrderPosNext(WalletBatch *batch = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
DBErrors ReorderTransactions();
void MarkDirty();
//! Callback for updating transaction metadata in mapWallet.
//!
//! @param wtx - reference to mapWallet transaction to update
//! @param new_tx - true if wtx is newly inserted, false if it previously
//! existed
//!
//! @return true if wtx is changed and needs to be saved to disk, otherwise
//! false
using UpdateWalletTxFn = std::function<bool(CWalletTx &wtx, bool new_tx)>;
CWalletTx *AddToWallet(CTransactionRef tx,
const CWalletTx::Confirmation &confirm,
const UpdateWalletTxFn &update_wtx = nullptr,
bool fFlushOnClose = true);
bool LoadToWallet(const TxId &txid, const UpdateWalletTxFn &fill_wtx)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void transactionAddedToMempool(const CTransactionRef &tx) override;
void blockConnected(const CBlock &block, int height) override;
void blockDisconnected(const CBlock &block, int height) override;
void updatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime,
const WalletRescanReserver &reserver, bool update);
struct ScanResult {
enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS;
//! Hash and height of most recent block that was successfully scanned.
//! Unset if no blocks were scanned due to read errors or the chain
//! being empty.
BlockHash last_scanned_block;
std::optional<int> last_scanned_height;
//! Hash of the most recent block that could not be scanned due to
//! read errors or pruning. Will be set if status is FAILURE, unset if
//! status is SUCCESS, and may or may not be set if status is
//! USER_ABORT.
BlockHash last_failed_block;
};
ScanResult ScanForWalletTransactions(const BlockHash &start_block,
int start_height,
std::optional<int> max_height,
const WalletRescanReserver &reserver,
bool fUpdate);
void transactionRemovedFromMempool(const CTransactionRef &tx,
MemPoolRemovalReason reason) override;
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions();
struct Balance {
//! Trusted, at depth=GetBalance.min_depth or more
Amount m_mine_trusted{Amount::zero()};
//! Untrusted, but in mempool (pending)
Amount m_mine_untrusted_pending{Amount::zero()};
//! Immature coinbases in the main chain
Amount m_mine_immature{Amount::zero()};
Amount m_watchonly_trusted{Amount::zero()};
Amount m_watchonly_untrusted_pending{Amount::zero()};
Amount m_watchonly_immature{Amount::zero()};
};
Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const;
Amount GetAvailableBalance(const CCoinControl *coinControl = nullptr) const;
OutputType
TransactionChangeType(const std::optional<OutputType> &change_type,
const std::vector<CRecipient> &vecSend);
/**
* Insert additional inputs into the transaction by calling
* CreateTransaction();
*/
bool FundTransaction(CMutableTransaction &tx, Amount &nFeeRet,
int &nChangePosInOut, bilingual_str &error,
bool lockUnspents,
const std::set<int> &setSubtractFeeFromOutputs,
CCoinControl coinControl);
// Fetch the inputs and sign with SIGHASH_ALL.
bool SignTransaction(CMutableTransaction &tx) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Sign the tx given the input coins and sighash.
bool SignTransaction(CMutableTransaction &tx,
const std::map<COutPoint, Coin> &coins,
SigHashType sighash,
std::map<int, std::string> &input_errors) const;
SigningResult SignMessage(const std::string &message, const PKHash &pkhash,
std::string &str_sig) const;
/**
* Fills out a PSBT with information from the wallet. Fills in UTXOs if we
* have them. Tries to sign if sign=true. Sets `complete` if the PSBT is now
* complete (i.e. has all required signatures or signature-parts, and is
* ready to finalize.) Sets `error` and returns false if something goes
* wrong.
*
* @param[in] psbtx PartiallySignedTransaction to fill in
* @param[out] complete indicates whether the PSBT is now complete
* @param[in] sighash_type the sighash type to use when signing (if PSBT
* does not specify)
* @param[in] sign whether to sign or not
* @param[in] bip32derivs whether to fill in bip32 derivation information
* if available return error
*/
TransactionError
FillPSBT(PartiallySignedTransaction &psbtx, bool &complete,
SigHashType sighash_type = SigHashType().withForkId(),
bool sign = true, bool bip32derivs = true) const;
/**
* Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed
* @note passing nChangePosInOut as -1 will result in setting a random
* position
*/
bool CreateTransaction(const std::vector<CRecipient> &vecSend,
CTransactionRef &tx, Amount &nFeeRet,
int &nChangePosInOut, bilingual_str &error,
const CCoinControl &coin_control, bool sign = true);
/**
* Submit the transaction to the node's mempool and then relay to peers.
* Should be called after CreateTransaction unless you want to abort
* broadcasting the transaction.
*
* @param[in] tx The transaction to be broadcast.
* @param[in] mapValue key-values to be set on the transaction.
* @param[in] orderForm BIP 70 / BIP 21 order form details to be set on the
* transaction.
*/
void CommitTransaction(
CTransactionRef tx, mapValue_t mapValue,
std::vector<std::pair<std::string, std::string>> orderForm);
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts,
bool use_max_sig = false) const {
std::vector<CTxOut> v_txouts(txouts.size());
std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
return DummySignTx(txNew, v_txouts, use_max_sig);
}
bool DummySignTx(CMutableTransaction &txNew,
const std::vector<CTxOut> &txouts,
bool use_max_sig = false) const;
bool DummySignInput(CTxIn &tx_in, const CTxOut &txout,
bool use_max_sig = false) const;
bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map,
const int64_t timestamp)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportPubKeys(
const std::vector<CKeyID> &ordered_pubkeys,
const std::map<CKeyID, CPubKey> &pubkey_map,
const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins,
const bool add_keypool, const bool internal, const int64_t timestamp)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool ImportScriptPubKeys(const std::string &label,
const std::set<CScript> &script_pub_keys,
const bool have_solving_data,
const bool apply_label, const int64_t timestamp)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE};
//! will be false if -fallbackfee=0
bool m_allow_fallback_fee{true};
// Override with -mintxfee
CFeeRate m_min_fee{DEFAULT_TRANSACTION_MINFEE_PER_KB};
/**
* If fee estimation does not have enough data to provide estimates, use
* this fee instead. Has no effect if not using fee estimation Override with
* -fallbackfee
*/
CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE};
//! note: this is absolute fee, not fee rate
Amount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE};
OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE};
/**
* Default output type for change outputs. When unset, automatically choose
* type based on address type setting and the types other of non-change
* outputs (see implementation in CWallet::TransactionChangeType for
* details).
*/
std::optional<OutputType> m_default_change_type{};
/**
* Absolute maximum transaction fee (in satoshis) used by default for the
* wallet.
*/
Amount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE};
size_t KeypoolCountExternalKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool TopUpKeyPool(unsigned int kpSize = 0);
int64_t GetOldestKeyPoolTime() const;
std::set<std::set<CTxDestination>> GetAddressGroupings() const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::map<CTxDestination, Amount> GetAddressBalances() const;
std::set<CTxDestination> GetLabelAddresses(const std::string &label) const;
/**
* Marks all outputs in each one of the destinations dirty, so their cache
* is reset and does not return outdated information.
*/
void MarkDestinationsDirty(const std::set<CTxDestination> &destinations)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool GetNewDestination(const OutputType type, const std::string label,
CTxDestination &dest, std::string &error);
bool GetNewChangeDestination(const OutputType type, CTxDestination &dest,
std::string &error);
isminetype IsMine(const CTxDestination &dest) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CScript &script) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CTxIn &txin) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Returns amount of debit if the input matches the filter, otherwise
* returns 0
*/
Amount GetDebit(const CTxIn &txin, const isminefilter &filter) const;
isminetype IsMine(const CTxOut &txout) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
;
Amount GetCredit(const CTxOut &txout, const isminefilter &filter) const;
bool IsChange(const CTxOut &txout) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsChange(const CScript &script) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
Amount GetChange(const CTxOut &txout) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsMine(const CTransaction &tx) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction &tx) const;
Amount GetDebit(const CTransaction &tx, const isminefilter &filter) const;
/** Returns whether all of the inputs match the filter */
bool IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const;
Amount GetCredit(const CTransaction &tx, const isminefilter &filter) const;
Amount GetChange(const CTransaction &tx) const;
void chainStateFlushed(const CBlockLocator &loc) override;
DBErrors LoadWallet(bool &fFirstRunRet);
DBErrors ZapSelectTx(std::vector<TxId> &txIdsIn,
std::vector<TxId> &txIdsOut)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool SetAddressBook(const CTxDestination &address,
const std::string &strName, const std::string &purpose);
bool DelAddressBook(const CTxDestination &address);
unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! signify that a particular wallet feature is now used. this may change
//! nWalletVersion and nWalletMaxVersion if those are lower
void SetMinVersion(enum WalletFeature, WalletBatch *batch_in = nullptr,
bool fExplicit = false) override;
//! change which version we're allowed to upgrade to (note that this does
//! not immediately imply upgrading to that format)
bool SetMaxVersion(int nVersion);
//! get the current wallet format (the oldest client version guaranteed to
//! understand this wallet)
int GetVersion() const {
LOCK(cs_wallet);
return nWalletVersion;
}
//! Get wallet transactions that conflict with given transaction (spend same
//! outputs)
std::set<TxId> GetConflicts(const TxId &txid) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Check if a given transaction has any of its outputs spent by another
//! transaction in the wallet
bool HasWalletSpend(const TxId &txid) const
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Flush wallet (bitdb flush)
void Flush();
//! Close wallet database
void Close();
/** Wallet is about to be unloaded */
boost::signals2::signal<void()> NotifyUnload;
/**
* Address book entry changed.
* @note called with lock cs_wallet held.
*/
boost::signals2::signal<void(CWallet *wallet, const CTxDestination &address,
const std::string &label, bool isMine,
const std::string &purpose, ChangeType status)>
NotifyAddressBookChanged;
/**
* Wallet transaction added, removed or updated.
* @note called with lock cs_wallet held.
*/
boost::signals2::signal<void(CWallet *wallet, const TxId &txid,
ChangeType status)>
NotifyTransactionChanged;
/** Show progress e.g. for rescan */
boost::signals2::signal<void(const std::string &title, int nProgress)>
ShowProgress;
/** Watch-only address added */
boost::signals2::signal<void(bool fHaveWatchOnly)> NotifyWatchonlyChanged;
/** Keypool has new keys */
boost::signals2::signal<void()> NotifyCanGetAddressesChanged;
/**
* Wallet status (encrypted, locked) changed.
* Note: Called without locks held.
*/
boost::signals2::signal<void(CWallet *wallet)> NotifyStatusChanged;
/** Inquire whether this wallet broadcasts transactions. */
bool GetBroadcastTransactions() const { return fBroadcastTransactions; }
/** Set whether this wallet broadcasts transactions. */
void SetBroadcastTransactions(bool broadcast) {
fBroadcastTransactions = broadcast;
}
/** Return whether transaction can be abandoned */
bool TransactionCanBeAbandoned(const TxId &txid) const;
/**
* Mark a transaction (and it in-wallet descendants) as abandoned so its
* inputs may be respent.
*/
bool AbandonTransaction(const TxId &txid);
/**
* Initializes the wallet, returns a new CWallet instance or a null pointer
* in case of an error.
*/
static std::shared_ptr<CWallet>
- CreateWalletFromFile(interfaces::Chain &chain, const std::string &name,
- bilingual_str &error,
- std::vector<bilingual_str> &warnings,
- uint64_t wallet_creation_flags = 0);
+ Create(interfaces::Chain &chain, const std::string &name,
+ std::unique_ptr<WalletDatabase> database,
+ uint64_t wallet_creation_flags, bilingual_str &error,
+ std::vector<bilingual_str> &warnings);
/**
* Wallet post-init setup
* Gives the wallet a chance to register repetitive tasks and complete
* post-init tasks
*/
void postInitProcess();
bool BackupWallet(const std::string &strDest) const;
/* Returns true if HD is enabled */
bool IsHDEnabled() const;
/**
* Returns true if the wallet can give out new addresses. This means it has
* keys in the keypool or can generate new keys.
*/
bool CanGetAddresses(bool internal = false) const;
/**
* Blocks until the wallet state is up-to-date to /at least/ the current
* chain at the time this function is entered.
* Obviously holding cs_main/cs_wallet when going into this call may cause
* deadlock
*/
void BlockUntilSyncedToCurrentChain() const LOCKS_EXCLUDED(::cs_main)
EXCLUSIVE_LOCKS_REQUIRED(!cs_wallet);
/**
* Set a single wallet flag.
*/
void SetWalletFlag(uint64_t flags);
/**
* Unsets a single wallet flag.
*/
void UnsetWalletFlag(uint64_t flag);
/**
* Check if a certain wallet flag is set.
*/
bool IsWalletFlagSet(uint64_t flag) const override;
/**
* Overwrite all flags by the given uint64_t.
* Returns false if unknown, non-tolerable flags are present.
*/
bool AddWalletFlags(uint64_t flags);
/** Loads the flags into the wallet. (used by LoadWallet) */
bool LoadWalletFlags(uint64_t flags);
/** Determine if we are a legacy wallet */
bool IsLegacy() const;
/**
* Returns a bracketed wallet name for displaying in logs, will return
* [default wallet] if the wallet has no name.
*/
const std::string GetDisplayName() const override {
std::string wallet_name =
GetName().length() == 0 ? "default wallet" : GetName();
return strprintf("[%s]", wallet_name);
};
/**
* Prepends the wallet name in logging output to ease debugging in
* multi-wallet use cases.
*/
template <typename... Params>
void WalletLogPrintf(std::string fmt, Params... parameters) const {
LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...);
};
template <typename... Params>
void WalletLogPrintfToBeContinued(std::string fmt,
Params... parameters) const {
LogPrintfToBeContinued(("%s " + fmt).c_str(), GetDisplayName(),
parameters...);
};
/** Upgrade the wallet */
bool UpgradeWallet(int version, bilingual_str &error,
std::vector<bilingual_str> &warnings);
//! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and
//! m_external_spk_managers
std::set<ScriptPubKeyMan *> GetActiveScriptPubKeyMans() const;
//! Returns all unique ScriptPubKeyMans
std::set<ScriptPubKeyMan *> GetAllScriptPubKeyMans() const;
//! Get the ScriptPubKeyMan for the given OutputType and internal/external
//! chain.
ScriptPubKeyMan *GetScriptPubKeyMan(const OutputType &type,
bool internal) const;
//! Get the ScriptPubKeyMan for a script
ScriptPubKeyMan *GetScriptPubKeyMan(const CScript &script) const;
//! Get the ScriptPubKeyMan by id
ScriptPubKeyMan *GetScriptPubKeyMan(const uint256 &id) const;
//! Get all of the ScriptPubKeyMans for a script given additional
//! information in sigdata (populated by e.g. a psbt)
std::set<ScriptPubKeyMan *>
GetScriptPubKeyMans(const CScript &script, SignatureData &sigdata) const;
//! Get the SigningProvider for a script
std::unique_ptr<SigningProvider>
GetSolvingProvider(const CScript &script) const;
std::unique_ptr<SigningProvider>
GetSolvingProvider(const CScript &script, SignatureData &sigdata) const;
//! Get the LegacyScriptPubKeyMan which is used for all types, internal, and
//! external.
LegacyScriptPubKeyMan *GetLegacyScriptPubKeyMan() const;
LegacyScriptPubKeyMan *GetOrCreateLegacyScriptPubKeyMan();
//! Make a LegacyScriptPubKeyMan and set it for all types, internal, and
//! external.
void SetupLegacyScriptPubKeyMan();
const CKeyingMaterial &GetEncryptionKey() const override;
bool HasEncryptionKeys() const override;
/** Get last block processed height */
int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
AssertLockHeld(cs_wallet);
assert(m_last_block_processed_height >= 0);
return m_last_block_processed_height;
};
BlockHash GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
AssertLockHeld(cs_wallet);
assert(m_last_block_processed_height >= 0);
return m_last_block_processed;
}
/** Set last block processed height, currently only use in unit test */
void SetLastBlockProcessed(int block_height, BlockHash block_hash)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) {
AssertLockHeld(cs_wallet);
m_last_block_processed_height = block_height;
m_last_block_processed = block_hash;
};
//! Connect the signals from ScriptPubKeyMans to the signals in CWallet
void ConnectScriptPubKeyManNotifiers();
//! Instantiate a descriptor ScriptPubKeyMan from the WalletDescriptor and
//! load it
void LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor &desc);
//! Adds the active ScriptPubKeyMan for the specified type and internal.
//! Writes it to the wallet file
//! @param[in] id The unique id for the ScriptPubKeyMan
//! @param[in] type The OutputType this ScriptPubKeyMan provides addresses
//! for
//! @param[in] internal Whether this ScriptPubKeyMan provides change
//! addresses
void AddActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
//! Loads an active ScriptPubKeyMan for the specified type and internal.
//! (used by LoadWallet)
//! @param[in] id The unique id for the ScriptPubKeyMan
//! @param[in] type The OutputType this ScriptPubKeyMan provides addresses
//! for
//! @param[in] internal Whether this ScriptPubKeyMan provides change
//! addresses
void LoadActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal);
//! Create new DescriptorScriptPubKeyMans and add them to the wallet
void SetupDescriptorScriptPubKeyMans() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is
//! already in the wallet
DescriptorScriptPubKeyMan *
GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const;
//! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated
//! output type
ScriptPubKeyMan *
AddWalletDescriptor(WalletDescriptor &desc,
const FlatSigningProvider &signing_provider,
const std::string &label);
};
/**
* Called periodically by the schedule thread. Prompts individual wallets to
* resend their transactions. Actual rebroadcast schedule is managed by the
* wallets themselves.
*/
void MaybeResendWalletTxs();
/** RAII object to check and reserve a wallet rescan */
class WalletRescanReserver {
private:
CWallet &m_wallet;
bool m_could_reserve;
public:
explicit WalletRescanReserver(CWallet &w)
: m_wallet(w), m_could_reserve(false) {}
bool reserve() {
assert(!m_could_reserve);
if (m_wallet.fScanningWallet.exchange(true)) {
return false;
}
m_wallet.m_scanning_start = GetTimeMillis();
m_wallet.m_scanning_progress = 0;
m_could_reserve = true;
return true;
}
bool isReserved() const {
return (m_could_reserve && m_wallet.fScanningWallet);
}
~WalletRescanReserver() {
if (m_could_reserve) {
m_wallet.fScanningWallet = false;
}
}
};
// Calculate the size of the transaction assuming all signatures are max size
// Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
// be IsAllFromMe).
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx,
const CWallet *wallet,
bool use_max_sig = false)
EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx,
const CWallet *wallet,
const std::vector<CTxOut> &txouts,
bool use_max_sig = false);
//! Add wallet name to persistent configuration so it will be loaded on startup.
bool AddWalletSetting(interfaces::Chain &chain, const std::string &wallet_name);
//! Remove wallet name from persistent configuration so it will not be loaded on
//! startup.
bool RemoveWalletSetting(interfaces::Chain &chain,
const std::string &wallet_name);
#endif // BITCOIN_WALLET_WALLET_H

File Metadata

Mime Type
text/x-diff
Expires
Sun, Dec 29, 19:19 (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
4844922
Default Alt Text
(341 KB)

Event Timeline