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