// Copyright (c) 2012-2022 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_DBWRAPPER_H #define BITCOIN_DBWRAPPER_H #include <attributes.h> #include <serialize.h> #include <span.h> #include <streams.h> #include <util/check.h> #include <util/fs.h> #include <cstddef> #include <exception> #include <memory> #include <optional> #include <stdexcept> #include <string> #include <vector> static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64; static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024; //! User-controlled performance and debug options. struct DBOptions { //! Compact database on startup. bool force_compact = false; }; //! Application-specific storage settings. struct DBParams { //! Location in the filesystem where leveldb data will be stored. fs::path path; //! Configures various leveldb cache settings. size_t cache_bytes; //! If true, use leveldb's memory environment. bool memory_only = false; //! If true, remove all existing data. bool wipe_data = false; //! If true, store data obfuscated via simple XOR. If false, XOR with a //! zero'd byte array. bool obfuscate = false; //! Passed-through options. DBOptions options{}; }; class dbwrapper_error : public std::runtime_error { public: explicit dbwrapper_error(const std::string& msg) : std::runtime_error(msg) {} }; class CDBWrapper; /** These should be considered an implementation detail of the specific database. */ namespace dbwrapper_private { /** 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); }; // namespace dbwrapper_private bool DestroyDB(const std::string& path_str); /** Batch of changes queued to be written to a CDBWrapper */ class CDBBatch { friend class CDBWrapper; private: const CDBWrapper &parent; struct WriteBatchImpl; const std::unique_ptr<WriteBatchImpl> m_impl_batch; DataStream ssKey{}; DataStream ssValue{}; size_t size_estimate{0}; void WriteImpl(Span<const std::byte> key, DataStream& ssValue); void EraseImpl(Span<const std::byte> key); public: /** * @param[in] _parent CDBWrapper that this batch is to be submitted to */ explicit CDBBatch(const CDBWrapper& _parent); ~CDBBatch(); void Clear(); template <typename K, typename V> void Write(const K& key, const V& value) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE); ssKey << key; ssValue << value; WriteImpl(ssKey, ssValue); ssKey.clear(); ssValue.clear(); } template <typename K> void Erase(const K& key) { ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; EraseImpl(ssKey); ssKey.clear(); } size_t SizeEstimate() const { return size_estimate; } }; class CDBIterator { public: struct IteratorImpl; private: const CDBWrapper &parent; const std::unique_ptr<IteratorImpl> m_impl_iter; void SeekImpl(Span<const std::byte> key); Span<const std::byte> GetKeyImpl() const; Span<const std::byte> GetValueImpl() const; public: /** * @param[in] _parent Parent CDBWrapper instance. * @param[in] _piter The original leveldb iterator. */ CDBIterator(const CDBWrapper& _parent, std::unique_ptr<IteratorImpl> _piter); ~CDBIterator(); bool Valid() const; void SeekToFirst(); template<typename K> void Seek(const K& key) { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; SeekImpl(ssKey); } void Next(); template<typename K> bool GetKey(K& key) { try { DataStream ssKey{GetKeyImpl()}; ssKey >> key; } catch (const std::exception&) { return false; } return true; } template<typename V> bool GetValue(V& value) { try { DataStream ssValue{GetValueImpl()}; ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); ssValue >> value; } catch (const std::exception&) { return false; } return true; } }; struct LevelDBContext; class CDBWrapper { friend const std::vector<unsigned char>& dbwrapper_private::GetObfuscateKey(const CDBWrapper &w); private: //! holds all leveldb-specific fields of this class std::unique_ptr<LevelDBContext> m_db_context; //! the name of this database std::string m_name; //! 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; //! the length of the obfuscate key in number of bytes static const unsigned int OBFUSCATE_KEY_NUM_BYTES; std::vector<unsigned char> CreateObfuscateKey() const; //! path to filesystem storage const fs::path m_path; //! whether or not the database resides in memory bool m_is_memory; std::optional<std::string> ReadImpl(Span<const std::byte> key) const; bool ExistsImpl(Span<const std::byte> key) const; size_t EstimateSizeImpl(Span<const std::byte> key1, Span<const std::byte> key2) const; auto& DBContext() const LIFETIMEBOUND { return *Assert(m_db_context); } public: CDBWrapper(const DBParams& params); ~CDBWrapper(); CDBWrapper(const CDBWrapper&) = delete; CDBWrapper& operator=(const CDBWrapper&) = delete; template <typename K, typename V> bool Read(const K& key, V& value) const { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; std::optional<std::string> strValue{ReadImpl(ssKey)}; if (!strValue) { return false; } try { DataStream ssValue{MakeByteSpan(*strValue)}; ssValue.Xor(obfuscate_key); ssValue >> value; } catch (const std::exception&) { return false; } return true; } template <typename K, typename V> bool Write(const K& key, const V& value, bool fSync = false) { CDBBatch batch(*this); batch.Write(key, value); return WriteBatch(batch, fSync); } //! @returns filesystem path to the on-disk data. std::optional<fs::path> StoragePath() { if (m_is_memory) { return {}; } return m_path; } template <typename K> bool Exists(const K& key) const { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; return ExistsImpl(ssKey); } template <typename K> bool Erase(const K& key, bool fSync = false) { CDBBatch batch(*this); batch.Erase(key); return WriteBatch(batch, fSync); } bool WriteBatch(CDBBatch& batch, bool fSync = false); // Get an estimate of LevelDB memory usage (in bytes). size_t DynamicMemoryUsage() const; CDBIterator* NewIterator(); /** * Return true if the database managed by this class contains no entries. */ bool IsEmpty(); template<typename K> size_t EstimateSize(const K& key_begin, const K& key_end) const { DataStream ssKey1{}, ssKey2{}; ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey1 << key_begin; ssKey2 << key_end; return EstimateSizeImpl(ssKey1, ssKey2); } }; #endif // BITCOIN_DBWRAPPER_H