Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F13711282
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
104 KB
Subscribers
None
View Options
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index 6de2fe3f2..4eeaff7c9 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,980 +1,980 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <wallet/db.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <boost/thread.hpp> // boost::this_thread::interruption_point() (mingw)
#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(
"BerkeleyBatch: 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(
"BerkeleyBatch: Can't open database %s (duplicates fileid %s "
"from %s)",
filename,
HexStr(std::begin(item.second.value),
std::end(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;
}
static void SplitWalletPath(const fs::path &wallet_path,
fs::path &env_directory,
std::string &database_filename) {
if (fs::is_regular_file(wallet_path)) {
// Special case for backwards compatibility: if wallet path points to an
// existing file, treat it as the path to a BDB data file in a parent
// directory that also contains BDB log files.
env_directory = wallet_path.parent_path();
database_filename = wallet_path.filename().string();
} else {
// Normal case: Interpret wallet path as a directory path containing
// data and log files.
env_directory = wallet_path;
database_filename = "wallet.dat";
}
}
bool IsWalletLoaded(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);
}
fs::path WalletDataFilePath(const fs::path &wallet_path) {
fs::path env_directory;
std::string database_filename;
SplitWalletPath(wallet_path, env_directory, database_filename);
return env_directory / database_filename;
}
/**
* @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) {
auto count = mapFileUseCount.find(db.first);
assert(count == mapFileUseCount.end() || count->second == 0);
BerkeleyDatabase &database = db.second.get();
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(bool retry) {
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);
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();
if (retry) {
// try moving the database env out of the way
fs::path pathDatabaseBak =
pathIn / strprintf("database.%d.bak", GetTime());
try {
fs::rename(pathLogDir, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n",
pathLogDir.string(), pathDatabaseBak.string());
} catch (const fs::filesystem_error &) {
// failure is ok (well, not really, but it's not worse than what
// we started with)
}
// try opening it again one more time
if (!Open(false /* retry */)) {
// if it still fails, it probably means we can't even create the
// database env
return false;
}
} else {
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;
}
bool BerkeleyEnvironment::Verify(const std::string &strFile) {
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
Db db(dbenv.get(), 0);
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
return result == 0;
}
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;
}
/* End of headers, beginning of key/value data */
static const char *HEADER_END = "HEADER=END";
/* End of key/value data */
static const char *DATA_END = "DATA=END";
typedef std::pair<std::vector<uint8_t>, std::vector<uint8_t>> KeyValPair;
-bool BerkeleyBatch::Recover(const fs::path &file_path, void *callbackDataIn,
- bool (*recoverKVcallback)(void *callbackData,
- CDataStream ssKey,
- CDataStream ssValue),
- std::string &newFilename) {
+bool RecoverDatabaseFile(const fs::path &file_path, void *callbackDataIn,
+ bool (*recoverKVcallback)(void *callbackData,
+ CDataStream ssKey,
+ CDataStream ssValue),
+ std::string &newFilename) {
std::string filename;
std::shared_ptr<BerkeleyEnvironment> env =
GetWalletEnv(file_path, filename);
// Recovery procedure:
// Move wallet file to walletfilename.timestamp.bak
// Call Salvage with fAggressive=true to get as much data as possible.
// Rewrite salvaged data to fresh wallet file.
// Set -rescan so any missing transactions will be found.
int64_t now = GetTime();
newFilename = strprintf("%s.%d.bak", filename, now);
int result = env->dbenv->dbrename(nullptr, filename.c_str(), nullptr,
newFilename.c_str(), DB_AUTO_COMMIT);
if (result == 0) {
LogPrintf("Renamed %s to %s\n", filename, newFilename);
} else {
LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
return false;
}
/**
* Salvage data from a file. The DB_AGGRESSIVE flag is being used (see
* berkeley DB->verify() method documentation). key/value pairs are appended
* to salvagedData which are then written out to a new wallet file. NOTE:
* reads the entire database into memory, so cannot be used for huge
* databases.
*/
std::vector<KeyValPair> salvagedData;
std::stringstream strDump;
Db db(env->dbenv.get(), 0);
result = db.verify(newFilename.c_str(), nullptr, &strDump,
DB_SALVAGE | DB_AGGRESSIVE);
if (result == DB_VERIFY_BAD) {
LogPrintf("Salvage: Database salvage found "
"errors, all data may not be recoverable.\n");
}
if (result != 0 && result != DB_VERIFY_BAD) {
LogPrintf("Salvage: Database salvage failed with "
"result %d.\n",
result);
return false;
}
// Format of bdb dump is ascii lines:
// header lines...
// HEADER=END
// hexadecimal key
// hexadecimal value
// ... repeated
// DATA=END
std::string strLine;
while (!strDump.eof() && strLine != HEADER_END) {
// Skip past header
getline(strDump, strLine);
}
std::string keyHex, valueHex;
while (!strDump.eof() && keyHex != DATA_END) {
getline(strDump, keyHex);
if (keyHex != DATA_END) {
if (strDump.eof()) {
break;
}
getline(strDump, valueHex);
if (valueHex == DATA_END) {
LogPrintf("Salvage: WARNING: Number of "
"keys in data does not match number of values.\n");
break;
}
salvagedData.push_back(
make_pair(ParseHex(keyHex), ParseHex(valueHex)));
}
}
bool fSuccess;
if (keyHex != DATA_END) {
LogPrintf("Salvage: WARNING: Unexpected end of "
"file while reading salvage output.\n");
fSuccess = false;
} else {
fSuccess = (result == 0);
}
if (salvagedData.empty()) {
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
return false;
}
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(env->dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
filename.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
DB_CREATE, // Flags
0);
if (ret > 0) {
LogPrintf("Cannot create database file %s\n", filename);
pdbCopy->close(0);
return false;
}
DbTxn *ptxn = env->TxnBegin();
for (KeyValPair &row : salvagedData) {
if (recoverKVcallback) {
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) {
continue;
}
}
Dbt datKey(&row.first[0], row.first.size());
Dbt datValue(&row.second[0], row.second.size());
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
if (ret2 > 0) {
fSuccess = false;
}
}
ptxn->commit(0);
pdbCopy->close(0);
return fSuccess;
}
bool BerkeleyBatch::VerifyEnvironment(const fs::path &file_path,
bilingual_str &errorStr) {
std::string walletFile;
std::shared_ptr<BerkeleyEnvironment> env =
GetWalletEnv(file_path, walletFile);
fs::path walletDir = env->Directory();
LogPrintf("Using BerkeleyDB version %s\n",
DbEnv::version(nullptr, nullptr, nullptr));
LogPrintf("Using wallet %s\n", file_path.string());
if (!env->Open(true /* retry */)) {
errorStr = strprintf(
_("Error initializing wallet database environment %s!"), walletDir);
return false;
}
return true;
}
bool BerkeleyBatch::VerifyDatabaseFile(const fs::path &file_path,
bilingual_str &errorStr) {
std::string walletFile;
std::shared_ptr<BerkeleyEnvironment> env =
GetWalletEnv(file_path, walletFile);
fs::path walletDir = env->Directory();
if (fs::exists(walletDir / walletFile)) {
if (!env->Verify(walletFile)) {
errorStr =
strprintf(_("%s corrupt. Try using the wallet tool "
"bitcoin-wallet to salvage or restoring a backup."),
walletFile);
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);
}
BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase &database, const char *pszMode,
bool fFlushOnCloseIn)
: pdb(nullptr), activeTxn(nullptr) {
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
fFlushOnClose = fFlushOnCloseIn;
env = database.env.get();
if (database.IsDummy()) {
return;
}
const std::string &strFilename = database.strFile;
bool fCreate = strchr(pszMode, 'c') != nullptr;
unsigned int nFlags = DB_THREAD;
if (fCreate) {
nFlags |= DB_CREATE;
}
{
LOCK(cs_db);
if (!env->Open(false /* retry */)) {
throw std::runtime_error(
"BerkeleyBatch: Failed to open database environment.");
}
pdb = database.m_db.get();
if (pdb == 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("BerkeleyBatch: Failed to configure for no "
"temp file backing for database %s",
strFilename));
}
}
ret = pdb_temp->open(
nullptr, // Txn pointer
fMockDb ? nullptr : strFilename.c_str(), // Filename
fMockDb ? strFilename.c_str() : "main", // Logical db name
DB_BTREE, // Database type
nFlags, // Flags
0);
if (ret != 0) {
throw std::runtime_error(
strprintf("BerkeleyBatch: Error %d, can't open database %s",
ret, strFilename));
}
// 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.
//
// Also call CheckUniqueFileid on all the other g_dbenvs to prevent
// bitcoin from opening the same data file through another
// environment when the file is referenced through equivalent but
// not obviously identical symlinked or hard linked or bind mounted
// paths. In the future a more relaxed check for equal inode and
// device ids could be done instead, which would allow opening
// different backup copies of a wallet at the same time. Maybe even
// more ideally, an exclusive lock for accessing the database could
// be implemented, so no equality checks are needed at all. (Newer
// versions of BDB have an set_lk_exclusive method for this
// purpose, but the older version we use does not.)
for (const auto &dbenv : g_dbenvs) {
CheckUniqueFileid(*dbenv.second.lock().get(), strFilename,
*pdb_temp, this->env->m_fileids[strFilename]);
}
pdb = pdb_temp.release();
database.m_db.reset(pdb);
if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly;
fReadOnly = false;
Write(std::string("version"), CLIENT_VERSION);
fReadOnly = fTmp;
}
}
++env->mapFileUseCount[strFilename];
strFile = strFilename;
}
}
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;
}
void BerkeleyBatch::Close() {
if (!pdb) {
return;
}
if (activeTxn) {
activeTxn->abort();
}
activeTxn = nullptr;
pdb = nullptr;
if (fFlushOnClose) {
Flush();
}
{
LOCK(cs_db);
--env->mapFileUseCount[strFile];
}
env->m_db_in_use.notify_all();
}
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 &count : mapFileUseCount) {
if (count.second > 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();
Open(true);
}
bool BerkeleyBatch::Rewrite(BerkeleyDatabase &database, const char *pszSkip) {
if (database.IsDummy()) {
return true;
}
BerkeleyEnvironment *env = database.env.get();
const std::string &strFile = database.strFile;
while (true) {
{
LOCK(cs_db);
if (!env->mapFileUseCount.count(strFile) ||
env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
env->mapFileUseCount.erase(strFile);
bool fSuccess = true;
LogPrintf("BerkeleyBatch::Rewrite: Rewriting %s...\n", strFile);
std::string strFileRes = strFile + ".rewrite";
{
// surround usage of db with extra {}
BerkeleyBatch db(database, "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;
}
Dbc *pcursor = db.GetCursor();
if (pcursor) {
while (fSuccess) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
int ret1 = db.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret1 == DB_NOTFOUND) {
pcursor->close();
break;
}
if (ret1 != 0) {
pcursor->close();
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;
}
}
}
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);
std::map<std::string, int>::iterator mi = mapFileUseCount.begin();
while (mi != mapFileUseCount.end()) {
std::string strFile = (*mi).first;
int nRefCount = (*mi).second;
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);
mapFileUseCount.erase(mi++);
} else {
mi++;
}
}
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 (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb) {
fs::remove_all(fs::path(strPath) / "database");
}
}
}
}
}
bool BerkeleyBatch::PeriodicFlush(BerkeleyDatabase &database) {
if (database.IsDummy()) {
return true;
}
bool ret = false;
BerkeleyEnvironment *env = database.env.get();
const std::string &strFile = database.strFile;
TRY_LOCK(cs_db, lockDb);
if (lockDb) {
// Don't do this if any databases are in use
int nRefCount = 0;
std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin();
while (mit != env->mapFileUseCount.end()) {
nRefCount += (*mit).second;
mit++;
}
if (nRefCount == 0) {
boost::this_thread::interruption_point();
std::map<std::string, int>::iterator mi =
env->mapFileUseCount.find(strFile);
if (mi != env->mapFileUseCount.end()) {
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);
env->mapFileUseCount.erase(mi++);
LogPrint(BCLog::WALLETDB, "Flushed %s %dms\n", strFile,
GetTimeMillis() - nStart);
ret = true;
}
}
}
return ret;
}
bool BerkeleyDatabase::Rewrite(const char *pszSkip) {
return BerkeleyBatch::Rewrite(*this, pszSkip);
}
bool BerkeleyDatabase::Backup(const std::string &strDest) const {
if (IsDummy()) {
return false;
}
while (true) {
{
LOCK(cs_db);
if (!env->mapFileUseCount.count(strFile) ||
env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
env->mapFileUseCount.erase(strFile);
// Copy wallet file.
fs::path pathSrc = 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(bool shutdown) {
if (!IsDummy()) {
env->Flush(shutdown);
if (shutdown) {
LOCK(cs_db);
g_dbenvs.erase(env->Directory().string());
env = nullptr;
} else {
// TODO: To avoid g_dbenvs.erase erasing the environment prematurely
// after the first database shutdown when multiple databases are
// open in the same environment, should replace raw database `env`
// pointers with shared or weak pointers, or else separate the
// database and environment shutdowns so environments can be shut
// down after databases.
env->m_fileids.erase(strFile);
}
}
}
void BerkeleyDatabase::ReloadDbEnv() {
if (!IsDummy()) {
env->ReloadDbEnv();
}
}
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 9de46631a..e956b7955 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,419 +1,420 @@
// 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 <serialize.h>
#include <streams.h>
#include <util/system.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, int> mapFileUseCount;
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 Verify(const std::string &strFile);
bool Open(bool retry);
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;
}
};
/** Return whether a wallet database is currently loaded. */
bool IsWalletLoaded(const fs::path &wallet_path);
/**
* 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);
/** Get BerkeleyEnvironment and database filename given a wallet path. */
std::shared_ptr<BerkeleyEnvironment>
GetWalletEnv(const fs::path &wallet_path, std::string &database_filename);
/**
* An instance of this class represents one database.
* For BerkeleyDB this is just a (env, strFile) tuple.
*/
class BerkeleyDatabase {
friend class BerkeleyBatch;
public:
/** Create dummy DB handle */
BerkeleyDatabase()
: nUpdateCounter(0), nLastSeen(0), nLastFlushed(0),
nLastWalletUpdate(0), env(nullptr) {}
/** Create DB handle to real database */
BerkeleyDatabase(std::shared_ptr<BerkeleyEnvironment> envIn,
std::string filename)
: nUpdateCounter(0), nLastSeen(0), nLastFlushed(0),
nLastWalletUpdate(0), env(std::move(envIn)),
strFile(std::move(filename)) {
auto inserted =
this->env->m_databases.emplace(strFile, std::ref(*this));
assert(inserted.second);
}
~BerkeleyDatabase() {
if (env) {
size_t erased = env->m_databases.erase(strFile);
assert(erased == 1);
}
}
/** Return object for accessing database at specified path. */
static std::unique_ptr<BerkeleyDatabase> Create(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.
*/
static std::unique_ptr<BerkeleyDatabase> CreateDummy() {
return std::make_unique<BerkeleyDatabase>();
}
/**
* Return object for accessing temporary in-memory database.
*/
static std::unique_ptr<BerkeleyDatabase> CreateMock() {
return std::make_unique<BerkeleyDatabase>(
std::make_shared<BerkeleyEnvironment>(), "");
}
/**
* Rewrite the entire database on disk, with the exception of key pszSkip if
* non-zero
*/
bool Rewrite(const char *pszSkip = nullptr);
/**
* Back up the entire database to a file.
*/
bool Backup(const std::string &strDest) const;
/**
* Make sure all changes are flushed to disk.
*/
void Flush(bool shutdown);
void IncrementUpdateCounter();
void ReloadDbEnv();
std::atomic<unsigned int> nUpdateCounter;
unsigned int nLastSeen;
unsigned int nLastFlushed;
int64_t nLastWalletUpdate;
/**
* 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;
private:
std::string strFile;
/**
* Return whether this database handle is a dummy for testing.
* Only to be used at a low level, application should ideally not care
* about this.
*/
bool IsDummy() const { return env == nullptr; }
};
/** RAII class that provides access to a Berkeley database */
class BerkeleyBatch {
/** 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 *();
};
protected:
Db *pdb;
std::string strFile;
DbTxn *activeTxn;
bool fReadOnly;
bool fFlushOnClose;
BerkeleyEnvironment *env;
public:
explicit BerkeleyBatch(BerkeleyDatabase &database,
const char *pszMode = "r+",
bool fFlushOnCloseIn = true);
~BerkeleyBatch() { Close(); }
BerkeleyBatch(const BerkeleyBatch &) = delete;
BerkeleyBatch &operator=(const BerkeleyBatch &) = delete;
void Flush();
void Close();
- static bool Recover(const fs::path &file_path, void *callbackDataIn,
- bool (*recoverKVcallback)(void *callbackData,
- CDataStream ssKey,
- CDataStream ssValue),
- std::string &out_backup_filename);
/* flush the wallet passively (TRY_LOCK)
ideal to be called periodically */
static bool PeriodicFlush(BerkeleyDatabase &database);
/* verifies the database environment */
static bool VerifyEnvironment(const fs::path &file_path,
bilingual_str &errorStr);
/* verifies the database file */
static bool VerifyDatabaseFile(const fs::path &file_path,
bilingual_str &errorStr);
template <typename K, typename T> bool Read(const K &key, T &value) {
if (!pdb) {
return false;
}
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
SafeDbt datKey(ssKey.data(), ssKey.size());
// Read
SafeDbt datValue;
int ret = pdb->get(activeTxn, datKey, datValue, 0);
bool success = false;
if (datValue.get_data() != nullptr) {
// Unserialize value
try {
CDataStream ssValue((char *)datValue.get_data(),
(char *)datValue.get_data() +
datValue.get_size(),
SER_DISK, CLIENT_VERSION);
ssValue >> value;
success = true;
} catch (const std::exception &) {
// In this case success remains 'false'
}
}
return ret == 0 && success;
}
template <typename K, typename T>
bool Write(const K &key, const T &value, bool fOverwrite = true) {
if (!pdb) {
return true;
}
if (fReadOnly) {
assert(!"Write called on database in read-only mode");
}
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
SafeDbt datKey(ssKey.data(), ssKey.size());
// Value
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
ssValue.reserve(10000);
ssValue << value;
SafeDbt datValue(ssValue.data(), ssValue.size());
// Write
int ret = pdb->put(activeTxn, datKey, datValue,
(fOverwrite ? 0 : DB_NOOVERWRITE));
return (ret == 0);
}
template <typename K> bool Erase(const K &key) {
if (!pdb) {
return false;
}
if (fReadOnly) {
assert(!"Erase called on database in read-only mode");
}
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
SafeDbt datKey(ssKey.data(), ssKey.size());
// Erase
int ret = pdb->del(activeTxn, datKey, 0);
return (ret == 0 || ret == DB_NOTFOUND);
}
template <typename K> bool Exists(const K &key) {
if (!pdb) {
return false;
}
// Key
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
ssKey.reserve(1000);
ssKey << key;
SafeDbt datKey(ssKey.data(), ssKey.size());
// Exists
int ret = pdb->exists(activeTxn, datKey, 0);
return (ret == 0);
}
Dbc *GetCursor() {
if (!pdb) {
return nullptr;
}
Dbc *pcursor = nullptr;
int ret = pdb->cursor(nullptr, &pcursor, 0);
if (ret != 0) {
return nullptr;
}
return pcursor;
}
int ReadAtCursor(Dbc *pcursor, CDataStream &ssKey, CDataStream &ssValue) {
// Read at cursor
SafeDbt datKey;
SafeDbt datValue;
int ret = pcursor->get(datKey, datValue, DB_NEXT);
if (ret != 0) {
return ret;
} else if (datKey.get_data() == nullptr ||
datValue.get_data() == nullptr) {
return 99999;
}
// Convert to streams
ssKey.SetType(SER_DISK);
ssKey.clear();
ssKey.write((char *)datKey.get_data(), datKey.get_size());
ssValue.SetType(SER_DISK);
ssValue.clear();
ssValue.write((char *)datValue.get_data(), datValue.get_size());
return 0;
}
bool TxnBegin() {
if (!pdb || activeTxn) {
return false;
}
DbTxn *ptxn = env->TxnBegin();
if (!ptxn) {
return false;
}
activeTxn = ptxn;
return true;
}
bool TxnCommit() {
if (!pdb || !activeTxn) {
return false;
}
int ret = activeTxn->commit(0);
activeTxn = nullptr;
return (ret == 0);
}
bool TxnAbort() {
if (!pdb || !activeTxn) {
return false;
}
int ret = activeTxn->abort();
activeTxn = nullptr;
return (ret == 0);
}
static bool Rewrite(BerkeleyDatabase &database,
const char *pszSkip = nullptr);
};
+bool RecoverDatabaseFile(const fs::path &file_path, void *callbackDataIn,
+ bool (*recoverKVcallback)(void *callbackData,
+ CDataStream ssKey,
+ CDataStream ssValue),
+ std::string &out_backup_filename);
+
#endif // BITCOIN_WALLET_DB_H
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 120d6056d..ccfb82b26 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,1061 +1,1041 @@
// 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/system.h>
#include <util/time.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.begin(), vchKey.end())), false);
}
bool WalletBatch::WriteCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret,
const CKeyMetadata &keyMeta) {
if (!WriteKeyMetadata(keyMeta, vchPubKey, true)) {
return false;
}
if (!WriteIC(std::make_pair(DBKeys::CRYPTED_KEY, vchPubKey),
vchCryptedSecret, false)) {
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.begin(), key.end())),
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;
CWalletScanState() {}
};
static bool ReadKeyValue(CWallet *pwallet, CDataStream &ssKey,
CDataStream &ssValue, CWalletScanState &wss,
std::string &strType, std::string &strErr)
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 (strType == DBKeys::NAME) {
std::string strAddress;
ssKey >> strAddress;
std::string label;
ssValue >> label;
pwallet
->m_address_book[DecodeDestination(strAddress,
pwallet->chainParams)]
.SetLabel(label);
} else if (strType == DBKeys::PURPOSE) {
std::string strAddress;
ssKey >> strAddress;
ssValue >> pwallet
->m_address_book[DecodeDestination(
strAddress, pwallet->chainParams)]
.purpose;
} else if (strType == DBKeys::TX) {
TxId txid;
ssKey >> txid;
CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
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;
}
pwallet->LoadToWallet(wtx);
} 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.begin(), vchKey.end()) != hash) {
strErr = "Error reading wallet database: CPubKey/CPrivKey "
"corrupt";
return false;
}
fSkipCheck = true;
}
if (!key.Load(pkey, vchPubKey, fSkipCheck)) {
strErr = "Error reading wallet database: CPrivKey corrupt";
return false;
}
if (!pwallet->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;
wss.nCKeys++;
if (!pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadCryptedKey(
vchPubKey, vchPrivKey)) {
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);
} 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->chainParams), strKey,
strValue);
} else if (strType == DBKeys::HDCHAIN) {
CHDChain chain;
ssValue >> chain;
pwallet->GetOrCreateLegacyScriptPubKeyMan()->SetHDChain(chain,
true);
} else if (strType == DBKeys::FLAGS) {
uint64_t flags;
ssValue >> flags;
if (!pwallet->SetWalletFlags(flags, true)) {
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 (wss.m_descriptor_caches.count(desc_id)) {
wss.m_descriptor_caches[desc_id] = DescriptorCache();
}
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.begin(), to_hash.end()) != 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) {
CWalletScanState dummy_wss;
LOCK(pwallet->cs_wallet);
return ReadKeyValue(pwallet, ssKey, ssValue, dummy_wss, strType, strErr);
}
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
Dbc *pcursor = m_batch.GetCursor();
if (!pcursor) {
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);
int ret = m_batch.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND) {
break;
}
if (ret != 0) {
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);
}
}
pcursor->close();
} catch (const boost::thread_interrupted &) {
throw;
} catch (...) {
result = DBErrors::CORRUPT;
}
// Set the active ScriptPubKeyMans
for (auto spk_man_pair : wss.m_active_external_spks) {
pwallet->SetActiveScriptPubKeyMan(
spk_man_pair.second, spk_man_pair.first, /* internal */ false,
/* memonly */ true);
}
for (auto spk_man_pair : wss.m_active_internal_spks) {
pwallet->SetActiveScriptPubKeyMan(
spk_man_pair.second, spk_man_pair.first, /* internal */ true,
/* memonly */ 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;
}
return result;
}
DBErrors WalletBatch::FindWalletTx(std::vector<TxId> &txIds,
std::vector<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
Dbc *pcursor = m_batch.GetCursor();
if (!pcursor) {
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);
int ret = m_batch.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret == DB_NOTFOUND) {
break;
}
if (ret != 0) {
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;
CWalletTx wtx(nullptr /* pwallet */, MakeTransactionRef());
ssValue >> wtx;
txIds.push_back(txid);
vWtx.push_back(wtx);
}
}
pcursor->close();
} catch (const boost::thread_interrupted &) {
throw;
} catch (...) {
result = DBErrors::CORRUPT;
}
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::vector<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;
}
DBErrors WalletBatch::ZapWalletTx(std::vector<CWalletTx> &vWtx) {
// Build list of wallet TXs.
std::vector<TxId> txIds;
DBErrors err = FindWalletTx(txIds, vWtx);
if (err != DBErrors::LOAD_OK) {
return err;
}
// Erase each wallet TX.
for (const TxId &txid : txIds) {
if (!EraseTx(txid)) {
return DBErrors::CORRUPT;
}
}
return DBErrors::LOAD_OK;
}
void MaybeCompactWalletDB() {
static std::atomic<bool> fOneThread;
if (fOneThread.exchange(true)) {
return;
}
if (!gArgs.GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET)) {
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 (BerkeleyBatch::PeriodicFlush(dbh)) {
dbh.nLastFlushed = nUpdateCounter;
}
}
}
fOneThread = false;
}
-//
-// Try to (very carefully!) recover wallet file if there is a problem.
-//
-bool WalletBatch::Recover(const fs::path &wallet_path, void *callbackDataIn,
- bool (*recoverKVcallback)(void *callbackData,
- CDataStream ssKey,
- CDataStream ssValue),
- std::string &out_backup_filename) {
- return BerkeleyBatch::Recover(wallet_path, callbackDataIn,
- recoverKVcallback, out_backup_filename);
-}
-
-bool WalletBatch::Recover(const fs::path &wallet_path,
- std::string &out_backup_filename) {
- // recover without a key filter callback
- // results in recovering all record types
- return WalletBatch::Recover(wallet_path, nullptr, nullptr,
- out_backup_filename);
-}
-
-bool WalletBatch::RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey,
- CDataStream ssValue) {
+bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey,
+ CDataStream ssValue) {
CWallet *dummyWallet = reinterpret_cast<CWallet *>(callbackData);
std::string strType, strErr;
bool fReadOK;
{
// Required in LoadKeyMetadata():
LOCK(dummyWallet->cs_wallet);
fReadOK = ReadKeyValue(dummyWallet, ssKey, ssValue, strType, strErr);
}
- if (!IsKeyType(strType) && strType != DBKeys::HDCHAIN) {
+ if (!WalletBatch::IsKeyType(strType) && strType != DBKeys::HDCHAIN) {
return false;
}
if (!fReadOK) {
LogPrintf("WARNING: WalletBatch::Recover skipping %s: %s\n", strType,
strErr);
return false;
}
return true;
}
bool WalletBatch::VerifyEnvironment(const fs::path &wallet_path,
bilingual_str &errorStr) {
return BerkeleyBatch::VerifyEnvironment(wallet_path, errorStr);
}
bool WalletBatch::VerifyDatabaseFile(const fs::path &wallet_path,
bilingual_str &errorStr) {
return BerkeleyBatch::VerifyDatabaseFile(wallet_path, errorStr);
}
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();
}
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 5079013cb..d3ba8b591 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,297 +1,287 @@
// 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/db.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;
/** Backend-agnostic database type. */
using WalletDatabase = BerkeleyDatabase;
/** Error statuses for the wallet database */
enum class DBErrors {
LOAD_OK,
CORRUPT,
NONCRITICAL_ERROR,
TOO_NEW,
LOAD_FAIL,
NEED_REWRITE
};
/* 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(); }
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(nExternalChainCounter);
READWRITE(seed_id);
if (this->nVersion >= VERSION_HD_CHAIN_SPLIT) {
READWRITE(nInternalChainCounter);
}
}
void SetNull() {
nVersion = CHDChain::CURRENT_VERSION;
nExternalChainCounter = 0;
nInternalChainCounter = 0;
seed_id.SetNull();
}
};
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_;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(nCreateTime);
if (this->nVersion >= VERSION_WITH_HDDATA) {
READWRITE(hdKeypath);
READWRITE(hd_seed_id);
}
if (this->nVersion >= VERSION_WITH_KEY_ORIGIN) {
READWRITE(key_origin);
READWRITE(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, 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::vector<CWalletTx> &vWtx);
DBErrors ZapWalletTx(std::vector<CWalletTx> &vWtx);
DBErrors ZapSelectTx(std::vector<TxId> &txIdsIn,
std::vector<TxId> &txIdsOut);
- /* Try to (very carefully!) recover wallet database (with a possible key
- * type filter) */
- static bool Recover(const fs::path &wallet_path, void *callbackDataIn,
- bool (*recoverKVcallback)(void *callbackData,
- CDataStream ssKey,
- CDataStream ssValue),
- std::string &out_backup_filename);
- /* Recover convenience-function to bypass the key filter callback, called
- * when verify fails, recovers everything */
- static bool Recover(const fs::path &wallet_path,
- std::string &out_backup_filename);
- /* Recover filter (used as callback), will only let keys (cryptographical
- * keys) as KV/key-type pass through */
- static bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey,
- CDataStream ssValue);
/* Function to determine if a certain KV/key-type is a key (cryptographical
* key) type */
static bool IsKeyType(const std::string &strType);
/* verifies the database environment */
static bool VerifyEnvironment(const fs::path &wallet_path,
bilingual_str &errorStr);
/* verifies the database file */
static bool VerifyDatabaseFile(const fs::path &wallet_path,
bilingual_str &errorStr);
//! 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:
BerkeleyBatch m_batch;
WalletDatabase &m_database;
};
//! Compacts BDB state so that wallet.dat is self-contained (if there are
//! changes)
void MaybeCompactWalletDB();
//! 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);
+/* Recover filter (used as callback), will only let keys (cryptographical keys)
+ * as KV/key-type pass through */
+bool RecoverKeysOnlyFilter(void *callbackData, CDataStream ssKey,
+ CDataStream ssValue);
+
#endif // BITCOIN_WALLET_WALLETDB_H
diff --git a/src/wallet/wallettool.cpp b/src/wallet/wallettool.cpp
index 88b683afa..b23984b1b 100644
--- a/src/wallet/wallettool.cpp
+++ b/src/wallet/wallettool.cpp
@@ -1,196 +1,195 @@
// Copyright (c) 2016-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 <chainparams.h>
#include <fs.h>
#include <util/system.h>
#include <util/translation.h>
#include <wallet/wallet.h>
#include <wallet/walletutil.h>
#include <stdexcept>
namespace WalletTool {
// The standard wallet deleter function blocks on the validation interface
// queue, which doesn't exist for the bitcoin-wallet. Define our own
// deleter here.
static void WalletToolReleaseWallet(CWallet *wallet) {
wallet->WalletLogPrintf("Releasing wallet\n");
wallet->Flush(true);
delete wallet;
}
static std::shared_ptr<CWallet> CreateWallet(const std::string &name,
const fs::path &path) {
if (fs::exists(path)) {
tfm::format(std::cerr, "Error: File exists already\n");
return nullptr;
}
// dummy chain interface
std::shared_ptr<CWallet> wallet_instance(
new CWallet(Params(), nullptr /* chain */, WalletLocation(name),
WalletDatabase::Create(path)),
WalletToolReleaseWallet);
LOCK(wallet_instance->cs_wallet);
bool first_run = true;
DBErrors load_wallet_ret = wallet_instance->LoadWallet(first_run);
if (load_wallet_ret != DBErrors::LOAD_OK) {
tfm::format(std::cerr, "Error creating %s", name);
return nullptr;
}
wallet_instance->SetMinVersion(FEATURE_HD_SPLIT);
// generate a new HD seed
auto spk_man = wallet_instance->GetOrCreateLegacyScriptPubKeyMan();
CPubKey seed = spk_man->GenerateNewSeed();
spk_man->SetHDSeed(seed);
tfm::format(std::cout, "Topping up keypool...\n");
wallet_instance->TopUpKeyPool();
return wallet_instance;
}
static std::shared_ptr<CWallet> LoadWallet(const std::string &name,
const fs::path &path) {
if (!fs::exists(path)) {
tfm::format(std::cerr, "Error: Wallet files does not exist\n");
return nullptr;
}
// dummy chain interface
std::shared_ptr<CWallet> wallet_instance(
new CWallet(Params(), nullptr /* chain */, WalletLocation(name),
WalletDatabase::Create(path)),
WalletToolReleaseWallet);
DBErrors load_wallet_ret;
try {
bool first_run;
load_wallet_ret = wallet_instance->LoadWallet(first_run);
} catch (const std::runtime_error &) {
tfm::format(
std::cerr,
"Error loading %s. Is wallet being used by another process?\n",
name);
return nullptr;
}
if (load_wallet_ret != DBErrors::LOAD_OK) {
wallet_instance = nullptr;
if (load_wallet_ret == DBErrors::CORRUPT) {
tfm::format(std::cerr, "Error loading %s: Wallet corrupted", name);
return nullptr;
} else if (load_wallet_ret == DBErrors::NONCRITICAL_ERROR) {
tfm::format(
std::cerr,
"Error reading %s! All keys read correctly, but transaction "
"data or address book entries might be missing or incorrect.",
name);
} else if (load_wallet_ret == DBErrors::TOO_NEW) {
tfm::format(std::cerr,
"Error loading %s: Wallet requires newer version of %s",
name, PACKAGE_NAME);
return nullptr;
} else if (load_wallet_ret == DBErrors::NEED_REWRITE) {
tfm::format(std::cerr,
"Wallet needed to be rewritten: restart %s to complete",
PACKAGE_NAME);
return nullptr;
} else {
tfm::format(std::cerr, "Error loading %s", name);
return nullptr;
}
}
return wallet_instance;
}
static void WalletShowInfo(CWallet *wallet_instance) {
LOCK(wallet_instance->cs_wallet);
tfm::format(std::cout, "Wallet info\n===========\n");
tfm::format(std::cout, "Encrypted: %s\n",
wallet_instance->IsCrypted() ? "yes" : "no");
tfm::format(std::cout, "HD (hd seed available): %s\n",
wallet_instance->IsHDEnabled() ? "yes" : "no");
tfm::format(std::cout, "Keypool Size: %u\n",
wallet_instance->GetKeyPoolSize());
tfm::format(std::cout, "Transactions: %zu\n",
wallet_instance->mapWallet.size());
tfm::format(std::cout, "Address Book: %zu\n",
wallet_instance->m_address_book.size());
}
static bool SalvageWallet(const fs::path &path) {
// Create a Database handle to allow for the db to be initialized before
// recovery
std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(path);
// Initialize the environment before recovery
bilingual_str error_string;
try {
WalletBatch::VerifyEnvironment(path, error_string);
} catch (const fs::filesystem_error &e) {
error_string =
Untranslated(strprintf("Error loading wallet. %s",
fsbridge::get_filesystem_error_message(e)));
}
if (!error_string.original.empty()) {
tfm::format(std::cerr, "Failed to open wallet for salvage :%s\n",
error_string.original);
return false;
}
// Perform the recovery
CWallet dummy_wallet(Params(), nullptr, WalletLocation(),
WalletDatabase::CreateDummy());
std::string backup_filename;
- return WalletBatch::Recover(path, (void *)&dummy_wallet,
- WalletBatch::RecoverKeysOnlyFilter,
- backup_filename);
+ return RecoverDatabaseFile(path, (void *)&dummy_wallet,
+ RecoverKeysOnlyFilter, backup_filename);
}
bool ExecuteWalletToolFunc(const std::string &command,
const std::string &name) {
fs::path path = fs::absolute(name, GetWalletDir());
if (command == "create") {
std::shared_ptr<CWallet> wallet_instance = CreateWallet(name, path);
if (wallet_instance) {
WalletShowInfo(wallet_instance.get());
wallet_instance->Flush(true);
}
} else if (command == "info" || command == "salvage") {
if (!fs::exists(path)) {
tfm::format(std::cerr, "Error: no wallet file at %s\n", name);
return false;
}
bilingual_str error;
if (!WalletBatch::VerifyEnvironment(path, error)) {
tfm::format(std::cerr,
"%s\nError loading %s. Is wallet being used by other "
"process?\n",
error.original, name);
return false;
}
if (command == "info") {
std::shared_ptr<CWallet> wallet_instance = LoadWallet(name, path);
if (!wallet_instance) {
return false;
}
WalletShowInfo(wallet_instance.get());
wallet_instance->Flush(true);
} else if (command == "salvage") {
return SalvageWallet(path);
}
} else {
tfm::format(std::cerr, "Invalid command: %s\n", command);
return false;
}
return true;
}
} // namespace WalletTool
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Apr 27, 11:20 (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5573394
Default Alt Text
(104 KB)
Attached To
rABC Bitcoin ABC
Event Timeline
Log In to Comment