2015-12-13 14:51:43 +01:00
|
|
|
// Copyright (c) 2012-2015 The Bitcoin Core developers
|
2014-10-31 01:43:19 +01:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
2012-09-03 19:05:30 +02:00
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
2013-11-06 02:58:43 +01:00
|
|
|
|
2015-10-23 03:33:06 +02:00
|
|
|
#ifndef BITCOIN_DBWRAPPER_H
|
|
|
|
#define BITCOIN_DBWRAPPER_H
|
2012-09-03 19:05:30 +02:00
|
|
|
|
2020-03-19 23:46:56 +01:00
|
|
|
#include <clientversion.h>
|
|
|
|
#include <fs.h>
|
|
|
|
#include <serialize.h>
|
|
|
|
#include <streams.h>
|
2021-06-27 08:33:13 +02:00
|
|
|
#include <util/system.h>
|
|
|
|
#include <util/strencodings.h>
|
2020-03-19 23:46:56 +01:00
|
|
|
#include <version.h>
|
2012-09-03 19:05:30 +02:00
|
|
|
|
2018-02-14 12:04:24 +01:00
|
|
|
#include <typeindex>
|
|
|
|
|
2012-09-03 19:05:30 +02:00
|
|
|
#include <leveldb/db.h>
|
|
|
|
#include <leveldb/write_batch.h>
|
|
|
|
|
2016-10-29 04:12:39 +02:00
|
|
|
static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64;
|
|
|
|
static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024;
|
|
|
|
|
2015-10-23 03:02:20 +02:00
|
|
|
class dbwrapper_error : public std::runtime_error
|
2013-01-29 01:44:19 +01:00
|
|
|
{
|
|
|
|
public:
|
2017-08-17 22:59:56 +02:00
|
|
|
explicit dbwrapper_error(const std::string& msg) : std::runtime_error(msg) {}
|
2013-01-29 01:44:19 +01:00
|
|
|
};
|
|
|
|
|
2016-04-25 12:44:58 +02:00
|
|
|
class CDBWrapper;
|
|
|
|
|
|
|
|
/** These should be considered an implementation detail of the specific database.
|
|
|
|
*/
|
|
|
|
namespace dbwrapper_private {
|
|
|
|
|
|
|
|
/** Handle database error by throwing dbwrapper_error exception.
|
|
|
|
*/
|
|
|
|
void HandleError(const leveldb::Status& status);
|
|
|
|
|
|
|
|
/** Work around circular dependency, as well as for testing in dbwrapper_tests.
|
|
|
|
* Database obfuscation should be considered an implementation detail of the
|
|
|
|
* specific database.
|
|
|
|
*/
|
|
|
|
const std::vector<unsigned char>& GetObfuscateKey(const CDBWrapper &w);
|
|
|
|
|
|
|
|
};
|
2013-01-29 01:44:19 +01:00
|
|
|
|
2015-10-23 03:02:20 +02:00
|
|
|
/** Batch of changes queued to be written to a CDBWrapper */
|
|
|
|
class CDBBatch
|
2012-09-03 19:05:30 +02:00
|
|
|
{
|
2015-10-23 03:02:20 +02:00
|
|
|
friend class CDBWrapper;
|
2012-09-03 19:05:30 +02:00
|
|
|
|
|
|
|
private:
|
2016-04-25 12:44:58 +02:00
|
|
|
const CDBWrapper &parent;
|
2012-09-03 19:05:30 +02:00
|
|
|
leveldb::WriteBatch batch;
|
|
|
|
|
2016-12-16 21:24:51 +01:00
|
|
|
CDataStream ssKey;
|
|
|
|
CDataStream ssValue;
|
|
|
|
|
2017-06-02 00:47:58 +02:00
|
|
|
size_t size_estimate;
|
|
|
|
|
2012-09-03 19:05:30 +02:00
|
|
|
public:
|
2015-09-08 00:22:23 +02:00
|
|
|
/**
|
2017-06-02 00:47:58 +02:00
|
|
|
* @param[in] parent CDBWrapper that this batch is to be submitted to
|
2015-09-08 00:22:23 +02:00
|
|
|
*/
|
2017-08-17 22:59:56 +02:00
|
|
|
explicit CDBBatch(const CDBWrapper &_parent) : parent(_parent), ssKey(SER_DISK, CLIENT_VERSION), ssValue(SER_DISK, CLIENT_VERSION), size_estimate(0) { };
|
2017-06-02 00:47:58 +02:00
|
|
|
|
|
|
|
void Clear()
|
|
|
|
{
|
|
|
|
batch.Clear();
|
|
|
|
size_estimate = 0;
|
|
|
|
}
|
2015-09-08 00:22:23 +02:00
|
|
|
|
2014-09-19 19:21:46 +02:00
|
|
|
template <typename K, typename V>
|
|
|
|
void Write(const K& key, const V& value)
|
|
|
|
{
|
2016-10-29 04:12:39 +02:00
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
2012-09-03 19:05:30 +02:00
|
|
|
ssKey << key;
|
2019-04-04 08:19:26 +02:00
|
|
|
Write(ssKey, value);
|
|
|
|
ssKey.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename V>
|
|
|
|
void Write(const CDataStream& _ssKey, const V& value)
|
|
|
|
{
|
|
|
|
leveldb::Slice slKey(_ssKey.data(), _ssKey.size());
|
2012-09-03 19:05:30 +02:00
|
|
|
|
2016-10-29 04:12:39 +02:00
|
|
|
ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE);
|
2012-09-03 19:05:30 +02:00
|
|
|
ssValue << value;
|
2016-04-25 12:44:58 +02:00
|
|
|
ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
|
2017-01-09 17:39:08 +01:00
|
|
|
leveldb::Slice slValue(ssValue.data(), ssValue.size());
|
2012-09-03 19:05:30 +02:00
|
|
|
|
|
|
|
batch.Put(slKey, slValue);
|
2017-06-02 00:47:58 +02:00
|
|
|
// - varint: key length (1 byte up to 127B, 2 bytes up to 16383B, ...)
|
|
|
|
// - byte[]: key
|
|
|
|
// - varint: value length
|
|
|
|
// - byte[]: value
|
|
|
|
// The formula below assumes the key and value are both less than 16k.
|
|
|
|
size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size();
|
2016-12-16 21:24:51 +01:00
|
|
|
ssValue.clear();
|
2012-09-03 19:05:30 +02:00
|
|
|
}
|
|
|
|
|
2014-09-19 19:21:46 +02:00
|
|
|
template <typename K>
|
|
|
|
void Erase(const K& key)
|
|
|
|
{
|
2016-10-29 04:12:39 +02:00
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
2012-09-03 19:05:30 +02:00
|
|
|
ssKey << key;
|
2019-04-04 08:19:26 +02:00
|
|
|
Erase(ssKey);
|
|
|
|
ssKey.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Erase(const CDataStream& _ssKey) {
|
|
|
|
leveldb::Slice slKey(_ssKey.data(), _ssKey.size());
|
2012-09-03 19:05:30 +02:00
|
|
|
|
|
|
|
batch.Delete(slKey);
|
2017-06-02 00:47:58 +02:00
|
|
|
// - byte: header
|
|
|
|
// - varint: key length
|
|
|
|
// - byte[]: key
|
|
|
|
// The formula below assumes the key is less than 16kB.
|
|
|
|
size_estimate += 2 + (slKey.size() > 127) + slKey.size();
|
2012-09-03 19:05:30 +02:00
|
|
|
}
|
2017-06-02 00:47:58 +02:00
|
|
|
|
|
|
|
size_t SizeEstimate() const { return size_estimate; }
|
2012-09-03 19:05:30 +02:00
|
|
|
};
|
2015-10-23 02:49:02 +02:00
|
|
|
|
2015-10-23 03:02:20 +02:00
|
|
|
class CDBIterator
|
2015-10-08 02:12:24 +02:00
|
|
|
{
|
|
|
|
private:
|
2016-04-25 12:44:58 +02:00
|
|
|
const CDBWrapper &parent;
|
2015-10-08 02:12:24 +02:00
|
|
|
leveldb::Iterator *piter;
|
|
|
|
|
|
|
|
public:
|
2015-10-08 09:44:10 +02:00
|
|
|
|
|
|
|
/**
|
2016-08-10 07:49:01 +02:00
|
|
|
* @param[in] _parent Parent CDBWrapper instance.
|
|
|
|
* @param[in] _piter The original leveldb iterator.
|
2015-10-08 09:44:10 +02:00
|
|
|
*/
|
2016-08-10 07:49:01 +02:00
|
|
|
CDBIterator(const CDBWrapper &_parent, leveldb::Iterator *_piter) :
|
|
|
|
parent(_parent), piter(_piter) { };
|
2015-10-23 03:02:20 +02:00
|
|
|
~CDBIterator();
|
2015-10-08 02:12:24 +02:00
|
|
|
|
2017-08-16 02:09:10 +02:00
|
|
|
bool Valid() const;
|
2012-09-03 19:05:30 +02:00
|
|
|
|
2015-10-08 02:12:24 +02:00
|
|
|
void SeekToFirst();
|
|
|
|
|
|
|
|
template<typename K> void Seek(const K& key) {
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
2016-10-29 04:12:39 +02:00
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
2015-10-08 02:12:24 +02:00
|
|
|
ssKey << key;
|
2019-04-04 08:19:26 +02:00
|
|
|
Seek(ssKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Seek(const CDataStream& ssKey) {
|
2017-01-09 17:39:08 +01:00
|
|
|
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
2015-10-08 02:12:24 +02:00
|
|
|
piter->Seek(slKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Next();
|
|
|
|
|
|
|
|
template<typename K> bool GetKey(K& key) {
|
|
|
|
try {
|
2019-04-04 10:03:56 +02:00
|
|
|
CDataStream ssKey = GetKey();
|
2015-10-08 02:12:24 +02:00
|
|
|
ssKey >> key;
|
2015-10-27 17:39:42 +01:00
|
|
|
} catch (const std::exception&) {
|
2015-10-08 02:12:24 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-04-04 10:03:56 +02:00
|
|
|
CDataStream GetKey() {
|
|
|
|
leveldb::Slice slKey = piter->key();
|
|
|
|
return CDataStream(slKey.data(), slKey.data() + slKey.size(), SER_DISK, CLIENT_VERSION);
|
|
|
|
}
|
|
|
|
|
2015-10-08 02:12:24 +02:00
|
|
|
unsigned int GetKeySize() {
|
|
|
|
return piter->key().size();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename V> bool GetValue(V& value) {
|
|
|
|
leveldb::Slice slValue = piter->value();
|
|
|
|
try {
|
|
|
|
CDataStream ssValue(slValue.data(), slValue.data() + slValue.size(), SER_DISK, CLIENT_VERSION);
|
2016-04-25 12:44:58 +02:00
|
|
|
ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
|
2015-10-08 02:12:24 +02:00
|
|
|
ssValue >> value;
|
2015-10-27 17:39:42 +01:00
|
|
|
} catch (const std::exception&) {
|
2015-10-08 02:12:24 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int GetValueSize() {
|
|
|
|
return piter->value().size();
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
2015-10-23 02:49:02 +02:00
|
|
|
|
2015-10-23 03:02:20 +02:00
|
|
|
class CDBWrapper
|
2012-09-03 19:05:30 +02:00
|
|
|
{
|
2016-04-25 12:44:58 +02:00
|
|
|
friend const std::vector<unsigned char>& dbwrapper_private::GetObfuscateKey(const CDBWrapper &w);
|
2012-09-03 19:05:30 +02:00
|
|
|
private:
|
2019-08-06 05:08:33 +02:00
|
|
|
//! custom environment this database is using (may be nullptr in case of default environment)
|
2014-09-19 19:21:46 +02:00
|
|
|
leveldb::Env* penv;
|
2012-09-03 19:05:30 +02:00
|
|
|
|
2014-10-31 01:43:19 +01:00
|
|
|
//! database options used
|
2012-09-03 19:05:30 +02:00
|
|
|
leveldb::Options options;
|
|
|
|
|
2014-10-31 01:43:19 +01:00
|
|
|
//! options used when reading from the database
|
2012-09-03 19:05:30 +02:00
|
|
|
leveldb::ReadOptions readoptions;
|
|
|
|
|
2014-10-31 01:43:19 +01:00
|
|
|
//! options used when iterating over values of the database
|
2012-09-03 19:05:30 +02:00
|
|
|
leveldb::ReadOptions iteroptions;
|
|
|
|
|
2014-10-31 01:43:19 +01:00
|
|
|
//! options used when writing to the database
|
2012-09-03 19:05:30 +02:00
|
|
|
leveldb::WriteOptions writeoptions;
|
|
|
|
|
2014-10-31 01:43:19 +01:00
|
|
|
//! options used when sync writing to the database
|
2012-09-03 19:05:30 +02:00
|
|
|
leveldb::WriteOptions syncoptions;
|
|
|
|
|
2014-10-31 01:43:19 +01:00
|
|
|
//! the database itself
|
2014-09-19 19:21:46 +02:00
|
|
|
leveldb::DB* pdb;
|
2012-09-03 19:05:30 +02:00
|
|
|
|
Merge #12604: Add DynamicMemoryUsage() to CDBWrapper to estimate LevelDB memory use
741f0177c Add DynamicMemoryUsage() to LevelDB (Evan Klitzke)
Pull request description:
This adds a new method `CDBWrapper::DynamicMemoryUsage()` similar to Bitcoin's existing methods of the same name. It's implemented by asking LevelDB for the information, and then parsing the string response. I've also added logging to `CDBWrapper::WriteBatch()` to track this information:
```
$ tail -f ~/.bitcoin/testnet3/debug.log | grep WriteBatch
2018-03-05 19:34:55 WriteBatch memory usage: db=chainstate, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:17 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:17 WriteBatch memory usage: db=chainstate, before=0.0MiB, after=8.0MiB
2018-03-05 19:35:22 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:22 WriteBatch memory usage: db=chainstate, before=8.0MiB, after=17.0MiB
2018-03-05 19:35:26 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:27 WriteBatch memory usage: db=chainstate, before=9.0MiB, after=18.0MiB
2018-03-05 19:35:40 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:41 WriteBatch memory usage: db=chainstate, before=9.0MiB, after=7.0MiB
2018-03-05 19:35:52 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:52 WriteBatch memory usage: db=chainstate, before=7.0MiB, after=9.0MiB
^C
```
As LevelDB doesn't seem to provide a way to get the database name, I've also added a new `m_name` field to the `CDBWrapper`. This is necessary because we have multiple LevelDB databases (two now, and possibly more later, e.g. #11857).
I am using this information in other branches where I'm experimenting with changing LevelDB buffer sizes.
Tree-SHA512: 7ea8ff5484bb07ef806af17d000c74ccca27d2e0f6c3229e12d93818f00874553335d87428482bd8acbcae81ea35aef2a243326f9fccbfac25989323d24391b4
2018-03-06 16:27:14 +01:00
|
|
|
//! the name of this database
|
|
|
|
std::string m_name;
|
|
|
|
|
2015-09-08 00:22:23 +02:00
|
|
|
//! a key used for optional XOR-obfuscation of the database
|
|
|
|
std::vector<unsigned char> obfuscate_key;
|
|
|
|
|
|
|
|
//! the key under which the obfuscation key is stored
|
|
|
|
static const std::string OBFUSCATE_KEY_KEY;
|
2015-10-23 02:49:02 +02:00
|
|
|
|
2015-09-08 00:22:23 +02:00
|
|
|
//! the length of the obfuscate key in number of bytes
|
|
|
|
static const unsigned int OBFUSCATE_KEY_NUM_BYTES;
|
2015-10-23 02:49:02 +02:00
|
|
|
|
2015-09-08 00:22:23 +02:00
|
|
|
std::vector<unsigned char> CreateObfuscateKey() const;
|
|
|
|
|
2012-09-03 19:05:30 +02:00
|
|
|
public:
|
2015-09-08 00:22:23 +02:00
|
|
|
/**
|
|
|
|
* @param[in] path Location in the filesystem where leveldb data will be stored.
|
|
|
|
* @param[in] nCacheSize Configures various leveldb cache settings.
|
|
|
|
* @param[in] fMemory If true, use leveldb's memory environment.
|
|
|
|
* @param[in] fWipe If true, remove all existing data.
|
|
|
|
* @param[in] obfuscate If true, store data obfuscated via simple XOR. If false, XOR
|
|
|
|
* with a zero'd byte array.
|
|
|
|
*/
|
2017-04-06 20:19:21 +02:00
|
|
|
CDBWrapper(const fs::path& path, size_t nCacheSize, bool fMemory = false, bool fWipe = false, bool obfuscate = false);
|
2015-10-23 03:02:20 +02:00
|
|
|
~CDBWrapper();
|
2012-09-03 19:05:30 +02:00
|
|
|
|
2021-05-25 12:48:04 +02:00
|
|
|
CDBWrapper(const CDBWrapper&) = delete;
|
|
|
|
CDBWrapper& operator=(const CDBWrapper&) = delete;
|
|
|
|
|
2019-01-18 11:54:29 +01:00
|
|
|
template <typename K>
|
|
|
|
bool ReadDataStream(const K& key, CDataStream& ssValue) const
|
2014-09-19 19:21:46 +02:00
|
|
|
{
|
2012-09-03 19:05:30 +02:00
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
2016-10-29 04:12:39 +02:00
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
2012-09-03 19:05:30 +02:00
|
|
|
ssKey << key;
|
2019-04-04 08:19:26 +02:00
|
|
|
return ReadDataStream(ssKey, ssValue);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ReadDataStream(const CDataStream& ssKey, CDataStream& ssValue) const
|
|
|
|
{
|
2017-01-09 17:39:08 +01:00
|
|
|
leveldb::Slice slKey(ssKey.data(), ssKey.size());
|
2012-09-03 19:05:30 +02:00
|
|
|
|
|
|
|
std::string strValue;
|
|
|
|
leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
|
|
|
|
if (!status.ok()) {
|
|
|
|
if (status.IsNotFound())
|
|
|
|
return false;
|
2014-05-21 12:50:46 +02:00
|
|
|
LogPrintf("LevelDB read failure: %s\n", status.ToString());
|
2016-04-25 12:44:58 +02:00
|
|
|
dbwrapper_private::HandleError(status);
|
2012-09-03 19:05:30 +02:00
|
|
|
}
|
2019-01-18 11:54:29 +01:00
|
|
|
CDataStream ssValueTmp(strValue.data(), strValue.data() + strValue.size(), SER_DISK, CLIENT_VERSION);
|
|
|
|
ssValueTmp.Xor(obfuscate_key);
|
|
|
|
ssValue = std::move(ssValueTmp);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K, typename V>
|
|
|
|
bool Read(const K& key, V& value) const
|
2019-04-04 08:19:26 +02:00
|
|
|
{
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
|
|
ssKey << key;
|
|
|
|
return Read(ssKey, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <typename V>
|
|
|
|
bool Read(const CDataStream& ssKey, V& value) const
|
2019-01-18 11:54:29 +01:00
|
|
|
{
|
|
|
|
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
|
2019-04-04 08:19:26 +02:00
|
|
|
if (!ReadDataStream(ssKey, ssValue)) {
|
2019-01-18 11:54:29 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-09-03 19:05:30 +02:00
|
|
|
try {
|
|
|
|
ssValue >> value;
|
2014-09-19 19:21:46 +02:00
|
|
|
} catch (const std::exception&) {
|
2012-09-03 19:05:30 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-09-19 19:21:46 +02:00
|
|
|
template <typename K, typename V>
|
2016-04-25 12:44:58 +02:00
|
|
|
bool Write(const K& key, const V& value, bool fSync = false)
|
2014-09-19 19:21:46 +02:00
|
|
|
{
|
2016-04-25 12:44:58 +02:00
|
|
|
CDBBatch batch(*this);
|
2012-09-03 19:05:30 +02:00
|
|
|
batch.Write(key, value);
|
|
|
|
return WriteBatch(batch, fSync);
|
|
|
|
}
|
|
|
|
|
2014-09-19 19:21:46 +02:00
|
|
|
template <typename K>
|
2016-04-25 12:44:58 +02:00
|
|
|
bool Exists(const K& key) const
|
2014-09-19 19:21:46 +02:00
|
|
|
{
|
2012-09-03 19:05:30 +02:00
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
2016-10-29 04:12:39 +02:00
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
2012-09-03 19:05:30 +02:00
|
|
|
ssKey << key;
|
2019-04-04 08:19:26 +02:00
|
|
|
return Exists(ssKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Exists(const CDataStream& key) const
|
|
|
|
{
|
|
|
|
leveldb::Slice slKey(key.data(), key.size());
|
2012-09-03 19:05:30 +02:00
|
|
|
|
|
|
|
std::string strValue;
|
|
|
|
leveldb::Status status = pdb->Get(readoptions, slKey, &strValue);
|
|
|
|
if (!status.ok()) {
|
|
|
|
if (status.IsNotFound())
|
|
|
|
return false;
|
2014-05-21 12:50:46 +02:00
|
|
|
LogPrintf("LevelDB read failure: %s\n", status.ToString());
|
2016-04-25 12:44:58 +02:00
|
|
|
dbwrapper_private::HandleError(status);
|
2012-09-03 19:05:30 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2014-09-19 19:21:46 +02:00
|
|
|
template <typename K>
|
2016-04-25 12:44:58 +02:00
|
|
|
bool Erase(const K& key, bool fSync = false)
|
2014-09-19 19:21:46 +02:00
|
|
|
{
|
2016-04-25 12:44:58 +02:00
|
|
|
CDBBatch batch(*this);
|
2012-09-03 19:05:30 +02:00
|
|
|
batch.Erase(key);
|
|
|
|
return WriteBatch(batch, fSync);
|
|
|
|
}
|
|
|
|
|
2016-04-25 12:44:58 +02:00
|
|
|
bool WriteBatch(CDBBatch& batch, bool fSync = false);
|
2012-09-03 19:05:30 +02:00
|
|
|
|
Merge #12604: Add DynamicMemoryUsage() to CDBWrapper to estimate LevelDB memory use
741f0177c Add DynamicMemoryUsage() to LevelDB (Evan Klitzke)
Pull request description:
This adds a new method `CDBWrapper::DynamicMemoryUsage()` similar to Bitcoin's existing methods of the same name. It's implemented by asking LevelDB for the information, and then parsing the string response. I've also added logging to `CDBWrapper::WriteBatch()` to track this information:
```
$ tail -f ~/.bitcoin/testnet3/debug.log | grep WriteBatch
2018-03-05 19:34:55 WriteBatch memory usage: db=chainstate, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:17 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:17 WriteBatch memory usage: db=chainstate, before=0.0MiB, after=8.0MiB
2018-03-05 19:35:22 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:22 WriteBatch memory usage: db=chainstate, before=8.0MiB, after=17.0MiB
2018-03-05 19:35:26 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:27 WriteBatch memory usage: db=chainstate, before=9.0MiB, after=18.0MiB
2018-03-05 19:35:40 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:41 WriteBatch memory usage: db=chainstate, before=9.0MiB, after=7.0MiB
2018-03-05 19:35:52 WriteBatch memory usage: db=index, before=0.0MiB, after=0.0MiB
2018-03-05 19:35:52 WriteBatch memory usage: db=chainstate, before=7.0MiB, after=9.0MiB
^C
```
As LevelDB doesn't seem to provide a way to get the database name, I've also added a new `m_name` field to the `CDBWrapper`. This is necessary because we have multiple LevelDB databases (two now, and possibly more later, e.g. #11857).
I am using this information in other branches where I'm experimenting with changing LevelDB buffer sizes.
Tree-SHA512: 7ea8ff5484bb07ef806af17d000c74ccca27d2e0f6c3229e12d93818f00874553335d87428482bd8acbcae81ea35aef2a243326f9fccbfac25989323d24391b4
2018-03-06 16:27:14 +01:00
|
|
|
// Get an estimate of LevelDB memory usage (in bytes).
|
|
|
|
size_t DynamicMemoryUsage() const;
|
|
|
|
|
2015-10-23 03:02:20 +02:00
|
|
|
CDBIterator *NewIterator()
|
2015-10-08 02:12:24 +02:00
|
|
|
{
|
2016-04-25 12:44:58 +02:00
|
|
|
return new CDBIterator(*this, pdb->NewIterator(iteroptions));
|
2015-10-08 09:44:10 +02:00
|
|
|
}
|
2015-09-08 00:22:23 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return true if the database managed by this class contains no entries.
|
|
|
|
*/
|
|
|
|
bool IsEmpty();
|
2017-06-02 00:47:58 +02:00
|
|
|
|
|
|
|
template<typename K>
|
|
|
|
size_t EstimateSize(const K& key_begin, const K& key_end) const
|
|
|
|
{
|
|
|
|
CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION);
|
|
|
|
ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
|
|
ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
|
|
ssKey1 << key_begin;
|
|
|
|
ssKey2 << key_end;
|
|
|
|
leveldb::Slice slKey1(ssKey1.data(), ssKey1.size());
|
|
|
|
leveldb::Slice slKey2(ssKey2.data(), ssKey2.size());
|
|
|
|
uint64_t size = 0;
|
|
|
|
leveldb::Range range(slKey1, slKey2);
|
|
|
|
pdb->GetApproximateSizes(&range, 1, &size);
|
|
|
|
return size;
|
|
|
|
}
|
2017-08-01 13:01:59 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Compact a certain range of keys in the database.
|
|
|
|
*/
|
|
|
|
template<typename K>
|
|
|
|
void CompactRange(const K& key_begin, const K& key_end) const
|
|
|
|
{
|
|
|
|
CDataStream ssKey1(SER_DISK, CLIENT_VERSION), ssKey2(SER_DISK, CLIENT_VERSION);
|
|
|
|
ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
|
|
ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
|
|
ssKey1 << key_begin;
|
|
|
|
ssKey2 << key_end;
|
|
|
|
leveldb::Slice slKey1(ssKey1.data(), ssKey1.size());
|
|
|
|
leveldb::Slice slKey2(ssKey2.data(), ssKey2.size());
|
|
|
|
pdb->CompactRange(&slKey1, &slKey2);
|
|
|
|
}
|
|
|
|
|
2019-07-09 07:59:57 +02:00
|
|
|
void CompactFull() const
|
|
|
|
{
|
|
|
|
pdb->CompactRange(nullptr, nullptr);
|
|
|
|
}
|
|
|
|
|
2012-09-03 19:05:30 +02:00
|
|
|
};
|
|
|
|
|
2019-04-04 10:03:56 +02:00
|
|
|
template<typename CDBTransaction>
|
|
|
|
class CDBTransactionIterator
|
|
|
|
{
|
|
|
|
private:
|
|
|
|
CDBTransaction& transaction;
|
|
|
|
|
|
|
|
typedef typename std::remove_pointer<decltype(transaction.parent.NewIterator())>::type ParentIterator;
|
|
|
|
|
|
|
|
// We maintain 2 iterators, one for the transaction and one for the parent
|
|
|
|
// At all times, only one of both provides the current value. The decision is made by comparing the current keys
|
|
|
|
// of both iterators, so that always the smaller key is the current one. On Next(), the previously chosen iterator
|
|
|
|
// is advanced.
|
|
|
|
typename CDBTransaction::WritesMap::iterator transactionIt;
|
|
|
|
std::unique_ptr<ParentIterator> parentIt;
|
|
|
|
CDataStream parentKey;
|
|
|
|
bool curIsParent{false};
|
|
|
|
|
|
|
|
public:
|
2020-01-05 01:55:41 +01:00
|
|
|
explicit CDBTransactionIterator(CDBTransaction& _transaction) :
|
2019-04-04 10:03:56 +02:00
|
|
|
transaction(_transaction),
|
|
|
|
parentKey(SER_DISK, CLIENT_VERSION)
|
|
|
|
{
|
|
|
|
transactionIt = transaction.writes.end();
|
|
|
|
parentIt = std::unique_ptr<ParentIterator>(transaction.parent.NewIterator());
|
|
|
|
}
|
|
|
|
|
|
|
|
void SeekToFirst() {
|
|
|
|
transactionIt = transaction.writes.begin();
|
|
|
|
parentIt->SeekToFirst();
|
|
|
|
SkipDeletedAndOverwritten();
|
|
|
|
DecideCur();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename K>
|
|
|
|
void Seek(const K& key) {
|
|
|
|
Seek(CDBTransaction::KeyToDataStream(key));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Seek(const CDataStream& ssKey) {
|
|
|
|
transactionIt = transaction.writes.lower_bound(ssKey);
|
|
|
|
parentIt->Seek(ssKey);
|
|
|
|
SkipDeletedAndOverwritten();
|
|
|
|
DecideCur();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Valid() {
|
|
|
|
return transactionIt != transaction.writes.end() || parentIt->Valid();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Next() {
|
|
|
|
if (transactionIt == transaction.writes.end() && !parentIt->Valid()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (curIsParent) {
|
|
|
|
assert(parentIt->Valid());
|
|
|
|
parentIt->Next();
|
|
|
|
SkipDeletedAndOverwritten();
|
|
|
|
} else {
|
|
|
|
assert(transactionIt != transaction.writes.end());
|
|
|
|
++transactionIt;
|
|
|
|
}
|
|
|
|
DecideCur();
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename K>
|
|
|
|
bool GetKey(K& key) {
|
|
|
|
if (!Valid()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (curIsParent) {
|
2019-04-05 13:37:25 +02:00
|
|
|
try {
|
|
|
|
// TODO try to avoid this copy (we need a stream that allows reading from external buffers)
|
|
|
|
CDataStream ssKey = parentKey;
|
|
|
|
ssKey >> key;
|
|
|
|
} catch (const std::exception&) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
2019-04-04 10:03:56 +02:00
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
// TODO try to avoid this copy (we need a stream that allows reading from external buffers)
|
|
|
|
CDataStream ssKey = transactionIt->first;
|
|
|
|
ssKey >> key;
|
|
|
|
} catch (const std::exception&) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CDataStream GetKey() {
|
|
|
|
if (!Valid()) {
|
|
|
|
return CDataStream(SER_DISK, CLIENT_VERSION);
|
|
|
|
}
|
|
|
|
if (curIsParent) {
|
2019-04-05 13:37:25 +02:00
|
|
|
return parentKey;
|
2019-04-04 10:03:56 +02:00
|
|
|
} else {
|
|
|
|
return transactionIt->first;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned int GetKeySize() {
|
|
|
|
if (!Valid()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (curIsParent) {
|
|
|
|
return parentIt->GetKeySize();
|
|
|
|
} else {
|
|
|
|
return transactionIt->first.vKey.size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename V>
|
|
|
|
bool GetValue(V& value) {
|
|
|
|
if (!Valid()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (curIsParent) {
|
|
|
|
return transaction.Read(parentKey, value);
|
|
|
|
} else {
|
|
|
|
return transaction.Read(transactionIt->first, value);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private:
|
|
|
|
void SkipDeletedAndOverwritten() {
|
|
|
|
while (parentIt->Valid()) {
|
|
|
|
parentKey = parentIt->GetKey();
|
|
|
|
if (!transaction.deletes.count(parentKey) && !transaction.writes.count(parentKey)) {
|
|
|
|
break;
|
|
|
|
}
|
2019-04-16 15:39:59 +02:00
|
|
|
parentIt->Next();
|
2019-04-04 10:03:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DecideCur() {
|
|
|
|
if (transactionIt != transaction.writes.end() && !parentIt->Valid()) {
|
|
|
|
curIsParent = false;
|
|
|
|
} else if (transactionIt == transaction.writes.end() && parentIt->Valid()) {
|
|
|
|
curIsParent = true;
|
|
|
|
} else if (transactionIt != transaction.writes.end() && parentIt->Valid()) {
|
|
|
|
if (CDBTransaction::DataStreamCmp::less(transactionIt->first, parentKey)) {
|
|
|
|
curIsParent = false;
|
|
|
|
} else {
|
|
|
|
curIsParent = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-03-06 20:45:39 +01:00
|
|
|
template<typename Parent, typename CommitTarget>
|
2018-02-14 12:04:24 +01:00
|
|
|
class CDBTransaction {
|
2019-04-04 10:03:56 +02:00
|
|
|
friend class CDBTransactionIterator<CDBTransaction>;
|
|
|
|
|
2019-03-06 20:45:39 +01:00
|
|
|
protected:
|
|
|
|
Parent &parent;
|
|
|
|
CommitTarget &commitTarget;
|
2019-07-02 06:16:27 +02:00
|
|
|
ssize_t memoryUsage{0}; // signed, just in case we made an error in the calculations so that we don't get an overflow
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
struct DataStreamCmp {
|
|
|
|
static bool less(const CDataStream& a, const CDataStream& b) {
|
2019-04-05 13:37:58 +02:00
|
|
|
return std::lexicographical_compare(
|
|
|
|
(const uint8_t*)a.data(), (const uint8_t*)a.data() + a.size(),
|
|
|
|
(const uint8_t*)b.data(), (const uint8_t*)b.data() + b.size());
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
2019-04-04 09:58:13 +02:00
|
|
|
bool operator()(const CDataStream& a, const CDataStream& b) const {
|
|
|
|
return less(a, b);
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
struct ValueHolder {
|
2019-07-02 06:16:27 +02:00
|
|
|
size_t memoryUsage;
|
2021-08-06 23:55:51 +02:00
|
|
|
explicit ValueHolder(size_t _memoryUsage) : memoryUsage(_memoryUsage) {}
|
2019-04-04 09:58:13 +02:00
|
|
|
virtual ~ValueHolder() = default;
|
|
|
|
virtual void Write(const CDataStream& ssKey, CommitTarget &parent) = 0;
|
2018-02-14 12:04:24 +01:00
|
|
|
};
|
2019-04-04 09:58:13 +02:00
|
|
|
typedef std::unique_ptr<ValueHolder> ValueHolderPtr;
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
template <typename V>
|
|
|
|
struct ValueHolderImpl : ValueHolder {
|
2019-07-02 06:16:27 +02:00
|
|
|
ValueHolderImpl(const V &_value, size_t _memoryUsage) : ValueHolder(_memoryUsage), value(_value) {}
|
2019-04-04 09:58:13 +02:00
|
|
|
|
2021-10-11 23:55:49 +02:00
|
|
|
virtual void Write(const CDataStream& ssKey, CommitTarget &commitTarget) override {
|
2019-03-06 20:45:39 +01:00
|
|
|
// we're moving the value instead of copying it. This means that Write() can only be called once per
|
2019-04-04 09:58:13 +02:00
|
|
|
// ValueHolderImpl instance. Commit() clears the write maps, so this ok.
|
|
|
|
commitTarget.Write(ssKey, std::move(value));
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
V value;
|
|
|
|
};
|
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
template<typename K>
|
|
|
|
static CDataStream KeyToDataStream(const K& key) {
|
|
|
|
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
|
|
|
|
ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
|
|
|
|
ssKey << key;
|
|
|
|
return ssKey;
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
typedef std::map<CDataStream, ValueHolderPtr, DataStreamCmp> WritesMap;
|
|
|
|
typedef std::set<CDataStream, DataStreamCmp> DeletesSet;
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
WritesMap writes;
|
|
|
|
DeletesSet deletes;
|
2019-03-06 20:45:39 +01:00
|
|
|
|
|
|
|
public:
|
|
|
|
CDBTransaction(Parent &_parent, CommitTarget &_commitTarget) : parent(_parent), commitTarget(_commitTarget) {}
|
|
|
|
|
|
|
|
template <typename K, typename V>
|
|
|
|
void Write(const K& key, const V& v) {
|
2019-04-04 09:58:13 +02:00
|
|
|
Write(KeyToDataStream(key), v);
|
2019-03-06 20:45:39 +01:00
|
|
|
}
|
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
template <typename V>
|
|
|
|
void Write(const CDataStream& ssKey, const V& v) {
|
2019-07-02 06:16:27 +02:00
|
|
|
auto valueMemoryUsage = ::GetSerializeSize(v, SER_DISK, CLIENT_VERSION);
|
|
|
|
|
|
|
|
if (deletes.erase(ssKey)) {
|
|
|
|
memoryUsage -= ssKey.size();
|
|
|
|
}
|
2019-04-04 09:58:13 +02:00
|
|
|
auto it = writes.emplace(ssKey, nullptr).first;
|
2019-07-02 06:16:27 +02:00
|
|
|
if (it->second) {
|
|
|
|
memoryUsage -= ssKey.size() + it->second->memoryUsage;
|
|
|
|
}
|
|
|
|
it->second = std::make_unique<ValueHolderImpl<V>>(v, valueMemoryUsage);
|
|
|
|
|
|
|
|
memoryUsage += ssKey.size() + valueMemoryUsage;
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K, typename V>
|
|
|
|
bool Read(const K& key, V& value) {
|
2019-04-04 09:58:13 +02:00
|
|
|
return Read(KeyToDataStream(key), value);
|
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
template <typename V>
|
|
|
|
bool Read(const CDataStream& ssKey, V& value) {
|
|
|
|
if (deletes.count(ssKey)) {
|
2018-02-14 12:04:24 +01:00
|
|
|
return false;
|
2019-04-04 09:58:13 +02:00
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
auto it = writes.find(ssKey);
|
|
|
|
if (it != writes.end()) {
|
|
|
|
auto *impl = dynamic_cast<ValueHolderImpl<V> *>(it->second.get());
|
|
|
|
if (!impl) {
|
|
|
|
throw std::runtime_error("Read called with V != previously written type");
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
2019-04-04 09:58:13 +02:00
|
|
|
value = impl->value;
|
|
|
|
return true;
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
return parent.Read(ssKey, value);
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
bool Exists(const K& key) {
|
2019-04-04 09:58:13 +02:00
|
|
|
return Exists(KeyToDataStream(key));
|
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
bool Exists(const CDataStream& ssKey) {
|
|
|
|
if (deletes.count(ssKey)) {
|
2018-02-14 12:04:24 +01:00
|
|
|
return false;
|
2019-04-04 09:58:13 +02:00
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
if (writes.count(ssKey)) {
|
2018-02-14 12:04:24 +01:00
|
|
|
return true;
|
2019-04-04 09:58:13 +02:00
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
return parent.Exists(ssKey);
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
template <typename K>
|
|
|
|
void Erase(const K& key) {
|
2019-04-04 09:58:13 +02:00
|
|
|
return Erase(KeyToDataStream(key));
|
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
|
2019-04-04 09:58:13 +02:00
|
|
|
void Erase(const CDataStream& ssKey) {
|
2019-07-02 06:16:27 +02:00
|
|
|
auto it = writes.find(ssKey);
|
|
|
|
if (it != writes.end()) {
|
|
|
|
memoryUsage -= ssKey.size() + it->second->memoryUsage;
|
|
|
|
writes.erase(it);
|
|
|
|
}
|
|
|
|
if (deletes.emplace(ssKey).second) {
|
|
|
|
memoryUsage += ssKey.size();
|
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void Clear() {
|
|
|
|
writes.clear();
|
|
|
|
deletes.clear();
|
2019-07-02 06:16:27 +02:00
|
|
|
memoryUsage = 0;
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
|
2019-03-06 20:45:39 +01:00
|
|
|
void Commit() {
|
2019-04-04 09:58:13 +02:00
|
|
|
for (const auto &k : deletes) {
|
|
|
|
commitTarget.Erase(k);
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
for (auto &p : writes) {
|
2019-04-04 09:58:13 +02:00
|
|
|
p.second->Write(p.first, commitTarget);
|
2018-02-14 12:04:24 +01:00
|
|
|
}
|
|
|
|
Clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool IsClean() {
|
|
|
|
return writes.empty() && deletes.empty();
|
|
|
|
}
|
2019-04-04 10:03:56 +02:00
|
|
|
|
2019-07-02 06:16:27 +02:00
|
|
|
size_t GetMemoryUsage() const {
|
|
|
|
if (memoryUsage < 0) {
|
|
|
|
// something went wrong when we accounted/calculated used memory...
|
|
|
|
static volatile bool didPrint = false;
|
|
|
|
if (!didPrint) {
|
|
|
|
LogPrintf("CDBTransaction::%s -- negative memoryUsage (%d)", __func__, memoryUsage);
|
|
|
|
didPrint = true;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return (size_t)memoryUsage;
|
|
|
|
}
|
|
|
|
|
2019-04-04 10:03:56 +02:00
|
|
|
CDBTransactionIterator<CDBTransaction>* NewIterator() {
|
|
|
|
return new CDBTransactionIterator<CDBTransaction>(*this);
|
|
|
|
}
|
|
|
|
std::unique_ptr<CDBTransactionIterator<CDBTransaction>> NewIteratorUniquePtr() {
|
|
|
|
return std::make_unique<CDBTransactionIterator<CDBTransaction>>(*this);
|
|
|
|
}
|
2018-02-14 12:04:24 +01:00
|
|
|
};
|
|
|
|
|
2015-10-23 03:33:06 +02:00
|
|
|
#endif // BITCOIN_DBWRAPPER_H
|