lbrycrd/src/wallet/db.h

417 lines
12 KiB
C
Raw Normal View History

// Copyright (c) 2009-2010 Satoshi Nakamoto
2018-07-27 00:36:45 +02:00
// 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.
#ifndef BITCOIN_WALLET_DB_H
#define BITCOIN_WALLET_DB_H
#include <clientversion.h>
#include <fs.h>
#include <serialize.h>
#include <streams.h>
#include <sync.h>
scripted-diff: Move util files to separate directory. -BEGIN VERIFY SCRIPT- mkdir -p src/util git mv src/util.h src/util/system.h git mv src/util.cpp src/util/system.cpp git mv src/utilmemory.h src/util/memory.h git mv src/utilmoneystr.h src/util/moneystr.h git mv src/utilmoneystr.cpp src/util/moneystr.cpp git mv src/utilstrencodings.h src/util/strencodings.h git mv src/utilstrencodings.cpp src/util/strencodings.cpp git mv src/utiltime.h src/util/time.h git mv src/utiltime.cpp src/util/time.cpp sed -i 's/<util\.h>/<util\/system\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utilmemory\.h>/<util\/memory\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utilmoneystr\.h>/<util\/moneystr\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utilstrencodings\.h>/<util\/strencodings\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/<utiltime\.h>/<util\/time\.h>/g' $(git ls-files 'src/*.h' 'src/*.cpp') sed -i 's/BITCOIN_UTIL_H/BITCOIN_UTIL_SYSTEM_H/g' src/util/system.h sed -i 's/BITCOIN_UTILMEMORY_H/BITCOIN_UTIL_MEMORY_H/g' src/util/memory.h sed -i 's/BITCOIN_UTILMONEYSTR_H/BITCOIN_UTIL_MONEYSTR_H/g' src/util/moneystr.h sed -i 's/BITCOIN_UTILSTRENCODINGS_H/BITCOIN_UTIL_STRENCODINGS_H/g' src/util/strencodings.h sed -i 's/BITCOIN_UTILTIME_H/BITCOIN_UTIL_TIME_H/g' src/util/time.h sed -i 's/ util\.\(h\|cpp\)/ util\/system\.\1/g' src/Makefile.am sed -i 's/utilmemory\.\(h\|cpp\)/util\/memory\.\1/g' src/Makefile.am sed -i 's/utilmoneystr\.\(h\|cpp\)/util\/moneystr\.\1/g' src/Makefile.am sed -i 's/utilstrencodings\.\(h\|cpp\)/util\/strencodings\.\1/g' src/Makefile.am sed -i 's/utiltime\.\(h\|cpp\)/util\/time\.\1/g' src/Makefile.am sed -i 's/-> util ->/-> util\/system ->/' test/lint/lint-circular-dependencies.sh sed -i 's/src\/util\.cpp/src\/util\/system\.cpp/g' test/lint/lint-format-strings.py test/lint/lint-locale-dependence.sh sed -i 's/src\/utilmoneystr\.cpp/src\/util\/moneystr\.cpp/g' test/lint/lint-locale-dependence.sh sed -i 's/src\/utilstrencodings\.\(h\|cpp\)/src\/util\/strencodings\.\1/g' test/lint/lint-locale-dependence.sh sed -i 's/src\\utilstrencodings\.cpp/src\\util\\strencodings\.cpp/' build_msvc/libbitcoinconsensus/libbitcoinconsensus.vcxproj -END VERIFY SCRIPT-
2018-10-23 00:51:11 +02:00
#include <util/system.h>
#include <version.h>
2017-06-12 20:39:48 +02:00
#include <atomic>
#include <map>
#include <memory>
#include <string>
2018-09-25 15:56:16 +02:00
#include <unordered_map>
#include <vector>
#include <db_cxx.h>
static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
static const bool DEFAULT_WALLET_PRIVDB = true;
2018-09-25 15:56:16 +02:00
struct WalletDatabaseFileId {
u_int8_t value[DB_FILE_ID_LEN];
bool operator==(const WalletDatabaseFileId& rhs) const;
};
class BerkeleyDatabase;
class BerkeleyEnvironment
{
private:
bool fDbEnvInit;
2012-05-22 21:51:13 +02:00
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:
2017-08-09 16:24:12 +02:00
std::unique_ptr<DbEnv> dbenv;
2012-05-18 08:49:50 +02:00
std::map<std::string, int> mapFileUseCount;
std::map<std::string, std::reference_wrapper<BerkeleyDatabase>> m_databases;
2018-09-25 15:56:16 +02:00
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();
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; }
/**
* Verify that database file strFile is OK. If it is not,
* call the callback to try to recover.
* This must be called BEFORE strFile is opened.
* Returns true if strFile is OK.
*/
scripted-diff: Convert 11 enums into scoped enums (C++11) -BEGIN VERIFY SCRIPT- sed -i 's/enum DBErrors/enum class DBErrors/g' src/wallet/walletdb.h git grep -l DB_ | xargs sed -i 's/DB_\(LOAD_OK\|CORRUPT\|NONCRITICAL_ERROR\|TOO_NEW\|LOAD_FAIL\|NEED_REWRITE\)/DBErrors::\1/g' sed -i 's/^ DBErrors::/ /g' src/wallet/walletdb.h sed -i 's/enum VerifyResult/enum class VerifyResult/g' src/wallet/db.h sed -i 's/\(VERIFY_OK\|RECOVER_OK\|RECOVER_FAIL\)/VerifyResult::\1/g' src/wallet/db.cpp sed -i 's/enum ThresholdState/enum class ThresholdState/g' src/versionbits.h git grep -l THRESHOLD_ | xargs sed -i 's/THRESHOLD_\(DEFINED\|STARTED\|LOCKED_IN\|ACTIVE\|FAILED\)/ThresholdState::\1/g' sed -i 's/^ ThresholdState::/ /g' src/versionbits.h sed -i 's/enum SigVersion/enum class SigVersion/g' src/script/interpreter.h git grep -l SIGVERSION_ | xargs sed -i 's/SIGVERSION_\(BASE\|WITNESS_V0\)/SigVersion::\1/g' sed -i 's/^ SigVersion::/ /g' src/script/interpreter.h sed -i 's/enum RetFormat {/enum class RetFormat {/g' src/rest.cpp sed -i 's/RF_\(UNDEF\|BINARY\|HEX\|JSON\)/RetFormat::\1/g' src/rest.cpp sed -i 's/^ RetFormat::/ /g' src/rest.cpp sed -i 's/enum HelpMessageMode {/enum class HelpMessageMode {/g' src/init.h git grep -l HMM_ | xargs sed -i 's/HMM_BITCOIN/HelpMessageMode::BITCOIN/g' sed -i 's/^ HelpMessageMode::/ /g' src/init.h sed -i 's/enum FeeEstimateHorizon/enum class FeeEstimateHorizon/g' src/policy/fees.h sed -i 's/enum RBFTransactionState/enum class RBFTransactionState/g' src/policy/rbf.h git grep -l RBF_ | xargs sed -i 's/RBF_TRANSACTIONSTATE_\(UNKNOWN\|REPLACEABLE_BIP125\|FINAL\)/RBFTransactionState::\1/g' sed -i 's/^ RBFTransactionState::/ /g' src/policy/rbf.h sed -i 's/enum BlockSource {/enum class BlockSource {/g' src/qt/clientmodel.h git grep -l BLOCK_SOURCE_ | xargs sed -i 's/BLOCK_SOURCE_\(NONE\|REINDEX\|DISK\|NETWORK\)/BlockSource::\1/g' sed -i 's/^ BlockSource::/ /g' src/qt/clientmodel.h sed -i 's/enum FlushStateMode {/enum class FlushStateMode {/g' src/validation.cpp sed -i 's/FLUSH_STATE_\(NONE\|IF_NEEDED\|PERIODIC\|ALWAYS\)/FlushStateMode::\1/g' src/validation.cpp sed -i 's/^ FlushStateMode::/ /g' src/validation.cpp sed -i 's/enum WitnessMode {/enum class WitnessMode {/g' src/test/script_tests.cpp sed -i 's/WITNESS_\(NONE\|PKH\|SH\)/WitnessMode::\1/g' src/test/script_tests.cpp sed -i 's/^ WitnessMode::/ /g' src/test/script_tests.cpp -END VERIFY SCRIPT-
2018-03-09 15:03:40 +01:00
enum class VerifyResult { VERIFY_OK,
RECOVER_OK,
RECOVER_FAIL };
typedef bool (*recoverFunc_type)(const fs::path& file_path, std::string& out_backup_filename);
VerifyResult Verify(const std::string& strFile, recoverFunc_type recoverFunc, std::string& out_backup_filename);
/**
* Salvage data from a file that Verify says is bad.
* fAggressive sets the DB_AGGRESSIVE flag (see berkeley DB->verify() method documentation).
* Appends binary key/value pairs to vResult, returns true if successful.
* NOTE: reads the entire database into memory, so cannot be used
* for huge databases.
*/
typedef std::pair<std::vector<unsigned char>, std::vector<unsigned char> > KeyValPair;
bool Salvage(const std::string& strFile, bool fAggressive, std::vector<KeyValPair>& vResult);
bool Open(bool retry);
void Close();
void Flush(bool fShutdown);
void CheckpointLSN(const std::string& strFile);
2012-05-18 08:49:50 +02:00
void CloseDb(const std::string& strFile);
void ReloadDbEnv();
2012-05-18 08:49:50 +02:00
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> env, std::string filename) :
nUpdateCounter(0), nLastSeen(0), nLastFlushed(0), nLastWalletUpdate(0), env(std::move(env)), 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 MakeUnique<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 MakeUnique<BerkeleyDatabase>();
}
/** Return object for accessing temporary in-memory database. */
static std::unique_ptr<BerkeleyDatabase> CreateMock()
{
return MakeUnique<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);
/** 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() { return env == nullptr; }
};
2012-03-26 16:48:23 +02:00
/** 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, std::string& errorStr);
/* verifies the database file */
static bool VerifyDatabaseFile(const fs::path& file_path, std::string& warningStr, std::string& errorStr, BerkeleyEnvironment::recoverFunc_type recoverFunc);
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);
}
bool ReadVersion(int& nVersion)
{
nVersion = 0;
return Read(std::string("version"), nVersion);
}
bool WriteVersion(int nVersion)
{
return Write(std::string("version"), nVersion);
}
2011-11-10 21:29:23 +01:00
bool static Rewrite(BerkeleyDatabase& database, const char* pszSkip = nullptr);
};
#endif // BITCOIN_WALLET_DB_H