// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include 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& 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 m_impl_batch; DataStream ssKey{}; CDataStream ssValue; size_t size_estimate{0}; void WriteImpl(Span key, CDataStream& ssValue); void EraseImpl(Span key); public: /** * @param[in] _parent CDBWrapper that this batch is to be submitted to */ explicit CDBBatch(const CDBWrapper& _parent); ~CDBBatch(); void Clear(); template 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 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 m_impl_iter; void SeekImpl(Span key); Span GetKeyImpl() const; Span GetValueImpl() const; public: /** * @param[in] _parent Parent CDBWrapper instance. * @param[in] _piter The original leveldb iterator. */ CDBIterator(const CDBWrapper& _parent, std::unique_ptr _piter); ~CDBIterator(); bool Valid() const; void SeekToFirst(); template void Seek(const K& key) { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; SeekImpl(ssKey); } void Next(); template bool GetKey(K& key) { try { DataStream ssKey{GetKeyImpl()}; ssKey >> key; } catch (const std::exception&) { return false; } return true; } template bool GetValue(V& value) { try { CDataStream ssValue{GetValueImpl(), SER_DISK, CLIENT_VERSION}; ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent)); ssValue >> value; } catch (const std::exception&) { return false; } return true; } }; struct LevelDBContext; class CDBWrapper { friend const std::vector& dbwrapper_private::GetObfuscateKey(const CDBWrapper &w); private: //! holds all leveldb-specific fields of this class std::unique_ptr m_db_context; //! the name of this database std::string m_name; //! a key used for optional XOR-obfuscation of the database std::vector 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 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 ReadImpl(Span key) const; bool ExistsImpl(Span key) const; size_t EstimateSizeImpl(Span key1, Span 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 bool Read(const K& key, V& value) const { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; std::optional strValue{ReadImpl(ssKey)}; if (!strValue) { return false; } try { CDataStream ssValue{MakeByteSpan(*strValue), SER_DISK, CLIENT_VERSION}; ssValue.Xor(obfuscate_key); ssValue >> value; } catch (const std::exception&) { return false; } return true; } template 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 StoragePath() { if (m_is_memory) { return {}; } return m_path; } template bool Exists(const K& key) const { DataStream ssKey{}; ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE); ssKey << key; return ExistsImpl(ssKey); } template 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 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