dbwrapper.cpp 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  #include <limenka-build-config.h> // IWYU pragma: keep
   6  
   7  #include <dbwrapper.h>
   8  
   9  #include <logging.h>
  10  #include <random.h>
  11  #include <serialize.h>
  12  #include <span.h>
  13  #include <streams.h>
  14  #include <util/fs.h>
  15  #include <util/fs_helpers.h>
  16  #include <util/obfuscation.h>
  17  #include <util/strencodings.h>
  18  #include <util/translation.h>
  19  
  20  #include <algorithm>
  21  #include <cassert>
  22  #include <cstdarg>
  23  #include <cstdint>
  24  #include <cstdio>
  25  #include <leveldb/c.h>
  26  #include <leveldb/cache.h>
  27  #include <leveldb/db.h>
  28  #include <leveldb/env.h>
  29  #include <leveldb/filter_policy.h>
  30  #include <memenv.h>
  31  #include <leveldb/iterator.h>
  32  #include <leveldb/options.h>
  33  #include <leveldb/slice.h>
  34  #include <leveldb/status.h>
  35  #include <leveldb/write_batch.h>
  36  #include <memory>
  37  #include <optional>
  38  #include <utility>
  39  
  40  static auto CharCast(const std::byte* data) { return reinterpret_cast<const char*>(data); }
  41  
  42  bool DestroyDB(const std::string& path_str)
  43  {
  44      return leveldb::DestroyDB(path_str, {}).ok();
  45  }
  46  
  47  /** Handle database error by throwing dbwrapper_error exception.
  48   */
  49  static void HandleError(const leveldb::Status& status)
  50  {
  51      if (status.ok())
  52          return;
  53      const std::string errmsg = "Fatal LevelDB error: " + status.ToString();
  54      LogError("%s", errmsg);
  55      LogInfo("You can use -debug=leveldb to get more complete diagnostic messages");
  56      throw dbwrapper_error(errmsg);
  57  }
  58  
  59  util::Result<void> dbwrapper_SanityCheck()
  60  {
  61  #ifndef EMBEDDED_LEVELDB
  62      unsigned long header_version = (leveldb::kMajorVersion << 16) | leveldb::kMinorVersion;
  63      unsigned long library_version = (leveldb_major_version() << 16) | leveldb_minor_version();
  64  
  65      if (header_version != library_version) {
  66          return util::Error{Untranslated(strprintf("Compiled with LevelDB %d.%d, but linked with LevelDB %d.%d (incompatible).",
  67              leveldb::kMajorVersion, leveldb::kMinorVersion,
  68              leveldb_major_version(), leveldb_minor_version()
  69          ))};
  70      }
  71  #endif
  72  
  73      return {};
  74  }
  75  
  76  #ifndef WIN32
  77  namespace leveldb {
  78  class EnvPosixTestHelper {
  79      static void SetReadOnlyMMapLimit(int limit);
  80  public:
  81      static inline void SetReadOnlyMMapLimitForLimenka(int limit) { SetReadOnlyMMapLimit(limit); }
  82  };
  83  }
  84  
  85  class LimenkaLevelDBInit {
  86  public:
  87      LimenkaLevelDBInit() {
  88          if (sizeof(void*) >= 8) {
  89              leveldb::EnvPosixTestHelper::SetReadOnlyMMapLimitForLimenka(4096);
  90          }
  91      }
  92  };
  93  static LimenkaLevelDBInit g_limenka_leveldb_init;
  94  #endif
  95  
  96  class CLimenkaLevelDBLogger : public leveldb::Logger {
  97  public:
  98      // This code is adapted from posix_logger.h, which is why it is using vsprintf.
  99      // Please do not do this in normal code
 100      void Logv(const char * format, va_list ap) override {
 101              if (!LogAcceptCategory(BCLog::LEVELDB, BCLog::Level::Debug)) {
 102                  return;
 103              }
 104              char buffer[500];
 105              for (int iter = 0; iter < 2; iter++) {
 106                  char* base;
 107                  int bufsize;
 108                  if (iter == 0) {
 109                      bufsize = sizeof(buffer);
 110                      base = buffer;
 111                  }
 112                  else {
 113                      bufsize = 30000;
 114                      base = new char[bufsize];
 115                  }
 116                  char* p = base;
 117                  char* limit = base + bufsize;
 118  
 119                  // Print the message
 120                  if (p < limit) {
 121                      va_list backup_ap;
 122                      va_copy(backup_ap, ap);
 123                      // Do not use vsnprintf elsewhere in limenka source code, see above.
 124                      p += vsnprintf(p, limit - p, format, backup_ap);
 125                      va_end(backup_ap);
 126                  }
 127  
 128                  // Truncate to available space if necessary
 129                  if (p >= limit) {
 130                      if (iter == 0) {
 131                          continue;       // Try again with larger buffer
 132                      }
 133                      else {
 134                          p = limit - 1;
 135                      }
 136                  }
 137  
 138                  // Add newline if necessary
 139                  if (p == base || p[-1] != '\n') {
 140                      *p++ = '\n';
 141                  }
 142  
 143                  assert(p <= limit);
 144                  base[std::min(bufsize - 1, (int)(p - base))] = '\0';
 145                  LogDebug(BCLog::LEVELDB, "%s\n", util::RemoveSuffixView(base, "\n"));
 146                  if (base != buffer) {
 147                      delete[] base;
 148                  }
 149                  break;
 150              }
 151      }
 152  };
 153  
 154  static void SetMaxOpenFiles(leveldb::Options *options) {
 155      // On most platforms the default setting of max_open_files (which is 1000)
 156      // is optimal. On Windows using a large file count is OK because the handles
 157      // do not interfere with select() loops. On 64-bit Unix hosts this value is
 158      // also OK, because up to that amount LevelDB will use an mmap
 159      // implementation that does not use extra file descriptors (the fds are
 160      // closed after being mmap'ed).
 161      //
 162      // Increasing the value beyond the default is dangerous because LevelDB will
 163      // fall back to a non-mmap implementation when the file count is too large.
 164      // On 32-bit Unix host we should decrease the value because the handles use
 165      // up real fds, and we want to avoid fd exhaustion issues.
 166      //
 167      // See PR #12495 for further discussion.
 168  
 169      int default_open_files = options->max_open_files;
 170  #ifndef WIN32
 171      if (sizeof(void*) < 8) {
 172          options->max_open_files = 64;
 173      }
 174  #endif
 175      LogDebug(BCLog::LEVELDB, "LevelDB using max_open_files=%d (default=%d)\n",
 176               options->max_open_files, default_open_files);
 177  }
 178  
 179  static leveldb::Options GetOptions(size_t nCacheSize)
 180  {
 181      leveldb::Options options;
 182      options.block_cache = leveldb::NewLRUCache(nCacheSize / 2);
 183      options.write_buffer_size = nCacheSize / 4; // up to two write buffers may be held in memory simultaneously
 184      options.filter_policy = leveldb::NewBloomFilterPolicy(10);
 185      options.compression = leveldb::kNoCompression;
 186      options.info_log = new CLimenkaLevelDBLogger();
 187      if (leveldb::kMajorVersion > 1 || (leveldb::kMajorVersion == 1 && leveldb::kMinorVersion >= 16)) {
 188          // LevelDB versions before 1.16 consider short writes to be corruption. Only trigger error
 189          // on corruption in later versions.
 190          options.paranoid_checks = true;
 191      }
 192      options.max_file_size = std::max(options.max_file_size, DBWRAPPER_MAX_FILE_SIZE);
 193      SetMaxOpenFiles(&options);
 194      return options;
 195  }
 196  
 197  struct CDBBatch::WriteBatchImpl {
 198      leveldb::WriteBatch batch;
 199  };
 200  
 201  CDBBatch::CDBBatch(const CDBWrapper& _parent)
 202      : parent{_parent},
 203        m_impl_batch{std::make_unique<CDBBatch::WriteBatchImpl>()}
 204  {
 205      Clear();
 206  };
 207  
 208  CDBBatch::~CDBBatch() = default;
 209  
 210  void CDBBatch::Clear()
 211  {
 212      m_impl_batch->batch.Clear();
 213      size_estimate = kHeader;
 214  }
 215  
 216  void CDBBatch::WriteImpl(Span<const std::byte> key, DataStream& ssValue)
 217  {
 218      leveldb::Slice slKey(CharCast(key.data()), key.size());
 219      ssValue.Xor(dbwrapper_private::GetObfuscateKey(parent));
 220      leveldb::Slice slValue(CharCast(ssValue.data()), ssValue.size());
 221      m_impl_batch->batch.Put(slKey, slValue);
 222      // LevelDB serializes writes as:
 223      // - byte: header
 224      // - varint: key length (1 byte up to 127B, 2 bytes up to 16383B, ...)
 225      // - byte[]: key
 226      // - varint: value length
 227      // - byte[]: value
 228      // The formula below assumes the key and value are both less than 16k.
 229      size_estimate += 3 + (slKey.size() > 127) + slKey.size() + (slValue.size() > 127) + slValue.size();
 230  }
 231  
 232  void CDBBatch::EraseImpl(Span<const std::byte> key)
 233  {
 234      leveldb::Slice slKey(CharCast(key.data()), key.size());
 235      m_impl_batch->batch.Delete(slKey);
 236      // LevelDB serializes erases as:
 237      // - byte: header
 238      // - varint: key length
 239      // - byte[]: key
 240      // The formula below assumes the key is less than 16kB.
 241      size_estimate += 2 + (slKey.size() > 127) + slKey.size();
 242  }
 243  
 244  struct LevelDBContext {
 245      //! custom environment this database is using (may be nullptr in case of default environment)
 246      leveldb::Env* penv;
 247  
 248      //! database options used
 249      leveldb::Options options;
 250  
 251      //! options used when reading from the database
 252      leveldb::ReadOptions readoptions;
 253  
 254      //! options used when iterating over values of the database
 255      leveldb::ReadOptions iteroptions;
 256  
 257      //! options used when writing to the database
 258      leveldb::WriteOptions writeoptions;
 259  
 260      //! options used when sync writing to the database
 261      leveldb::WriteOptions syncoptions;
 262  
 263      //! the database itself
 264      leveldb::DB* pdb;
 265  };
 266  
 267  CDBWrapper::CDBWrapper(const DBParams& params)
 268      : m_db_context{std::make_unique<LevelDBContext>()}, m_name{fs::PathToString(params.path.stem())}, m_path{params.path}, m_is_memory{params.memory_only}
 269  {
 270      DBContext().penv = nullptr;
 271      DBContext().readoptions.verify_checksums = true;
 272      DBContext().iteroptions.verify_checksums = true;
 273      DBContext().iteroptions.fill_cache = false;
 274      DBContext().syncoptions.sync = true;
 275      DBContext().options = GetOptions(params.cache_bytes);
 276      DBContext().options.max_file_size = params.options.max_file_size;
 277      DBContext().options.create_if_missing = true;
 278      if (params.memory_only) {
 279          DBContext().penv = leveldb::NewMemEnv(leveldb::Env::Default());
 280          DBContext().options.env = DBContext().penv;
 281      } else {
 282          if (params.wipe_data) {
 283              LogPrintf("Wiping LevelDB in %s\n", fs::PathToString(params.path));
 284              leveldb::Status result = leveldb::DestroyDB(fs::PathToString(params.path), DBContext().options);
 285              HandleError(result);
 286          }
 287          TryCreateDirectories(params.path);
 288          LogPrintf("Opening LevelDB in %s\n", fs::PathToString(params.path));
 289      }
 290      // PathToString() return value is safe to pass to leveldb open function,
 291      // because on POSIX leveldb passes the byte string directly to ::open(), and
 292      // on Windows it converts from UTF-8 to UTF-16 before calling ::CreateFileW
 293      // (see env_posix.cc and env_windows.cc).
 294      leveldb::Status status = leveldb::DB::Open(DBContext().options, fs::PathToString(params.path), &DBContext().pdb);
 295      HandleError(status);
 296      LogPrintf("Opened LevelDB successfully\n");
 297  
 298      if (params.options.force_compact) {
 299          LogPrintf("Starting database compaction of %s\n", fs::PathToString(params.path));
 300          DBContext().pdb->CompactRange(nullptr, nullptr);
 301          LogPrintf("Finished database compaction of %s\n", fs::PathToString(params.path));
 302      }
 303  
 304      assert(!obfuscate_key); // Needed for unobfuscated Read()/Write() below
 305  
 306      bool key_exists = Read(OBFUSCATE_KEY_KEY, obfuscate_key);
 307  
 308      if (!key_exists && params.obfuscate && IsEmpty()) {
 309          // Initialize non-degenerate obfuscation if it won't upset
 310          // existing, non-obfuscated data.
 311          std::vector<unsigned char> new_key = CreateObfuscateKey();
 312  
 313          // Write `new_key` so we don't obfuscate the key with itself
 314          Write(OBFUSCATE_KEY_KEY, new_key);
 315          Read(CDBWrapper::OBFUSCATE_KEY_KEY, obfuscate_key);
 316  
 317          LogInfo("Wrote new obfuscation key for %s: %s", fs::PathToString(params.path), obfuscate_key.HexKey());
 318      } else if (!key_exists && params.obfuscate) {
 319          // Existing unobfuscated data — proceed without obfuscation.
 320          // Generating a new key would corrupt reads of legacy plaintext data.
 321          // obfuscate_key stays empty; reads and writes operate without XOR.
 322          LogInfo("No obfuscation key for existing data in %s — operating unobfuscated", fs::PathToString(params.path));
 323      }
 324      LogInfo("Using obfuscation key for %s: %s", fs::PathToString(params.path), obfuscate_key.HexKey());
 325  }
 326  
 327  CDBWrapper::~CDBWrapper()
 328  {
 329      delete DBContext().pdb;
 330      DBContext().pdb = nullptr;
 331      delete DBContext().options.filter_policy;
 332      DBContext().options.filter_policy = nullptr;
 333      delete DBContext().options.info_log;
 334      DBContext().options.info_log = nullptr;
 335      delete DBContext().options.block_cache;
 336      DBContext().options.block_cache = nullptr;
 337      delete DBContext().penv;
 338      DBContext().options.env = nullptr;
 339  }
 340  
 341  bool CDBWrapper::WriteBatch(CDBBatch& batch, bool fSync)
 342  {
 343      const bool log_memory = LogAcceptCategory(BCLog::LEVELDB, BCLog::Level::Debug);
 344      double mem_before = 0;
 345      if (log_memory) {
 346          mem_before = DynamicMemoryUsage() / 1024.0 / 1024;
 347      }
 348      leveldb::Status status = DBContext().pdb->Write(fSync ? DBContext().syncoptions : DBContext().writeoptions, &batch.m_impl_batch->batch);
 349      HandleError(status);
 350      if (log_memory) {
 351          double mem_after = DynamicMemoryUsage() / 1024.0 / 1024;
 352          LogDebug(BCLog::LEVELDB, "WriteBatch memory usage: db=%s, before=%.1fMiB, after=%.1fMiB\n",
 353                   m_name, mem_before, mem_after);
 354      }
 355      return true;
 356  }
 357  
 358  size_t CDBWrapper::DynamicMemoryUsage() const
 359  {
 360      std::string memory;
 361      std::optional<size_t> parsed;
 362      if (!DBContext().pdb->GetProperty("leveldb.approximate-memory-usage", &memory) || !(parsed = ToIntegral<size_t>(memory))) {
 363          LogDebug(BCLog::LEVELDB, "Failed to get approximate-memory-usage property\n");
 364          return 0;
 365      }
 366      return parsed.value();
 367  }
 368  
 369  // Prefixed with null character to avoid collisions with other keys
 370  //
 371  // We must use a string constructor which specifies length so that we copy
 372  // past the null-terminator.
 373  const std::string CDBWrapper::OBFUSCATE_KEY_KEY("\000obfuscate_key", 14);
 374  
 375  /**
 376   * Returns a string (consisting of 8 random bytes) suitable for use as an
 377   * obfuscating XOR key.
 378   */
 379  std::vector<unsigned char> CDBWrapper::CreateObfuscateKey() const
 380  {
 381      auto ret = FastRandomContext{}.randbytes(Obfuscation::KEY_SIZE);
 382      return ret;
 383  }
 384  
 385  std::optional<std::string> CDBWrapper::ReadImpl(Span<const std::byte> key) const
 386  {
 387      leveldb::Slice slKey(CharCast(key.data()), key.size());
 388      std::string strValue;
 389      leveldb::Status status = DBContext().pdb->Get(DBContext().readoptions, slKey, &strValue);
 390      if (!status.ok()) {
 391          if (status.IsNotFound())
 392              return std::nullopt;
 393          LogError("LevelDB read failure: %s", status.ToString());
 394          HandleError(status);
 395      }
 396      return strValue;
 397  }
 398  
 399  bool CDBWrapper::ExistsImpl(Span<const std::byte> key) const
 400  {
 401      leveldb::Slice slKey(CharCast(key.data()), key.size());
 402  
 403      std::string strValue;
 404      leveldb::Status status = DBContext().pdb->Get(DBContext().readoptions, slKey, &strValue);
 405      if (!status.ok()) {
 406          if (status.IsNotFound())
 407              return false;
 408          LogError("LevelDB read failure: %s", status.ToString());
 409          HandleError(status);
 410      }
 411      return true;
 412  }
 413  
 414  size_t CDBWrapper::EstimateSizeImpl(Span<const std::byte> key1, Span<const std::byte> key2) const
 415  {
 416      leveldb::Slice slKey1(CharCast(key1.data()), key1.size());
 417      leveldb::Slice slKey2(CharCast(key2.data()), key2.size());
 418      uint64_t size = 0;
 419      leveldb::Range range(slKey1, slKey2);
 420      DBContext().pdb->GetApproximateSizes(&range, 1, &size);
 421      return size;
 422  }
 423  
 424  bool CDBWrapper::IsEmpty()
 425  {
 426      std::unique_ptr<CDBIterator> it(NewIterator());
 427      it->SeekToFirst();
 428      return !(it->Valid());
 429  }
 430  
 431  struct CDBIterator::IteratorImpl {
 432      const std::unique_ptr<leveldb::Iterator> iter;
 433  
 434      explicit IteratorImpl(leveldb::Iterator* _iter) : iter{_iter} {}
 435  };
 436  
 437  CDBIterator::CDBIterator(const CDBWrapper& _parent, std::unique_ptr<IteratorImpl> _piter) : parent(_parent),
 438                                                                                              m_impl_iter(std::move(_piter)) {}
 439  
 440  CDBIterator* CDBWrapper::NewIterator()
 441  {
 442      return new CDBIterator{*this, std::make_unique<CDBIterator::IteratorImpl>(DBContext().pdb->NewIterator(DBContext().iteroptions))};
 443  }
 444  
 445  void CDBIterator::SeekImpl(Span<const std::byte> key)
 446  {
 447      leveldb::Slice slKey(CharCast(key.data()), key.size());
 448      m_impl_iter->iter->Seek(slKey);
 449  }
 450  
 451  Span<const std::byte> CDBIterator::GetKeyImpl() const
 452  {
 453      return MakeByteSpan(m_impl_iter->iter->key());
 454  }
 455  
 456  Span<const std::byte> CDBIterator::GetValueImpl() const
 457  {
 458      return MakeByteSpan(m_impl_iter->iter->value());
 459  }
 460  
 461  CDBIterator::~CDBIterator() = default;
 462  bool CDBIterator::Valid() const { return m_impl_iter->iter->Valid(); }
 463  void CDBIterator::SeekToFirst() { m_impl_iter->iter->SeekToFirst(); }
 464  void CDBIterator::Next() { m_impl_iter->iter->Next(); }
 465  
 466  namespace dbwrapper_private {
 467  
 468  const Obfuscation& GetObfuscateKey(const CDBWrapper& w)
 469  {
 470      return w.obfuscate_key;
 471  }
 472  
 473  } // namespace dbwrapper_private
 474