dbwrapper.h raw

   1  // Copyright (c) 2012-2022 The Limenka developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef LIMENKA_DBWRAPPER_H
   6  #define LIMENKA_DBWRAPPER_H
   7  
   8  #include <attributes.h>
   9  #include <serialize.h>
  10  #include <span.h>
  11  #include <streams.h>
  12  #include <util/check.h>
  13  #include <util/fs.h>
  14  #include <util/result.h>
  15  
  16  #include <cstddef>
  17  #include <exception>
  18  #include <memory>
  19  #include <optional>
  20  #include <stdexcept>
  21  #include <string>
  22  #include <vector>
  23  
  24  util::Result<void> dbwrapper_SanityCheck();
  25  
  26  static const size_t DBWRAPPER_PREALLOC_KEY_SIZE = 64;
  27  static const size_t DBWRAPPER_PREALLOC_VALUE_SIZE = 1024;
  28  static const size_t DBWRAPPER_MAX_FILE_SIZE = 32 << 20; // 32 MiB
  29  
  30  static constexpr size_t DEFAULT_DB_FILE_SIZE{64};
  31  
  32  //! User-controlled performance and debug options.
  33  struct DBOptions {
  34      //! Compact database on startup.
  35      bool force_compact = false;
  36      //! Target size of files.
  37      size_t max_file_size{DEFAULT_DB_FILE_SIZE << 20};
  38  };
  39  
  40  //! Application-specific storage settings.
  41  struct DBParams {
  42      //! Location in the filesystem where leveldb data will be stored.
  43      fs::path path;
  44      //! Configures various leveldb cache settings.
  45      size_t cache_bytes;
  46      //! If true, use leveldb's memory environment.
  47      bool memory_only = false;
  48      //! If true, remove all existing data.
  49      bool wipe_data = false;
  50      //! If true, store data obfuscated via simple XOR. If false, XOR with a
  51      //! zero'd byte array.
  52      bool obfuscate = false;
  53      //! Passed-through options.
  54      DBOptions options{};
  55  };
  56  
  57  class dbwrapper_error : public std::runtime_error
  58  {
  59  public:
  60      explicit dbwrapper_error(const std::string& msg) : std::runtime_error(msg) {}
  61  };
  62  
  63  class CDBWrapper;
  64  
  65  /** These should be considered an implementation detail of the specific database.
  66   */
  67  namespace dbwrapper_private {
  68  
  69  /** Work around circular dependency, as well as for testing in dbwrapper_tests.
  70   * Database obfuscation should be considered an implementation detail of the
  71   * specific database.
  72   */
  73  const Obfuscation& GetObfuscateKey(const CDBWrapper&);
  74  
  75  }; // namespace dbwrapper_private
  76  
  77  bool DestroyDB(const std::string& path_str);
  78  
  79  /** Batch of changes queued to be written to a CDBWrapper */
  80  class CDBBatch
  81  {
  82      friend class CDBWrapper;
  83  
  84  private:
  85      static constexpr size_t kHeader{12}; // See: src/leveldb/db/write_batch.cc#L27
  86  
  87      const CDBWrapper &parent;
  88  
  89      struct WriteBatchImpl;
  90      const std::unique_ptr<WriteBatchImpl> m_impl_batch;
  91  
  92      DataStream ssKey{};
  93      DataStream ssValue{};
  94  
  95      size_t size_estimate{0};
  96  
  97      void WriteImpl(Span<const std::byte> key, DataStream& ssValue);
  98      void EraseImpl(Span<const std::byte> key);
  99  
 100  public:
 101      /**
 102       * @param[in] _parent   CDBWrapper that this batch is to be submitted to
 103       */
 104      explicit CDBBatch(const CDBWrapper& _parent);
 105      ~CDBBatch();
 106      void Clear();
 107  
 108      template <typename K, typename V>
 109      void Write(const K& key, const V& value)
 110      {
 111          ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 112          ssValue.reserve(DBWRAPPER_PREALLOC_VALUE_SIZE);
 113          ssKey << key;
 114          ssValue << value;
 115          WriteImpl(ssKey, ssValue);
 116          ssKey.clear();
 117          ssValue.clear();
 118      }
 119  
 120      template <typename K>
 121      void Erase(const K& key)
 122      {
 123          ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 124          ssKey << key;
 125          EraseImpl(ssKey);
 126          ssKey.clear();
 127      }
 128  
 129      size_t SizeEstimate() const { return size_estimate; }
 130  };
 131  
 132  class CDBIterator
 133  {
 134  public:
 135      struct IteratorImpl;
 136  
 137  private:
 138      const CDBWrapper &parent;
 139      const std::unique_ptr<IteratorImpl> m_impl_iter;
 140  
 141      void SeekImpl(Span<const std::byte> key);
 142      Span<const std::byte> GetKeyImpl() const;
 143      Span<const std::byte> GetValueImpl() const;
 144  
 145  public:
 146  
 147      /**
 148       * @param[in] _parent          Parent CDBWrapper instance.
 149       * @param[in] _piter           The original leveldb iterator.
 150       */
 151      CDBIterator(const CDBWrapper& _parent, std::unique_ptr<IteratorImpl> _piter);
 152      ~CDBIterator();
 153  
 154      bool Valid() const;
 155  
 156      void SeekToFirst();
 157  
 158      template<typename K> void Seek(const K& key) {
 159          DataStream ssKey{};
 160          ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 161          ssKey << key;
 162          SeekImpl(ssKey);
 163      }
 164  
 165      void Next();
 166  
 167      template<typename K> bool GetKey(K& key) {
 168          try {
 169              DataStream ssKey{GetKeyImpl()};
 170              ssKey >> key;
 171          } catch (const std::exception&) {
 172              return false;
 173          }
 174          return true;
 175      }
 176  
 177      template<typename V> bool GetValue(V& value) {
 178          try {
 179              DataStream ssValue{GetValueImpl()};
 180              ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
 181              ssValue >> value;
 182          } catch (const std::exception&) {
 183              return false;
 184          }
 185          return true;
 186      }
 187  };
 188  
 189  struct LevelDBContext;
 190  
 191  class CDBWrapper
 192  {
 193      friend const Obfuscation& dbwrapper_private::GetObfuscateKey(const CDBWrapper&);
 194  private:
 195      //! holds all leveldb-specific fields of this class
 196      std::unique_ptr<LevelDBContext> m_db_context;
 197  
 198      //! the name of this database
 199      std::string m_name;
 200  
 201      //! optional XOR-obfuscation of the database
 202      Obfuscation obfuscate_key;
 203  
 204      //! the key under which the obfuscation key is stored
 205      static const std::string OBFUSCATE_KEY_KEY;
 206  
 207      std::vector<unsigned char> CreateObfuscateKey() const;
 208  
 209      //! path to filesystem storage
 210      const fs::path m_path;
 211  
 212      //! whether or not the database resides in memory
 213      bool m_is_memory;
 214  
 215      std::optional<std::string> ReadImpl(Span<const std::byte> key) const;
 216      bool ExistsImpl(Span<const std::byte> key) const;
 217      size_t EstimateSizeImpl(Span<const std::byte> key1, Span<const std::byte> key2) const;
 218      auto& DBContext() const LIFETIMEBOUND { return *Assert(m_db_context); }
 219  
 220  public:
 221      CDBWrapper(const DBParams& params);
 222      ~CDBWrapper();
 223  
 224      CDBWrapper(const CDBWrapper&) = delete;
 225      CDBWrapper& operator=(const CDBWrapper&) = delete;
 226  
 227      template <typename K, typename V>
 228      bool Read(const K& key, V& value) const
 229      {
 230          DataStream ssKey{};
 231          ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 232          ssKey << key;
 233          std::optional<std::string> strValue{ReadImpl(ssKey)};
 234          if (!strValue) {
 235              return false;
 236          }
 237          try {
 238              DataStream ssValue{MakeByteSpan(*strValue)};
 239              ssValue.Xor(obfuscate_key);
 240              ssValue >> value;
 241          } catch (const std::exception&) {
 242              return false;
 243          }
 244          return true;
 245      }
 246  
 247      template <typename K, typename V>
 248      bool Write(const K& key, const V& value, bool fSync = false)
 249      {
 250          CDBBatch batch(*this);
 251          batch.Write(key, value);
 252          return WriteBatch(batch, fSync);
 253      }
 254  
 255      //! @returns filesystem path to the on-disk data.
 256      std::optional<fs::path> StoragePath() {
 257          if (m_is_memory) {
 258              return {};
 259          }
 260          return m_path;
 261      }
 262  
 263      template <typename K>
 264      bool Exists(const K& key) const
 265      {
 266          DataStream ssKey{};
 267          ssKey.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 268          ssKey << key;
 269          return ExistsImpl(ssKey);
 270      }
 271  
 272      template <typename K>
 273      bool Erase(const K& key, bool fSync = false)
 274      {
 275          CDBBatch batch(*this);
 276          batch.Erase(key);
 277          return WriteBatch(batch, fSync);
 278      }
 279  
 280      bool WriteBatch(CDBBatch& batch, bool fSync = false);
 281  
 282      // Get an estimate of LevelDB memory usage (in bytes).
 283      size_t DynamicMemoryUsage() const;
 284  
 285      CDBIterator* NewIterator();
 286  
 287      /**
 288       * Return true if the database managed by this class contains no entries.
 289       */
 290      bool IsEmpty();
 291  
 292      template<typename K>
 293      size_t EstimateSize(const K& key_begin, const K& key_end) const
 294      {
 295          DataStream ssKey1{}, ssKey2{};
 296          ssKey1.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 297          ssKey2.reserve(DBWRAPPER_PREALLOC_KEY_SIZE);
 298          ssKey1 << key_begin;
 299          ssKey2 << key_end;
 300          return EstimateSizeImpl(ssKey1, ssKey2);
 301      }
 302  };
 303  
 304  #endif // LIMENKA_DBWRAPPER_H
 305