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 939ea3649..cf4cbe930 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -1,948 +1,933 @@
// 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;
}
-bool IsBDBWalletLoaded(const fs::path &wallet_path) {
- fs::path env_directory;
- std::string database_filename;
- SplitWalletPath(wallet_path, env_directory, database_filename);
-
- LOCK(cs_db);
- auto env = g_dbenvs.find(env_directory.string());
- if (env == g_dbenvs.end()) {
- return false;
- }
-
- auto database = env->second.lock();
- return database && database->IsDatabaseLoaded(database_filename);
-}
-
/**
* @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 ddb24e3a4..df98ba783 100644
--- a/src/wallet/bdb.h
+++ b/src/wallet/bdb.h
@@ -1,259 +1,253 @@
// 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; }
- bool IsDatabaseLoaded(const std::string &db_filename) const {
- return m_databases.find(db_filename) != m_databases.end();
- }
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);
-/** Return whether a BDB wallet database is currently loaded. */
-bool IsBDBWalletLoaded(const fs::path &wallet_path);
-
/** 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) override;
+ bool Verify(bilingual_str &error);
/**
* 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.h b/src/wallet/db.h
index b9ee08965..64797c8d9 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,253 +1,249 @@
// 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;
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
int64_t nLastWalletUpdate;
- /** Verifies the environment and database file */
- virtual bool Verify(bilingual_str &error) = 0;
-
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 {}
- bool Verify(bilingual_str &errorStr) override { return true; }
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 208c94e60..500ece2d9 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -1,148 +1,145 @@
// 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;
- std::vector<bilingual_str> warnings;
- bool verify_success =
- CWallet::Verify(chain, wallet_file, error_string, warnings);
- if (!warnings.empty()) {
- chain.initWarning(Join(warnings, Untranslated("\n")));
- }
- if (!verify_success) {
+ 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) {
bilingual_str error;
std::vector<bilingual_str> warnings;
std::shared_ptr<CWallet> pwallet = CWallet::CreateWalletFromFile(
chain, walletFile, error, warnings);
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/wallet.cpp b/src/wallet/wallet.cpp
index 29678ec7e..4f1f8ad07 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5111 +1,5090 @@
// 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 (!CWallet::Verify(chain, name, error, warnings)) {
+ if (!MakeWalletDatabase(name, options, status, error)) {
error = Untranslated("Wallet file verification failed.") +
Untranslated(" ") + error;
return nullptr;
}
std::shared_ptr<CWallet> wallet =
CWallet::CreateWalletFromFile(chain, name, 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 (!CWallet::Verify(chain, name, error, warnings)) {
+ if (!MakeWalletDatabase(name, options, status, error)) {
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);
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;
}
-bool CWallet::Verify(interfaces::Chain &chain, const std::string &name,
- bilingual_str &error_string,
- std::vector<bilingual_str> &warnings) {
+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.
- LOCK(cs_wallets);
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()));
- return false;
- }
-
- // Make sure that the wallet path doesn't clash with an existing wallet path
- if (IsWalletLoaded(wallet_path)) {
- error_string = Untranslated(strprintf(
- "Error loading wallet %s. Duplicate -wallet filename specified.",
- name));
- return false;
- }
-
- // Keep same database environment instance across Verify/Recover calls
- // below.
- std::unique_ptr<WalletDatabase> database =
- CreateWalletDatabase(wallet_path);
-
- try {
- return database->Verify(error_string);
- } catch (const fs::filesystem_error &e) {
- error_string =
- Untranslated(strprintf("Error loading wallet %s. %s", name,
- fsbridge::get_filesystem_error_message(e)));
- return false;
+ 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();
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);
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 cc724bdb3..cd3bd5c3c 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,1639 +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);
- //! Verify wallet naming and perform salvage on the wallet if required
- static bool Verify(interfaces::Chain &chain, const std::string &name,
- bilingual_str &error_string,
- std::vector<bilingual_str> &warnings);
-
/**
* 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);
/**
* 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
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index d4f64da4a..81c6ceaef 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,1183 +1,1179 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/walletdb.h>
#include <chainparams.h>
#include <fs.h>
#include <key_io.h>
#include <protocol.h>
#include <serialize.h>
#include <sync.h>
#include <util/bip32.h>
#include <util/system.h>
#include <util/time.h>
#include <util/translation.h>
#include <wallet/bdb.h>
#include <wallet/wallet.h>
#include <atomic>
namespace DBKeys {
const std::string ACENTRY{"acentry"};
const std::string ACTIVEEXTERNALSPK{"activeexternalspk"};
const std::string ACTIVEINTERNALSPK{"activeinternalspk"};
const std::string BESTBLOCK_NOMERKLE{"bestblock_nomerkle"};
const std::string BESTBLOCK{"bestblock"};
const std::string CRYPTED_KEY{"ckey"};
const std::string CSCRIPT{"cscript"};
const std::string DEFAULTKEY{"defaultkey"};
const std::string DESTDATA{"destdata"};
const std::string FLAGS{"flags"};
const std::string HDCHAIN{"hdchain"};
const std::string KEYMETA{"keymeta"};
const std::string KEY{"key"};
const std::string MASTER_KEY{"mkey"};
const std::string MINVERSION{"minversion"};
const std::string NAME{"name"};
const std::string OLD_KEY{"wkey"};
const std::string ORDERPOSNEXT{"orderposnext"};
const std::string POOL{"pool"};
const std::string PURPOSE{"purpose"};
const std::string SETTINGS{"settings"};
const std::string TX{"tx"};
const std::string VERSION{"version"};
const std::string WALLETDESCRIPTOR{"walletdescriptor"};
const std::string WALLETDESCRIPTORCACHE{"walletdescriptorcache"};
const std::string WALLETDESCRIPTORCKEY{"walletdescriptorckey"};
const std::string WALLETDESCRIPTORKEY{"walletdescriptorkey"};
const std::string WATCHMETA{"watchmeta"};
const std::string WATCHS{"watchs"};
} // namespace DBKeys
//
// WalletBatch
//
bool WalletBatch::WriteName(const CTxDestination &address,
const std::string &strName) {
if (!IsValidDestination(address)) {
return false;
}
return WriteIC(
std::make_pair(DBKeys::NAME, EncodeLegacyAddr(address, Params())),
strName);
}
bool WalletBatch::EraseName(const CTxDestination &address) {
// This should only be used for sending addresses, never for receiving
// addresses, receiving addresses must always have an address book entry if
// they're not change return.
if (!IsValidDestination(address)) {
return false;
}
return EraseIC(
std::make_pair(DBKeys::NAME, EncodeLegacyAddr(address, Params())));
}
bool WalletBatch::WritePurpose(const CTxDestination &address,
const std::string &strPurpose) {
if (!IsValidDestination(address)) {
return false;
}
return WriteIC(
std::make_pair(DBKeys::PURPOSE, EncodeLegacyAddr(address, Params())),
strPurpose);
}
bool WalletBatch::ErasePurpose(const CTxDestination &address) {
if (!IsValidDestination(address)) {
return false;
}
return EraseIC(
std::make_pair(DBKeys::PURPOSE, EncodeLegacyAddr(address, Params())));
}
bool WalletBatch::WriteTx(const CWalletTx &wtx) {
return WriteIC(std::make_pair(DBKeys::TX, wtx.GetId()), wtx);
}
bool WalletBatch::EraseTx(uint256 hash) {
return EraseIC(std::make_pair(DBKeys::TX, hash));
}
bool WalletBatch::WriteKeyMetadata(const CKeyMetadata &meta,
const CPubKey &pubkey,
const bool overwrite) {
return WriteIC(std::make_pair(DBKeys::KEYMETA, pubkey), meta, overwrite);
}
bool WalletBatch::WriteKey(const CPubKey &vchPubKey, const CPrivKey &vchPrivKey,
const CKeyMetadata &keyMeta) {
if (!WriteKeyMetadata(keyMeta, vchPubKey, false)) {
return false;
}
// hash pubkey/privkey to accelerate wallet load
std::vector<uint8_t> vchKey;
vchKey.reserve(vchPubKey.size() + vchPrivKey.size());
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end());
return WriteIC(std::make_pair(DBKeys::KEY, vchPubKey),
std::make_pair(vchPrivKey, Hash(vchKey)), false);
}
bool WalletBatch::WriteCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret,
const CKeyMetadata &keyMeta) {
if (!WriteKeyMetadata(keyMeta, vchPubKey, true)) {
return false;
}
// Compute a checksum of the encrypted key
uint256 checksum = Hash(vchCryptedSecret);
const auto key = std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey);
if (!WriteIC(key, std::make_pair(vchCryptedSecret, checksum), false)) {
// It may already exist, so try writing just the checksum
std::vector<uint8_t> val;
if (!m_batch->Read(key, val)) {
return false;
}
if (!WriteIC(key, std::make_pair(val, checksum), true)) {
return false;
}
}
EraseIC(std::make_pair(DBKeys::KEY, vchPubKey));
return true;
}
bool WalletBatch::WriteMasterKey(unsigned int nID,
const CMasterKey &kMasterKey) {
return WriteIC(std::make_pair(DBKeys::MASTER_KEY, nID), kMasterKey, true);
}
bool WalletBatch::WriteCScript(const uint160 &hash,
const CScript &redeemScript) {
return WriteIC(std::make_pair(DBKeys::CSCRIPT, hash), redeemScript, false);
}
bool WalletBatch::WriteWatchOnly(const CScript &dest,
const CKeyMetadata &keyMeta) {
if (!WriteIC(std::make_pair(DBKeys::WATCHMETA, dest), keyMeta)) {
return false;
}
return WriteIC(std::make_pair(DBKeys::WATCHS, dest), '1');
}
bool WalletBatch::EraseWatchOnly(const CScript &dest) {
if (!EraseIC(std::make_pair(DBKeys::WATCHMETA, dest))) {
return false;
}
return EraseIC(std::make_pair(DBKeys::WATCHS, dest));
}
bool WalletBatch::WriteBestBlock(const CBlockLocator &locator) {
// Write empty block locator so versions that require a merkle branch
// automatically rescan
WriteIC(DBKeys::BESTBLOCK, CBlockLocator());
return WriteIC(DBKeys::BESTBLOCK_NOMERKLE, locator);
}
bool WalletBatch::ReadBestBlock(CBlockLocator &locator) {
if (m_batch->Read(DBKeys::BESTBLOCK, locator) && !locator.vHave.empty()) {
return true;
}
return m_batch->Read(DBKeys::BESTBLOCK_NOMERKLE, locator);
}
bool WalletBatch::WriteOrderPosNext(int64_t nOrderPosNext) {
return WriteIC(DBKeys::ORDERPOSNEXT, nOrderPosNext);
}
bool WalletBatch::ReadPool(int64_t nPool, CKeyPool &keypool) {
return m_batch->Read(std::make_pair(DBKeys::POOL, nPool), keypool);
}
bool WalletBatch::WritePool(int64_t nPool, const CKeyPool &keypool) {
return WriteIC(std::make_pair(DBKeys::POOL, nPool), keypool);
}
bool WalletBatch::ErasePool(int64_t nPool) {
return EraseIC(std::make_pair(DBKeys::POOL, nPool));
}
bool WalletBatch::WriteMinVersion(int nVersion) {
return WriteIC(DBKeys::MINVERSION, nVersion);
}
bool WalletBatch::WriteActiveScriptPubKeyMan(uint8_t type, const uint256 &id,
bool internal) {
std::string key =
internal ? DBKeys::ACTIVEINTERNALSPK : DBKeys::ACTIVEEXTERNALSPK;
return WriteIC(make_pair(key, type), id);
}
bool WalletBatch::WriteDescriptorKey(const uint256 &desc_id,
const CPubKey &pubkey,
const CPrivKey &privkey) {
// hash pubkey/privkey to accelerate wallet load
std::vector<uint8_t> key;
key.reserve(pubkey.size() + privkey.size());
key.insert(key.end(), pubkey.begin(), pubkey.end());
key.insert(key.end(), privkey.begin(), privkey.end());
return WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY,
std::make_pair(desc_id, pubkey)),
std::make_pair(privkey, Hash(key)), false);
}
bool WalletBatch::WriteCryptedDescriptorKey(
const uint256 &desc_id, const CPubKey &pubkey,
const std::vector<uint8_t> &secret) {
if (!WriteIC(std::make_pair(DBKeys::WALLETDESCRIPTORCKEY,
std::make_pair(desc_id, pubkey)),
secret, false)) {
return false;
}
EraseIC(std::make_pair(DBKeys::WALLETDESCRIPTORKEY,
std::make_pair(desc_id, pubkey)));
return true;
}
bool WalletBatch::WriteDescriptor(const uint256 &desc_id,
const WalletDescriptor &descriptor) {
return WriteIC(make_pair(DBKeys::WALLETDESCRIPTOR, desc_id), descriptor);
}
bool WalletBatch::WriteDescriptorDerivedCache(const CExtPubKey &xpub,
const uint256 &desc_id,
uint32_t key_exp_index,
uint32_t der_index) {
std::vector<uint8_t> ser_xpub(BIP32_EXTKEY_SIZE);
xpub.Encode(ser_xpub.data());
return WriteIC(
std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id),
std::make_pair(key_exp_index, der_index)),
ser_xpub);
}
bool WalletBatch::WriteDescriptorParentCache(const CExtPubKey &xpub,
const uint256 &desc_id,
uint32_t key_exp_index) {
std::vector<uint8_t> ser_xpub(BIP32_EXTKEY_SIZE);
xpub.Encode(ser_xpub.data());
return WriteIC(
std::make_pair(std::make_pair(DBKeys::WALLETDESCRIPTORCACHE, desc_id),
key_exp_index),
ser_xpub);
}
class CWalletScanState {
public:
unsigned int nKeys{0};
unsigned int nCKeys{0};
unsigned int nWatchKeys{0};
unsigned int nKeyMeta{0};
unsigned int m_unknown_records{0};
bool fIsEncrypted{false};
bool fAnyUnordered{false};
std::vector<TxId> vWalletUpgrade;
std::map<OutputType, uint256> m_active_external_spks;
std::map<OutputType, uint256> m_active_internal_spks;
std::map<uint256, DescriptorCache> m_descriptor_caches;
std::map<std::pair<uint256, CKeyID>, CKey> m_descriptor_keys;
std::map<std::pair<uint256, CKeyID>,
std::pair<CPubKey, std::vector<uint8_t>>>
m_descriptor_crypt_keys;
std::map<uint160, CHDChain> m_hd_chains;
CWalletScanState() {}
};
static bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey,
CDataStream &ssValue, CWalletScanState &wss,
std::string &strType, std::string &strErr,
const KeyFilterFn &filter_fn = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
try {
// Unserialize
// Taking advantage of the fact that pair serialization is just the two
// items serialized one after the other.
ssKey >> strType;
// If we have a filter, check if this matches the filter
if (filter_fn && !filter_fn(strType)) {
return true;
}
if (strType == DBKeys::NAME) {
std::string strAddress;
ssKey >> strAddress;
std::string label;
ssValue >> label;
pwallet
->m_address_book[DecodeDestination(strAddress,
pwallet->GetChainParams())]
.SetLabel(label);
} else if (strType == DBKeys::PURPOSE) {
std::string strAddress;
ssKey >> strAddress;
ssValue >> pwallet
->m_address_book[DecodeDestination(
strAddress, pwallet->GetChainParams())]
.purpose;
} else if (strType == DBKeys::TX) {
TxId txid;
ssKey >> txid;
// LoadToWallet call below creates a new CWalletTx that fill_wtx
// callback fills with transaction metadata.
auto fill_wtx = [&](CWalletTx &wtx, bool new_tx) {
assert(new_tx);
ssValue >> wtx;
if (wtx.GetId() != txid) {
return false;
}
// Undo serialize changes in 31600
if (31404 <= wtx.fTimeReceivedIsTxTime &&
wtx.fTimeReceivedIsTxTime <= 31703) {
if (!ssValue.empty()) {
char fTmp;
char fUnused;
std::string unused_string;
ssValue >> fTmp >> fUnused >> unused_string;
strErr = strprintf(
"LoadWallet() upgrading tx ver=%d %d %s",
wtx.fTimeReceivedIsTxTime, fTmp, txid.ToString());
wtx.fTimeReceivedIsTxTime = fTmp;
} else {
strErr = strprintf(
"LoadWallet() repairing tx ver=%d %s",
wtx.fTimeReceivedIsTxTime, txid.ToString());
wtx.fTimeReceivedIsTxTime = 0;
}
wss.vWalletUpgrade.push_back(txid);
}
if (wtx.nOrderPos == -1) {
wss.fAnyUnordered = true;
}
return true;
};
if (!pwallet->LoadToWallet(txid, fill_wtx)) {
return false;
}
} else if (strType == DBKeys::WATCHS) {
wss.nWatchKeys++;
CScript script;
ssKey >> script;
char fYes;
ssValue >> fYes;
if (fYes == '1') {
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(
script);
}
} else if (strType == DBKeys::KEY) {
CPubKey vchPubKey;
ssKey >> vchPubKey;
if (!vchPubKey.IsValid()) {
strErr = "Error reading wallet database: CPubKey corrupt";
return false;
}
CKey key;
CPrivKey pkey;
uint256 hash;
wss.nKeys++;
ssValue >> pkey;
// Old wallets store keys as DBKeys::KEY [pubkey] => [privkey] ...
// which was slow for wallets with lots of keys, because the public
// key is re-derived from the private key using EC operations as a
// checksum. Newer wallets store keys as DBKeys::KEY [pubkey] =>
// [privkey][hash(pubkey,privkey)], which is much faster while
// remaining backwards-compatible.
try {
ssValue >> hash;
} catch (...) {
}
bool fSkipCheck = false;
if (!hash.IsNull()) {
// hash pubkey/privkey to accelerate wallet load
std::vector<uint8_t> vchKey;
vchKey.reserve(vchPubKey.size() + pkey.size());
vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end());
vchKey.insert(vchKey.end(), pkey.begin(), pkey.end());
if (Hash(vchKey) != hash) {
strErr = "Error reading wallet database: CPubKey/CPrivKey "
"corrupt";
return false;
}
fSkipCheck = true;
}
if (!key.Load(pkey, vchPubKey, fSkipCheck)) {
strErr = "Error reading wallet database: CPrivKey corrupt";
return false;
}
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKey(
key, vchPubKey)) {
strErr = "Error reading wallet database: "
"LegacyScriptPubKeyMan::LoadKey failed";
return false;
}
} else if (strType == DBKeys::MASTER_KEY) {
// Master encryption key is loaded into only the wallet and not any
// of the ScriptPubKeyMans.
unsigned int nID;
ssKey >> nID;
CMasterKey kMasterKey;
ssValue >> kMasterKey;
if (pwallet->mapMasterKeys.count(nID) != 0) {
strErr = strprintf(
"Error reading wallet database: duplicate CMasterKey id %u",
nID);
return false;
}
pwallet->mapMasterKeys[nID] = kMasterKey;
if (pwallet->nMasterKeyMaxID < nID) {
pwallet->nMasterKeyMaxID = nID;
}
} else if (strType == DBKeys::CRYPTED_KEY) {
CPubKey vchPubKey;
ssKey >> vchPubKey;
if (!vchPubKey.IsValid()) {
strErr = "Error reading wallet database: CPubKey corrupt";
return false;
}
std::vector<uint8_t> vchPrivKey;
ssValue >> vchPrivKey;
// Get the checksum and check it
bool checksum_valid = false;
if (!ssValue.eof()) {
uint256 checksum;
ssValue >> checksum;
if ((checksum_valid = Hash(vchPrivKey) != checksum)) {
strErr =
"Error reading wallet database: Crypted key corrupt";
return false;
}
}
wss.nCKeys++;
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(
vchPubKey, vchPrivKey, checksum_valid)) {
strErr = "Error reading wallet database: "
"LegacyScriptPubKeyMan::LoadCryptedKey failed";
return false;
}
wss.fIsEncrypted = true;
} else if (strType == DBKeys::KEYMETA) {
CPubKey vchPubKey;
ssKey >> vchPubKey;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyMetadata(
vchPubKey.GetID(), keyMeta);
// Extract some CHDChain info from this metadata if it has any
if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA &&
!keyMeta.hd_seed_id.IsNull() && keyMeta.hdKeypath.size() > 0) {
// Get the path from the key origin or from the path string
// Not applicable when path is "s" or "m" as those indicate a
// seed.
// See https://reviews.bitcoinabc.org/D4175
bool internal = false;
uint32_t index = 0;
if (keyMeta.hdKeypath != "s" && keyMeta.hdKeypath != "m") {
std::vector<uint32_t> path;
if (keyMeta.has_key_origin) {
// We have a key origin, so pull it from its path vector
path = keyMeta.key_origin.path;
} else {
// No key origin, have to parse the string
if (!ParseHDKeypath(keyMeta.hdKeypath, path)) {
strErr = "Error reading wallet database: keymeta "
"with invalid HD keypath";
return false;
}
}
// Extract the index and internal from the path
// Path string is m/0'/k'/i'
// Path vector is [0', k', i'] (but as ints OR'd with the
// hardened bit k == 0 for external, 1 for internal. i is
// the index
if (path.size() != 3) {
strErr = "Error reading wallet database: keymeta found "
"with unexpected path";
return false;
}
if (path[0] != 0x80000000) {
strErr = strprintf(
"Unexpected path index of 0x%08x (expected "
"0x80000000) for the element at index 0",
path[0]);
return false;
}
if (path[1] != 0x80000000 && path[1] != (1 | 0x80000000)) {
strErr =
strprintf("Unexpected path index of 0x%08x "
"(expected 0x80000000 or 0x80000001) for "
"the element at index 1",
path[1]);
return false;
}
if ((path[2] & 0x80000000) == 0) {
strErr = strprintf(
"Unexpected path index of 0x%08x (expected to be "
"greater than or equal to 0x80000000)",
path[2]);
return false;
}
internal = path[1] == (1 | 0x80000000);
index = path[2] & ~0x80000000;
}
// Insert a new CHDChain, or get the one that already exists
auto ins =
wss.m_hd_chains.emplace(keyMeta.hd_seed_id, CHDChain());
CHDChain &chain = ins.first->second;
if (ins.second) {
// For new chains, we want to default to VERSION_HD_BASE
// until we see an internal
chain.nVersion = CHDChain::VERSION_HD_BASE;
chain.seed_id = keyMeta.hd_seed_id;
}
if (internal) {
chain.nVersion = CHDChain::VERSION_HD_CHAIN_SPLIT;
chain.nInternalChainCounter =
std::max(chain.nInternalChainCounter, index);
} else {
chain.nExternalChainCounter =
std::max(chain.nExternalChainCounter, index);
}
}
} else if (strType == DBKeys::WATCHMETA) {
CScript script;
ssKey >> script;
CKeyMetadata keyMeta;
ssValue >> keyMeta;
wss.nKeyMeta++;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadScriptMetadata(
CScriptID(script), keyMeta);
} else if (strType == DBKeys::DEFAULTKEY) {
// We don't want or need the default key, but if there is one set,
// we want to make sure that it is valid so that we can detect
// corruption
CPubKey vchPubKey;
ssValue >> vchPubKey;
if (!vchPubKey.IsValid()) {
strErr = "Error reading wallet database: Default Key corrupt";
return false;
}
} else if (strType == DBKeys::POOL) {
int64_t nIndex;
ssKey >> nIndex;
CKeyPool keypool;
ssValue >> keypool;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadKeyPool(nIndex,
keypool);
} else if (strType == DBKeys::CSCRIPT) {
uint160 hash;
ssKey >> hash;
CScript script;
ssValue >> script;
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCScript(
script)) {
strErr = "Error reading wallet database: "
"LegacyScriptPubKeyMan::LoadCScript failed";
return false;
}
} else if (strType == DBKeys::ORDERPOSNEXT) {
ssValue >> pwallet->nOrderPosNext;
} else if (strType == DBKeys::DESTDATA) {
std::string strAddress, strKey, strValue;
ssKey >> strAddress;
ssKey >> strKey;
ssValue >> strValue;
pwallet->LoadDestData(
DecodeDestination(strAddress, pwallet->GetChainParams()),
strKey, strValue);
} else if (strType == DBKeys::HDCHAIN) {
CHDChain chain;
ssValue >> chain;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadHDChain(chain);
} else if (strType == DBKeys::FLAGS) {
uint64_t flags;
ssValue >> flags;
if (!pwallet->LoadWalletFlags(flags)) {
strErr = "Error reading wallet database: Unknown non-tolerable "
"wallet flags found";
return false;
}
} else if (strType == DBKeys::OLD_KEY) {
strErr = "Found unsupported 'wkey' record, try loading with "
"version 0.20";
return false;
} else if (strType == DBKeys::ACTIVEEXTERNALSPK ||
strType == DBKeys::ACTIVEINTERNALSPK) {
uint8_t type;
ssKey >> type;
uint256 id;
ssValue >> id;
bool internal = strType == DBKeys::ACTIVEINTERNALSPK;
auto &spk_mans = internal ? wss.m_active_internal_spks
: wss.m_active_external_spks;
if (spk_mans.count(static_cast<OutputType>(type)) > 0) {
strErr =
"Multiple ScriptPubKeyMans specified for a single type";
return false;
}
spk_mans[static_cast<OutputType>(type)] = id;
} else if (strType == DBKeys::WALLETDESCRIPTOR) {
uint256 id;
ssKey >> id;
WalletDescriptor desc;
ssValue >> desc;
if (wss.m_descriptor_caches.count(id) == 0) {
wss.m_descriptor_caches[id] = DescriptorCache();
}
pwallet->LoadDescriptorScriptPubKeyMan(id, desc);
} else if (strType == DBKeys::WALLETDESCRIPTORCACHE) {
bool parent = true;
uint256 desc_id;
uint32_t key_exp_index;
uint32_t der_index;
ssKey >> desc_id;
ssKey >> key_exp_index;
// if the der_index exists, it's a derived xpub
try {
ssKey >> der_index;
parent = false;
} catch (...) {
}
std::vector<uint8_t> ser_xpub(BIP32_EXTKEY_SIZE);
ssValue >> ser_xpub;
CExtPubKey xpub;
xpub.Decode(ser_xpub.data());
if (parent) {
wss.m_descriptor_caches[desc_id].CacheParentExtPubKey(
key_exp_index, xpub);
} else {
wss.m_descriptor_caches[desc_id].CacheDerivedExtPubKey(
key_exp_index, der_index, xpub);
}
} else if (strType == DBKeys::WALLETDESCRIPTORKEY) {
uint256 desc_id;
CPubKey pubkey;
ssKey >> desc_id;
ssKey >> pubkey;
if (!pubkey.IsValid()) {
strErr = "Error reading wallet database: CPubKey corrupt";
return false;
}
CKey key;
CPrivKey pkey;
uint256 hash;
wss.nKeys++;
ssValue >> pkey;
ssValue >> hash;
// hash pubkey/privkey to accelerate wallet load
std::vector<uint8_t> to_hash;
to_hash.reserve(pubkey.size() + pkey.size());
to_hash.insert(to_hash.end(), pubkey.begin(), pubkey.end());
to_hash.insert(to_hash.end(), pkey.begin(), pkey.end());
if (Hash(to_hash) != hash) {
strErr =
"Error reading wallet database: CPubKey/CPrivKey corrupt";
return false;
}
if (!key.Load(pkey, pubkey, true)) {
strErr = "Error reading wallet database: CPrivKey corrupt";
return false;
}
wss.m_descriptor_keys.insert(
std::make_pair(std::make_pair(desc_id, pubkey.GetID()), key));
} else if (strType == DBKeys::WALLETDESCRIPTORCKEY) {
uint256 desc_id;
CPubKey pubkey;
ssKey >> desc_id;
ssKey >> pubkey;
if (!pubkey.IsValid()) {
strErr = "Error reading wallet database: CPubKey corrupt";
return false;
}
std::vector<uint8_t> privkey;
ssValue >> privkey;
wss.nCKeys++;
wss.m_descriptor_crypt_keys.insert(
std::make_pair(std::make_pair(desc_id, pubkey.GetID()),
std::make_pair(pubkey, privkey)));
wss.fIsEncrypted = true;
} else if (strType != DBKeys::BESTBLOCK &&
strType != DBKeys::BESTBLOCK_NOMERKLE &&
strType != DBKeys::MINVERSION &&
strType != DBKeys::ACENTRY && strType != DBKeys::VERSION &&
strType != DBKeys::SETTINGS) {
wss.m_unknown_records++;
}
} catch (const std::exception &e) {
if (strErr.empty()) {
strErr = e.what();
}
return false;
} catch (...) {
if (strErr.empty()) {
strErr = "Caught unknown exception in ReadKeyValue";
}
return false;
}
return true;
}
bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey, CDataStream &ssValue,
std::string &strType, std::string &strErr,
const KeyFilterFn &filter_fn) {
CWalletScanState dummy_wss;
LOCK(pwallet->cs_wallet);
return ReadKeyValue(pwallet, ssKey, ssValue, dummy_wss, strType, strErr,
filter_fn);
}
bool WalletBatch::IsKeyType(const std::string &strType) {
return (strType == DBKeys::KEY || strType == DBKeys::MASTER_KEY ||
strType == DBKeys::CRYPTED_KEY);
}
DBErrors WalletBatch::LoadWallet(CWallet *pwallet) {
CWalletScanState wss;
bool fNoncriticalErrors = false;
DBErrors result = DBErrors::LOAD_OK;
LOCK(pwallet->cs_wallet);
try {
int nMinVersion = 0;
if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
if (nMinVersion > FEATURE_LATEST) {
return DBErrors::TOO_NEW;
}
pwallet->LoadMinVersion(nMinVersion);
}
// Get cursor
if (!m_batch->StartCursor()) {
pwallet->WalletLogPrintf("Error getting wallet database cursor\n");
return DBErrors::CORRUPT;
}
while (true) {
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
bool complete;
bool ret = m_batch->ReadAtCursor(ssKey, ssValue, complete);
if (complete) {
break;
}
if (!ret) {
m_batch->CloseCursor();
pwallet->WalletLogPrintf(
"Error reading next record from wallet database\n");
return DBErrors::CORRUPT;
}
// Try to be tolerant of single corrupt records:
std::string strType, strErr;
if (!ReadKeyValue(pwallet, ssKey, ssValue, wss, strType, strErr)) {
// losing keys is considered a catastrophic error, anything else
// we assume the user can live with:
if (IsKeyType(strType) || strType == DBKeys::DEFAULTKEY) {
result = DBErrors::CORRUPT;
} else if (strType == DBKeys::FLAGS) {
// Reading the wallet flags can only fail if unknown flags
// are present.
result = DBErrors::TOO_NEW;
} else {
// Leave other errors alone, if we try to fix them we might
// make things worse. But do warn the user there is
// something wrong.
fNoncriticalErrors = true;
if (strType == DBKeys::TX) {
// Rescan if there is a bad transaction record:
gArgs.SoftSetBoolArg("-rescan", true);
}
}
}
if (!strErr.empty()) {
pwallet->WalletLogPrintf("%s\n", strErr);
}
}
} catch (...) {
result = DBErrors::CORRUPT;
}
m_batch->CloseCursor();
// Set the active ScriptPubKeyMans
for (auto spk_man_pair : wss.m_active_external_spks) {
pwallet->LoadActiveScriptPubKeyMan(
spk_man_pair.second, spk_man_pair.first, /* internal */ false);
}
for (auto spk_man_pair : wss.m_active_internal_spks) {
pwallet->LoadActiveScriptPubKeyMan(
spk_man_pair.second, spk_man_pair.first, /* internal */ true);
}
// Set the descriptor caches
for (auto desc_cache_pair : wss.m_descriptor_caches) {
auto spk_man = pwallet->GetScriptPubKeyMan(desc_cache_pair.first);
assert(spk_man);
((DescriptorScriptPubKeyMan *)spk_man)
->SetCache(desc_cache_pair.second);
}
// Set the descriptor keys
for (auto desc_key_pair : wss.m_descriptor_keys) {
auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);
((DescriptorScriptPubKeyMan *)spk_man)
->AddKey(desc_key_pair.first.second, desc_key_pair.second);
}
for (auto desc_key_pair : wss.m_descriptor_crypt_keys) {
auto spk_man = pwallet->GetScriptPubKeyMan(desc_key_pair.first.first);
((DescriptorScriptPubKeyMan *)spk_man)
->AddCryptedKey(desc_key_pair.first.second,
desc_key_pair.second.first,
desc_key_pair.second.second);
}
if (fNoncriticalErrors && result == DBErrors::LOAD_OK) {
result = DBErrors::NONCRITICAL_ERROR;
}
// Any wallet corruption at all: skip any rewriting or upgrading, we don't
// want to make it worse.
if (result != DBErrors::LOAD_OK) {
return result;
}
// Last client version to open this wallet, was previously the file version
// number
int last_client = CLIENT_VERSION;
m_batch->Read(DBKeys::VERSION, last_client);
int wallet_version = pwallet->GetVersion();
pwallet->WalletLogPrintf("Wallet File Version = %d\n",
wallet_version > 0 ? wallet_version : last_client);
pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ "
"metadata, %u total. Unknown wallet records: %u\n",
wss.nKeys, wss.nCKeys, wss.nKeyMeta,
wss.nKeys + wss.nCKeys, wss.m_unknown_records);
// nTimeFirstKey is only reliable if all keys have metadata
if (pwallet->IsLegacy() &&
(wss.nKeys + wss.nCKeys + wss.nWatchKeys) != wss.nKeyMeta) {
auto spk_man = pwallet->GetOrCreateLegacyScriptPubKeyMan();
if (spk_man) {
LOCK(spk_man->cs_KeyStore);
spk_man->UpdateTimeFirstKey(1);
}
}
for (const TxId &txid : wss.vWalletUpgrade) {
WriteTx(pwallet->mapWallet.at(txid));
}
// Rewrite encrypted wallets of versions 0.4.0 and 0.5.0rc:
if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000)) {
return DBErrors::NEED_REWRITE;
}
if (last_client < CLIENT_VERSION) {
// Update
m_batch->Write(DBKeys::VERSION, CLIENT_VERSION);
}
if (wss.fAnyUnordered) {
result = pwallet->ReorderTransactions();
}
// Upgrade all of the wallet keymetadata to have the hd master key id
// This operation is not atomic, but if it fails, updated entries are still
// backwards compatible with older software
try {
pwallet->UpgradeKeyMetadata();
} catch (...) {
result = DBErrors::CORRUPT;
}
// Set the inactive chain
if (wss.m_hd_chains.size() > 0) {
LegacyScriptPubKeyMan *legacy_spkm =
pwallet->GetLegacyScriptPubKeyMan();
if (!legacy_spkm) {
pwallet->WalletLogPrintf(
"Inactive HD Chains found but no Legacy ScriptPubKeyMan\n");
return DBErrors::CORRUPT;
}
for (const auto &chain_pair : wss.m_hd_chains) {
if (chain_pair.first !=
pwallet->GetLegacyScriptPubKeyMan()->GetHDChain().seed_id) {
pwallet->GetLegacyScriptPubKeyMan()->AddInactiveHDChain(
chain_pair.second);
}
}
}
return result;
}
DBErrors WalletBatch::FindWalletTx(std::vector<TxId> &txIds,
std::list<CWalletTx> &vWtx) {
DBErrors result = DBErrors::LOAD_OK;
try {
int nMinVersion = 0;
if (m_batch->Read(DBKeys::MINVERSION, nMinVersion)) {
if (nMinVersion > FEATURE_LATEST) {
return DBErrors::TOO_NEW;
}
}
// Get cursor
if (!m_batch->StartCursor()) {
LogPrintf("Error getting wallet database cursor\n");
return DBErrors::CORRUPT;
}
while (true) {
// Read next record
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
bool complete;
bool ret = m_batch->ReadAtCursor(ssKey, ssValue, complete);
if (complete) {
break;
}
if (!ret) {
m_batch->CloseCursor();
LogPrintf("Error reading next record from wallet database\n");
return DBErrors::CORRUPT;
}
std::string strType;
ssKey >> strType;
if (strType == DBKeys::TX) {
TxId txid;
ssKey >> txid;
txIds.push_back(txid);
vWtx.emplace_back(nullptr /* wallet */, nullptr /* tx */);
ssValue >> vWtx.back();
}
}
} catch (...) {
result = DBErrors::CORRUPT;
}
m_batch->CloseCursor();
return result;
}
DBErrors WalletBatch::ZapSelectTx(std::vector<TxId> &txIdsIn,
std::vector<TxId> &txIdsOut) {
// Build list of wallet TXs and hashes.
std::vector<TxId> txIds;
std::list<CWalletTx> vWtx;
DBErrors err = FindWalletTx(txIds, vWtx);
if (err != DBErrors::LOAD_OK) {
return err;
}
std::sort(txIds.begin(), txIds.end());
std::sort(txIdsIn.begin(), txIdsIn.end());
// Erase each matching wallet TX.
bool delerror = false;
std::vector<TxId>::iterator it = txIdsIn.begin();
for (const TxId &txid : txIds) {
while (it < txIdsIn.end() && (*it) < txid) {
it++;
}
if (it == txIdsIn.end()) {
break;
}
if ((*it) == txid) {
if (!EraseTx(txid)) {
LogPrint(BCLog::WALLETDB,
"Transaction was found for deletion but returned "
"database error: %s\n",
txid.GetHex());
delerror = true;
}
txIdsOut.push_back(txid);
}
}
if (delerror) {
return DBErrors::CORRUPT;
}
return DBErrors::LOAD_OK;
}
void MaybeCompactWalletDB() {
static std::atomic<bool> fOneThread;
if (fOneThread.exchange(true)) {
return;
}
for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) {
WalletDatabase &dbh = pwallet->GetDBHandle();
unsigned int nUpdateCounter = dbh.nUpdateCounter;
if (dbh.nLastSeen != nUpdateCounter) {
dbh.nLastSeen = nUpdateCounter;
dbh.nLastWalletUpdate = GetTime();
}
if (dbh.nLastFlushed != nUpdateCounter &&
GetTime() - dbh.nLastWalletUpdate >= 2) {
if (dbh.PeriodicFlush()) {
dbh.nLastFlushed = nUpdateCounter;
}
}
}
fOneThread = false;
}
bool WalletBatch::WriteDestData(const CTxDestination &address,
const std::string &key,
const std::string &value) {
if (!IsValidDestination(address)) {
return false;
}
return WriteIC(
std::make_pair(
DBKeys::DESTDATA,
std::make_pair(EncodeLegacyAddr(address, Params()), key)),
value);
}
bool WalletBatch::EraseDestData(const CTxDestination &address,
const std::string &key) {
if (!IsValidDestination(address)) {
return false;
}
return EraseIC(std::make_pair(
DBKeys::DESTDATA,
std::make_pair(EncodeLegacyAddr(address, Params()), key)));
}
bool WalletBatch::WriteHDChain(const CHDChain &chain) {
return WriteIC(DBKeys::HDCHAIN, chain);
}
bool WalletBatch::WriteWalletFlags(const uint64_t flags) {
return WriteIC(DBKeys::FLAGS, flags);
}
bool WalletBatch::TxnBegin() {
return m_batch->TxnBegin();
}
bool WalletBatch::TxnCommit() {
return m_batch->TxnCommit();
}
bool WalletBatch::TxnAbort() {
return m_batch->TxnAbort();
}
std::unique_ptr<WalletDatabase> MakeDatabase(const fs::path &path,
const DatabaseOptions &options,
DatabaseStatus &status,
bilingual_str &error) {
bool exists;
try {
exists = fs::symlink_status(path).type() != fs::file_not_found;
} catch (const fs::filesystem_error &e) {
error = Untranslated(
strprintf("Failed to access database path '%s': %s", path.string(),
fsbridge::get_filesystem_error_message(e)));
status = DatabaseStatus::FAILED_BAD_PATH;
return nullptr;
}
std::optional<DatabaseFormat> format;
if (exists) {
if (ExistsBerkeleyDatabase(path)) {
format = DatabaseFormat::BERKELEY;
}
} else if (options.require_existing) {
error = Untranslated(
strprintf("Failed to load database path '%s'. Path does not exist.",
path.string()));
status = DatabaseStatus::FAILED_NOT_FOUND;
return nullptr;
}
if (!format && options.require_existing) {
error = Untranslated(strprintf("Failed to load database path '%s'. "
"Data is not in recognized format.",
path.string()));
status = DatabaseStatus::FAILED_BAD_FORMAT;
return nullptr;
}
if (format && options.require_create) {
error = Untranslated(strprintf(
"Failed to create database path '%s'. Database already exists.",
path.string()));
status = DatabaseStatus::FAILED_ALREADY_EXISTS;
return nullptr;
}
return MakeBerkeleyDatabase(path, options, status, error);
}
-bool IsWalletLoaded(const fs::path &wallet_path) {
- return IsBDBWalletLoaded(wallet_path);
-}
-
/** Return object for accessing database at specified path. */
std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path &path) {
std::string filename;
return std::make_unique<BerkeleyDatabase>(GetWalletEnv(path, filename),
std::move(filename));
}
/**
* Return object for accessing dummy database with no read/write capabilities.
*/
std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase() {
return std::make_unique<DummyDatabase>();
}
/** Return object for accessing temporary in-memory database. */
std::unique_ptr<WalletDatabase> CreateMockWalletDatabase() {
return std::make_unique<BerkeleyDatabase>(
std::make_shared<BerkeleyEnvironment>(), "");
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index e38ccb1d1..df5900216 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,318 +1,315 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Copyright (c) 2017-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_WALLETDB_H
#define BITCOIN_WALLET_WALLETDB_H
#include <amount.h>
#include <key.h>
#include <script/sign.h>
#include <script/standard.h> // for CTxDestination
#include <wallet/bdb.h>
#include <wallet/walletutil.h>
#include <cstdint>
#include <string>
#include <vector>
/**
* Overview of wallet database classes:
*
* - WalletBatch is an abstract modifier object for the wallet database, and
* encapsulates a database batch update as well as methods to act on the
* database. It should be agnostic to the database implementation.
*
* The following classes are implementation specific:
* - BerkeleyEnvironment is an environment in which the database exists.
* - BerkeleyDatabase represents a wallet database.
* - BerkeleyBatch is a low-level database batch update.
*/
static const bool DEFAULT_FLUSHWALLET = true;
struct CBlockLocator;
class CKeyPool;
class CMasterKey;
class CScript;
class CWallet;
class CWalletTx;
class uint160;
class uint256;
/** Error statuses for the wallet database */
enum class DBErrors {
LOAD_OK,
CORRUPT,
NONCRITICAL_ERROR,
TOO_NEW,
LOAD_FAIL,
NEED_REWRITE
};
namespace DBKeys {
extern const std::string ACENTRY;
extern const std::string ACTIVEEXTERNALSPK;
extern const std::string ACTIVEINTERNALSPK;
extern const std::string BESTBLOCK;
extern const std::string BESTBLOCK_NOMERKLE;
extern const std::string CRYPTED_KEY;
extern const std::string CSCRIPT;
extern const std::string DEFAULTKEY;
extern const std::string DESTDATA;
extern const std::string FLAGS;
extern const std::string HDCHAIN;
extern const std::string KEY;
extern const std::string KEYMETA;
extern const std::string MASTER_KEY;
extern const std::string MINVERSION;
extern const std::string NAME;
extern const std::string OLD_KEY;
extern const std::string ORDERPOSNEXT;
extern const std::string POOL;
extern const std::string PURPOSE;
extern const std::string SETTINGS;
extern const std::string TX;
extern const std::string VERSION;
extern const std::string WALLETDESCRIPTOR;
extern const std::string WALLETDESCRIPTORCACHE;
extern const std::string WALLETDESCRIPTORCKEY;
extern const std::string WALLETDESCRIPTORKEY;
extern const std::string WATCHMETA;
extern const std::string WATCHS;
} // namespace DBKeys
/* simple HD chain data model */
class CHDChain {
public:
uint32_t nExternalChainCounter;
uint32_t nInternalChainCounter;
//! seed hash160
CKeyID seed_id;
static const int VERSION_HD_BASE = 1;
static const int VERSION_HD_CHAIN_SPLIT = 2;
static const int CURRENT_VERSION = VERSION_HD_CHAIN_SPLIT;
int nVersion;
CHDChain() { SetNull(); }
SERIALIZE_METHODS(CHDChain, obj) {
READWRITE(obj.nVersion, obj.nExternalChainCounter, obj.seed_id);
if (obj.nVersion >= VERSION_HD_CHAIN_SPLIT) {
READWRITE(obj.nInternalChainCounter);
}
}
void SetNull() {
nVersion = CHDChain::CURRENT_VERSION;
nExternalChainCounter = 0;
nInternalChainCounter = 0;
seed_id.SetNull();
}
bool operator==(const CHDChain &chain) const {
return seed_id == chain.seed_id;
}
};
class CKeyMetadata {
public:
static const int VERSION_BASIC = 1;
static const int VERSION_WITH_HDDATA = 10;
static const int VERSION_WITH_KEY_ORIGIN = 12;
static const int CURRENT_VERSION = VERSION_WITH_KEY_ORIGIN;
int nVersion;
// 0 means unknown.
int64_t nCreateTime;
// optional HD/bip32 keypath. Still used to determine whether a key is a
// seed. Also kept for backwards compatibility
std::string hdKeypath;
// Id of the HD seed used to derive this key.
CKeyID hd_seed_id;
// Key origin info with path and fingerprint
KeyOriginInfo key_origin;
//! Whether the key_origin is useful
bool has_key_origin = false;
CKeyMetadata() { SetNull(); }
explicit CKeyMetadata(int64_t nCreateTime_) {
SetNull();
nCreateTime = nCreateTime_;
}
SERIALIZE_METHODS(CKeyMetadata, obj) {
READWRITE(obj.nVersion, obj.nCreateTime);
if (obj.nVersion >= VERSION_WITH_HDDATA) {
READWRITE(obj.hdKeypath, obj.hd_seed_id);
}
if (obj.nVersion >= VERSION_WITH_KEY_ORIGIN) {
READWRITE(obj.key_origin);
READWRITE(obj.has_key_origin);
}
}
void SetNull() {
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
hdKeypath.clear();
hd_seed_id.SetNull();
key_origin.clear();
has_key_origin = false;
}
};
/**
* Access to the wallet database.
* Opens the database and provides read and write access to it. Each read and
* write is its own transaction. Multiple operation transactions can be started
* using TxnBegin() and committed using TxnCommit() Otherwise the transaction
* will be committed when the object goes out of scope. Optionally (on by
* default) it will flush to disk on close. Every 1000 writes will automatically
* trigger a flush to disk.
*/
class WalletBatch {
private:
template <typename K, typename T>
bool WriteIC(const K &key, const T &value, bool fOverwrite = true) {
if (!m_batch->Write(key, value, fOverwrite)) {
return false;
}
m_database.IncrementUpdateCounter();
if (m_database.nUpdateCounter % 1000 == 0) {
m_batch->Flush();
}
return true;
}
template <typename K> bool EraseIC(const K &key) {
if (!m_batch->Erase(key)) {
return false;
}
m_database.IncrementUpdateCounter();
if (m_database.nUpdateCounter % 1000 == 0) {
m_batch->Flush();
}
return true;
}
public:
explicit WalletBatch(WalletDatabase &database, const char *pszMode = "r+",
bool _fFlushOnClose = true)
: m_batch(database.MakeBatch(pszMode, _fFlushOnClose)),
m_database(database) {}
WalletBatch(const WalletBatch &) = delete;
WalletBatch &operator=(const WalletBatch &) = delete;
bool WriteName(const CTxDestination &address, const std::string &strName);
bool EraseName(const CTxDestination &address);
bool WritePurpose(const CTxDestination &address,
const std::string &purpose);
bool ErasePurpose(const CTxDestination &address);
bool WriteTx(const CWalletTx &wtx);
bool EraseTx(uint256 hash);
bool WriteKeyMetadata(const CKeyMetadata &meta, const CPubKey &pubkey,
const bool overwrite);
bool WriteKey(const CPubKey &vchPubKey, const CPrivKey &vchPrivKey,
const CKeyMetadata &keyMeta);
bool WriteCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret,
const CKeyMetadata &keyMeta);
bool WriteMasterKey(unsigned int nID, const CMasterKey &kMasterKey);
bool WriteCScript(const uint160 &hash, const CScript &redeemScript);
bool WriteWatchOnly(const CScript &script, const CKeyMetadata &keymeta);
bool EraseWatchOnly(const CScript &script);
bool WriteBestBlock(const CBlockLocator &locator);
bool ReadBestBlock(CBlockLocator &locator);
bool WriteOrderPosNext(int64_t nOrderPosNext);
bool ReadPool(int64_t nPool, CKeyPool &keypool);
bool WritePool(int64_t nPool, const CKeyPool &keypool);
bool ErasePool(int64_t nPool);
bool WriteMinVersion(int nVersion);
bool WriteDescriptorKey(const uint256 &desc_id, const CPubKey &pubkey,
const CPrivKey &privkey);
bool WriteCryptedDescriptorKey(const uint256 &desc_id,
const CPubKey &pubkey,
const std::vector<uint8_t> &secret);
bool WriteDescriptor(const uint256 &desc_id,
const WalletDescriptor &descriptor);
bool WriteDescriptorDerivedCache(const CExtPubKey &xpub,
const uint256 &desc_id,
uint32_t key_exp_index,
uint32_t der_index);
bool WriteDescriptorParentCache(const CExtPubKey &xpub,
const uint256 &desc_id,
uint32_t key_exp_index);
/// Write destination data key,value tuple to database.
bool WriteDestData(const CTxDestination &address, const std::string &key,
const std::string &value);
/// Erase destination data tuple from wallet database.
bool EraseDestData(const CTxDestination &address, const std::string &key);
bool WriteActiveScriptPubKeyMan(uint8_t type, const uint256 &id,
bool internal);
DBErrors LoadWallet(CWallet *pwallet);
DBErrors FindWalletTx(std::vector<TxId> &txIds, std::list<CWalletTx> &vWtx);
DBErrors ZapSelectTx(std::vector<TxId> &txIdsIn,
std::vector<TxId> &txIdsOut);
/* Function to determine if a certain KV/key-type is a key (cryptographical
* key) type */
static bool IsKeyType(const std::string &strType);
//! write the hdchain model (external chain child index counter)
bool WriteHDChain(const CHDChain &chain);
bool WriteWalletFlags(const uint64_t flags);
//! Begin a new transaction
bool TxnBegin();
//! Commit current transaction
bool TxnCommit();
//! Abort current transaction
bool TxnAbort();
private:
std::unique_ptr<DatabaseBatch> m_batch;
WalletDatabase &m_database;
};
//! Compacts BDB state so that wallet.dat is self-contained (if there are
//! changes)
void MaybeCompactWalletDB();
//! Callback for filtering key types to deserialize in ReadKeyValue
using KeyFilterFn = std::function<bool(const std::string &)>;
//! Unserialize a given Key-Value pair and load it into the wallet
bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey, CDataStream &ssValue,
std::string &strType, std::string &strErr,
const KeyFilterFn &filter_fn = nullptr);
-/** Return whether a wallet database is currently loaded. */
-bool IsWalletLoaded(const fs::path &wallet_path);
-
/** Return object for accessing database at specified path. */
std::unique_ptr<WalletDatabase> CreateWalletDatabase(const fs::path &path);
/**
* Return object for accessing dummy database with no read/write capabilities.
*/
std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase();
/** Return object for accessing temporary in-memory database. */
std::unique_ptr<WalletDatabase> CreateMockWalletDatabase();
#endif // BITCOIN_WALLET_WALLETDB_H
diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py
index 5532d1d91..cd285b98e 100755
--- a/test/functional/wallet_multiwallet.py
+++ b/test/functional/wallet_multiwallet.py
@@ -1,416 +1,431 @@
#!/usr/bin/env python3
# Copyright (c) 2017-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.
"""Test multiwallet.
Verify that a bitcoind node can load multiple wallet files
"""
import os
import shutil
import time
from decimal import Decimal
from threading import Thread
from test_framework.authproxy import JSONRPCException
from test_framework.test_framework import BitcoinTestFramework
from test_framework.test_node import ErrorMatch
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
get_rpc_proxy,
)
FEATURE_LATEST = 200300
got_loading_error = False
def test_load_unload(node, name):
global got_loading_error
for _ in range(10):
if got_loading_error:
return
try:
node.loadwallet(name)
node.unloadwallet(name)
except JSONRPCException as e:
if e.error['code'] == - \
4 and 'Wallet already being loading' in e.error['message']:
got_loading_error = True
return
class MultiWalletTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 2
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def add_options(self, parser):
parser.add_argument(
'--data_wallets_dir',
default=os.path.join(
os.path.dirname(
os.path.realpath(__file__)),
'data/wallets/'),
help='Test data with wallet directories (default: %(default)s)',
)
def run_test(self):
node = self.nodes[0]
def data_dir(*p): return os.path.join(node.datadir, self.chain, *p)
def wallet_dir(*p): return data_dir('wallets', *p)
def wallet(name): return node.get_wallet_rpc(name)
def wallet_file(name):
if os.path.isdir(wallet_dir(name)):
return wallet_dir(name, "wallet.dat")
return wallet_dir(name)
assert_equal(self.nodes[0].listwalletdir(),
{'wallets': [{'name': ''}]})
# check wallet.dat is created
self.stop_nodes()
assert_equal(os.path.isfile(wallet_dir('wallet.dat')), True)
# create symlink to verify wallet directory path can be referenced
# through symlink
if os.name != 'nt':
os.mkdir(wallet_dir('w7'))
os.symlink('w7', wallet_dir('w7_symlink'))
# rename wallet.dat to make sure plain wallet file paths (as opposed to
# directory paths) can be loaded
os.rename(wallet_dir("wallet.dat"), wallet_dir("w8"))
# create another dummy wallet for use in testing backups later
self.start_node(0, [])
self.stop_nodes()
empty_wallet = os.path.join(self.options.tmpdir, 'empty.dat')
os.rename(wallet_dir("wallet.dat"), empty_wallet)
# restart node with a mix of wallet names:
# w1, w2, w3 - to verify new wallets created when non-existing paths specified
# w - to verify wallet name matching works when one wallet path is prefix of another
# sub/w5 - to verify relative wallet path is created correctly
# extern/w6 - to verify absolute wallet path is created correctly
# w7_symlink - to verify symlinked wallet path is initialized correctly
# w8 - to verify existing wallet file is loaded correctly
# '' - to verify default wallet file is created correctly
wallet_names = ['w1', 'w2', 'w3', 'w', 'sub/w5',
os.path.join(self.options.tmpdir, 'extern/w6'), 'w7_symlink', 'w8', '']
if os.name == 'nt':
wallet_names.remove('w7_symlink')
extra_args = ['-wallet={}'.format(n) for n in wallet_names]
self.start_node(0, extra_args)
assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [
'', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8'])
assert_equal(set(node.listwallets()), set(wallet_names))
# check that all requested wallets were created
self.stop_node(0)
for wallet_name in wallet_names:
assert_equal(os.path.isfile(wallet_file(wallet_name)), True)
# should not initialize if wallet path can't be created
exp_stderr = "boost::filesystem::create_directory:"
self.nodes[0].assert_start_raises_init_error(
['-wallet=wallet.dat/bad'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
self.nodes[0].assert_start_raises_init_error(
['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist')
self.nodes[0].assert_start_raises_init_error(
['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir())
self.nodes[0].assert_start_raises_init_error(
['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir())
# should not initialize if there are duplicate wallets
self.nodes[0].assert_start_raises_init_error(
['-wallet=w1', '-wallet=w1'], 'Error: Error loading wallet w1. Duplicate -wallet filename specified.')
# should not initialize if one wallet is a copy of another
shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy'))
exp_stderr = r"BerkeleyDatabase: Can't open database w8_copy \(duplicates fileid \w+ from w8\)"
self.nodes[0].assert_start_raises_init_error(
['-wallet=w8', '-wallet=w8_copy'], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
# should not initialize if wallet file is a symlink
if os.name != 'nt':
os.symlink('w8', wallet_dir('w8_symlink'))
self.nodes[0].assert_start_raises_init_error(
['-wallet=w8_symlink'], r'Error: Invalid -wallet path \'w8_symlink\'\. .*', match=ErrorMatch.FULL_REGEX)
# should not initialize if the specified walletdir does not exist
self.nodes[0].assert_start_raises_init_error(
['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist')
# should not initialize if the specified walletdir is not a directory
not_a_dir = wallet_dir('notadir')
open(not_a_dir, 'a', encoding="utf8").close()
self.nodes[0].assert_start_raises_init_error(
['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory')
# if wallets/ doesn't exist, datadir should be the default wallet dir
wallet_dir2 = data_dir('walletdir')
os.rename(wallet_dir(), wallet_dir2)
self.start_node(0, ['-wallet=w4', '-wallet=w5'])
assert_equal(set(node.listwallets()), {"w4", "w5"})
w5 = wallet("w5")
node.generatetoaddress(nblocks=1, address=w5.getnewaddress())
# now if wallets/ exists again, but the rootdir is specified as the
# walletdir, w4 and w5 should still be loaded
os.rename(wallet_dir2, wallet_dir())
self.restart_node(0, ['-wallet=w4', '-wallet=w5',
'-walletdir=' + data_dir()])
assert_equal(set(node.listwallets()), {"w4", "w5"})
w5 = wallet("w5")
w5_info = w5.getwalletinfo()
assert_equal(w5_info['immature_balance'], 50000000)
competing_wallet_dir = os.path.join(
self.options.tmpdir, 'competing_walletdir')
os.mkdir(competing_wallet_dir)
self.restart_node(0, ['-walletdir=' + competing_wallet_dir])
exp_stderr = r"Error: Error initializing wallet database environment \"\S+competing_walletdir\"!"
self.nodes[1].assert_start_raises_init_error(
['-walletdir=' + competing_wallet_dir], exp_stderr, match=ErrorMatch.PARTIAL_REGEX)
self.restart_node(0, extra_args)
assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [
'', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy'])
wallets = [wallet(w) for w in wallet_names]
wallet_bad = wallet("bad")
# check wallet names and balances
node.generatetoaddress(nblocks=1, address=wallets[0].getnewaddress())
for wallet_name, wallet in zip(wallet_names, wallets):
info = wallet.getwalletinfo()
assert_equal(info['immature_balance'],
50000000 if wallet is wallets[0] else 0)
assert_equal(info['walletname'], wallet_name)
# accessing invalid wallet fails
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
wallet_bad.getwalletinfo)
# accessing wallet RPC without using wallet endpoint fails
assert_raises_rpc_error(-19, "Wallet file not specified (must request wallet RPC through /wallet/<filename> uri-path).",
node.getwalletinfo)
w1, w2, w3, w4, *_ = wallets
node.generatetoaddress(nblocks=101, address=w1.getnewaddress())
assert_equal(w1.getbalance(), 100000000)
assert_equal(w2.getbalance(), 0)
assert_equal(w3.getbalance(), 0)
assert_equal(w4.getbalance(), 0)
w1.sendtoaddress(w2.getnewaddress(), 1000000)
w1.sendtoaddress(w3.getnewaddress(), 2000000)
w1.sendtoaddress(w4.getnewaddress(), 3000000)
node.generatetoaddress(nblocks=1, address=w1.getnewaddress())
assert_equal(w2.getbalance(), 1000000)
assert_equal(w3.getbalance(), 2000000)
assert_equal(w4.getbalance(), 3000000)
batch = w1.batch([w1.getblockchaininfo.get_request(),
w1.getwalletinfo.get_request()])
assert_equal(batch[0]["result"]["chain"], self.chain)
assert_equal(batch[1]["result"]["walletname"], "w1")
self.log.info('Check for per-wallet settxfee call')
assert_equal(w1.getwalletinfo()['paytxfee'], 0)
assert_equal(w2.getwalletinfo()['paytxfee'], 0)
w2.settxfee(1000)
assert_equal(w1.getwalletinfo()['paytxfee'], 0)
assert_equal(w2.getwalletinfo()['paytxfee'], Decimal('1000.00'))
self.log.info("Test dynamic wallet loading")
self.restart_node(0, ['-nowallet'])
assert_equal(node.listwallets(), [])
assert_raises_rpc_error(-32601, "Method not found", node.getwalletinfo)
self.log.info("Load first wallet")
loadwallet_name = node.loadwallet(wallet_names[0])
assert_equal(loadwallet_name['name'], wallet_names[0])
assert_equal(node.listwallets(), wallet_names[0:1])
node.getwalletinfo()
w1 = node.get_wallet_rpc(wallet_names[0])
w1.getwalletinfo()
self.log.info("Load second wallet")
loadwallet_name = node.loadwallet(wallet_names[1])
assert_equal(loadwallet_name['name'], wallet_names[1])
assert_equal(node.listwallets(), wallet_names[0:2])
assert_raises_rpc_error(-19,
"Wallet file not specified", node.getwalletinfo)
w2 = node.get_wallet_rpc(wallet_names[1])
w2.getwalletinfo()
self.log.info("Concurrent wallet loading")
threads = []
for _ in range(3):
n = node.cli if self.options.usecli else get_rpc_proxy(
node.url, 1, timeout=600, coveragedir=node.coverage_dir)
t = Thread(target=test_load_unload, args=(n, wallet_names[2], ))
t.start()
threads.append(t)
for t in threads:
t.join()
global got_loading_error
assert_equal(got_loading_error, True)
self.log.info("Load remaining wallets")
for wallet_name in wallet_names[2:]:
loadwallet_name = self.nodes[0].loadwallet(wallet_name)
assert_equal(loadwallet_name['name'], wallet_name)
assert_equal(set(self.nodes[0].listwallets()), set(wallet_names))
# Fail to load if wallet doesn't exist
assert_raises_rpc_error(-18, 'Wallet wallets not found.',
self.nodes[0].loadwallet, 'wallets')
# Fail to load duplicate wallets
+ path = os.path.join(
+ self.options.tmpdir,
+ "node0",
+ "regtest",
+ "wallets",
+ "w1",
+ "wallet.dat")
assert_raises_rpc_error(
-4,
- 'Wallet file verification failed. Error loading wallet w1. Duplicate -wallet filename specified.',
+ "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(
+ path),
self.nodes[0].loadwallet,
wallet_names[0])
# Fail to load duplicate wallets by different ways (directory and
# filepath)
+ path = os.path.join(
+ self.options.tmpdir,
+ "node0",
+ "regtest",
+ "wallets",
+ "wallet.dat")
assert_raises_rpc_error(
-4,
- "Wallet file verification failed. Error loading wallet wallet.dat. Duplicate -wallet filename specified.",
+ "Wallet file verification failed. Refusing to load database. Data file '{}' is already loaded.".format(
+ path),
self.nodes[0].loadwallet,
'wallet.dat')
# Fail to load if one wallet is a copy of another
assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid",
self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if one wallet is a copy of another.
# Test this twice to make sure that we don't re-introduce
# https://github.com/bitcoin/bitcoin/issues/14304
assert_raises_rpc_error(-4, "BerkeleyDatabase: Can't open database w8_copy (duplicates fileid",
self.nodes[0].loadwallet, 'w8_copy')
# Fail to load if wallet file is a symlink
if os.name != 'nt':
assert_raises_rpc_error(
-4,
"Wallet file verification failed. Invalid -wallet path 'w8_symlink'",
self.nodes[0].loadwallet,
'w8_symlink')
# Fail to load if a directory is specified that doesn't contain a
# wallet
os.mkdir(wallet_dir('empty_wallet_dir'))
assert_raises_rpc_error(-18, "Directory empty_wallet_dir does not contain a wallet.dat file",
self.nodes[0].loadwallet, 'empty_wallet_dir')
self.log.info("Test dynamic wallet creation.")
# Fail to create a wallet if it already exists.
assert_raises_rpc_error(-4, "Wallet w2 already exists.",
self.nodes[0].createwallet, 'w2')
# Successfully create a wallet with a new name
loadwallet_name = self.nodes[0].createwallet('w9')
assert_equal(loadwallet_name['name'], 'w9')
w9 = node.get_wallet_rpc('w9')
assert_equal(w9.getwalletinfo()['walletname'], 'w9')
assert 'w9' in self.nodes[0].listwallets()
# Successfully create a wallet using a full path
new_wallet_dir = os.path.join(self.options.tmpdir, 'new_walletdir')
new_wallet_name = os.path.join(new_wallet_dir, 'w10')
loadwallet_name = self.nodes[0].createwallet(new_wallet_name)
assert_equal(loadwallet_name['name'], new_wallet_name)
w10 = node.get_wallet_rpc(new_wallet_name)
assert_equal(w10.getwalletinfo()['walletname'], new_wallet_name)
assert new_wallet_name in self.nodes[0].listwallets()
self.log.info("Test dynamic wallet unloading")
# Test `unloadwallet` errors
assert_raises_rpc_error(-1, "JSON value is not a string as expected",
self.nodes[0].unloadwallet)
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
self.nodes[0].unloadwallet, "dummy")
assert_raises_rpc_error(-18, "Requested wallet does not exist or is not loaded",
node.get_wallet_rpc("dummy").unloadwallet)
assert_raises_rpc_error(-8, "Cannot unload the requested wallet",
w1.unloadwallet, "w2"),
# Successfully unload the specified wallet name
self.nodes[0].unloadwallet("w1")
assert 'w1' not in self.nodes[0].listwallets()
# Successfully unload the wallet referenced by the request endpoint
# Also ensure unload works during walletpassphrase timeout
w2.encryptwallet('test')
w2.walletpassphrase('test', 1)
w2.unloadwallet()
time.sleep(1.1)
assert 'w2' not in self.nodes[0].listwallets()
# Successfully unload all wallets
for wallet_name in self.nodes[0].listwallets():
self.nodes[0].unloadwallet(wallet_name)
assert_equal(self.nodes[0].listwallets(), [])
assert_raises_rpc_error(-32601, "Method not found (wallet method is disabled because no wallet is loaded)",
self.nodes[0].getwalletinfo)
# Successfully load a previously unloaded wallet
self.nodes[0].loadwallet('w1')
assert_equal(self.nodes[0].listwallets(), ['w1'])
assert_equal(w1.getwalletinfo()['walletname'], 'w1')
assert_equal(sorted(map(lambda w: w['name'], self.nodes[0].listwalletdir()['wallets'])), [
'', os.path.join('sub', 'w5'), 'w', 'w1', 'w2', 'w3', 'w7', 'w7_symlink', 'w8', 'w8_copy', 'w9'])
# Test backing up and restoring wallets
self.log.info("Test wallet backup")
self.restart_node(0, ['-nowallet'])
for wallet_name in wallet_names:
self.nodes[0].loadwallet(wallet_name)
for wallet_name in wallet_names:
rpc = self.nodes[0].get_wallet_rpc(wallet_name)
addr = rpc.getnewaddress()
backup = os.path.join(self.options.tmpdir, 'backup.dat')
rpc.backupwallet(backup)
self.nodes[0].unloadwallet(wallet_name)
shutil.copyfile(empty_wallet, wallet_file(wallet_name))
self.nodes[0].loadwallet(wallet_name)
assert_equal(rpc.getaddressinfo(addr)['ismine'], False)
self.nodes[0].unloadwallet(wallet_name)
shutil.copyfile(backup, wallet_file(wallet_name))
self.nodes[0].loadwallet(wallet_name)
assert_equal(rpc.getaddressinfo(addr)['ismine'], True)
# Test .walletlock file is closed
self.start_node(1)
wallet = os.path.join(self.options.tmpdir, 'my_wallet')
self.nodes[0].createwallet(wallet)
assert_raises_rpc_error(-4, "Error initializing wallet database environment",
self.nodes[1].loadwallet, wallet)
self.nodes[0].unloadwallet(wallet)
self.nodes[1].loadwallet(wallet)
if __name__ == '__main__':
MultiWalletTest().main()

File Metadata

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

Event Timeline