Bitcoin Core 31.99.0
P2P Digital Currency
dbwrapper.cpp
Go to the documentation of this file.
1// Copyright (c) The Bitcoin Core 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 <dbwrapper.h>
6#include <compat/byteswap.h>
7#include <random.h>
8#include <sync.h>
10#include <test/fuzz/fuzz.h>
11#include <test/fuzz/util.h>
12#include <test/util/random.h>
14#include <util/byte_units.h>
15#include <util/check.h>
16#include <util/threadpool.h>
17
18#include <leveldb/env.h>
19#include <leveldb/helpers/memenv/memenv.h>
20
21#include <algorithm>
22#include <cassert>
23#include <cstdint>
24#include <deque>
25#include <functional>
26#include <future>
27#include <latch>
28#include <map>
29#include <memory>
30#include <numeric>
31#include <optional>
32#include <set>
33#include <span>
34#include <string>
35#include <tuple>
36#include <vector>
37
38namespace {
39
60class DeterministicEnv final : public leveldb::EnvWrapper
61{
62 using WorkFunction = void (*)(void*);
63
64 struct Work {
65 WorkFunction function;
66 void* arg;
67 };
68
69 Mutex m_mutex;
70 std::deque<Work> m_queue GUARDED_BY(m_mutex);
71
72public:
73 explicit DeterministicEnv(leveldb::Env* base) : EnvWrapper(base) {}
74
75 void Schedule(WorkFunction function, void* arg) override EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
76 {
77 LOCK(m_mutex);
78 m_queue.push_back({function, arg});
79 }
80
83 bool RunOne() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
84 {
85 Work work;
86 {
87 LOCK(m_mutex);
88 if (m_queue.empty()) return false;
89 work = m_queue.front();
90 m_queue.pop_front();
91 }
92 work.function(work.arg);
93 return true;
94 }
95
97 void DrainWork() EXCLUSIVE_LOCKS_REQUIRED(!m_mutex) { while (RunOne()) {} }
98};
99
100constexpr size_t MAX_VALUE_LEN{4096};
101constexpr uint8_t MAX_VALUE_MULTIPLIER{8};
102constexpr size_t WRITE_BATCH_HEADER{12}; // See kHeader in db/write_batch.cc
103
106const std::string OBFUSCATION_KEY{"\000obfuscate_key", 14};
107
111std::vector<uint8_t> MakeValue(uint16_t key, uint32_t size)
112{
113 std::vector<uint8_t> v(size);
114 std::iota(v.begin(), v.end(), static_cast<uint8_t>(key ^ (key >> 8)));
115 return v;
116}
117
120struct LevelDBBytewiseU16Cmp {
121 bool operator()(uint16_t a, uint16_t b) const { return internal_bswap_16(a) < internal_bswap_16(b); }
122};
123
125using Oracle = std::map<uint16_t, uint32_t, LevelDBBytewiseU16Cmp>;
126
127struct FailUnserialize {
128 template <typename Stream>
129 void Unserialize(Stream&) { throw std::ios_base::failure{"always fail"}; }
130};
131
132uint16_t ConsumeKey(FuzzedDataProvider& provider) { return provider.ConsumeIntegral<uint16_t>(); }
133uint32_t ConsumeValueSize(FuzzedDataProvider& provider)
134{
135 const uint16_t len{provider.ConsumeIntegralInRange<uint16_t>(0, MAX_VALUE_LEN)};
136 const uint8_t multiplier{provider.ConsumeIntegralInRange<uint8_t>(1, MAX_VALUE_MULTIPLIER)};
137 return static_cast<uint32_t>(len) * multiplier;
138}
139
142void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle,
143 bool obfuscate, std::optional<uint16_t> seek_key = std::nullopt)
144{
145 const std::unique_ptr<CDBIterator> it{dbw.NewIterator()};
146 auto oracle_it{seek_key ? oracle.lower_bound(*seek_key) : oracle.begin()};
147 if (seek_key) {
148 it->Seek(*seek_key);
149 } else {
150 it->SeekToFirst();
151 }
152 for (; it->Valid(); it->Next()) {
153 uint16_t db_key;
154 assert(it->GetKey(db_key));
155 if (oracle_it != oracle.end() && db_key == oracle_it->first) {
156 std::vector<uint8_t> db_value;
157 assert(it->GetValue(db_value));
158 assert(db_value == MakeValue(db_key, oracle_it->second));
159 ++oracle_it;
160 } else {
161 assert(obfuscate);
162 std::string key_str;
163 assert(it->GetKey(key_str));
164 assert(key_str == OBFUSCATION_KEY);
165 }
166 }
167 assert(oracle_it == oracle.end());
168}
169
171constexpr size_t MAX_READ_WORKERS{8};
172
174constexpr size_t MAX_READ_QUERIES_PER_WORKER{128};
175
176ThreadPool g_read_pool{"dbfuzz"};
177
178void StartReadPoolIfNeeded()
179{
180 if (!g_read_pool.WorkersCount()) g_read_pool.Start(MAX_READ_WORKERS);
181}
182
184DBParams ConsumeDBParams(FuzzedDataProvider& provider, leveldb::Env* testing_env,
185 bool obfuscate, DBOptions options = {})
186{
187 return DBParams{
188 .path = "dbwrapper_fuzz",
189 .cache_bytes = provider.ConsumeIntegralInRange<size_t>(64 << 10, 1_MiB),
190 .obfuscate = obfuscate,
191 .options = options,
192 .testing_env = testing_env,
193 .max_file_size = provider.ConsumeBool()
195 : provider.ConsumeIntegralInRange<size_t>(1_MiB, 4_MiB),
196 };
197}
198
199template <typename DrainWorkFn, typename RunOneFn>
201 leveldb::Env* testing_env,
202 DrainWorkFn drain_work,
203 RunOneFn run_one,
204 bool allow_force_compact)
205{
207
208 const bool obfuscate{provider.ConsumeBool()};
209
210 const auto make_db{[&](DBOptions options = {}) {
211 return std::make_unique<CDBWrapper>(ConsumeDBParams(provider, testing_env, obfuscate, options));
212 }};
213 std::unique_ptr<CDBWrapper> dbw{make_db()};
214
215 // Oracle: key → value size. Content is reconstructed via MakeValue().
216 Oracle oracle;
217
219 {
220 CallOneOf(
221 provider,
222 // --- Mutations ---
223 [&] {
224 const auto key{ConsumeKey(provider)};
225 const auto size{ConsumeValueSize(provider)};
226 drain_work();
227 dbw->Write(key, MakeValue(key, size), /*fSync=*/provider.ConsumeBool());
228 oracle[key] = size;
229 },
230 [&] {
231 const auto key{ConsumeKey(provider)};
232 drain_work();
233 dbw->Erase(key, /*fSync=*/provider.ConsumeBool());
234 oracle.erase(key);
235 },
236 [&] {
237 CDBBatch batch{*dbw};
238 std::map<uint16_t, uint32_t> batch_writes;
239 std::set<uint16_t> batch_erases;
240 const auto fill{[&] {
242 {
243 const auto key{ConsumeKey(provider)};
244 if (provider.ConsumeBool()) {
245 const auto size{ConsumeValueSize(provider)};
246 batch.Write(key, MakeValue(key, size));
247 batch_writes[key] = size;
248 batch_erases.erase(key);
249 } else {
250 batch.Erase(key);
251 batch_erases.insert(key);
252 batch_writes.erase(key);
253 }
254 }
255 }};
256 fill();
257 if (provider.ConsumeBool()) {
258 assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
259 batch.Clear();
260 assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
261 batch_writes.clear();
262 batch_erases.clear();
263 fill();
264 }
265 drain_work();
266 dbw->WriteBatch(batch, /*fSync=*/provider.ConsumeBool());
267 for (const auto& [k, v] : batch_writes) oracle[k] = v;
268 for (const auto& k : batch_erases) oracle.erase(k);
269 },
270 [&] {
271 drain_work();
272 dbw.reset();
273 DBOptions options{};
274 if (allow_force_compact && provider.ConsumeBool()) {
275 options.force_compact = true;
276 }
277 dbw = make_db(options);
278 VerifyIterator(*dbw, oracle, obfuscate);
279 },
280 // --- Reads ---
281 [&] {
282 const auto key{ConsumeKey(provider)};
283 std::vector<uint8_t> value;
284 const bool found{dbw->Read(key, value)};
285 if (const auto it{oracle.find(key)}; it != oracle.end()) {
286 assert(found && value == MakeValue(key, it->second));
287 } else {
288 assert(!found);
289 }
290 },
291 [&] {
292 const auto key{ConsumeKey(provider)};
293 assert(dbw->Exists(key) == oracle.contains(key));
294 },
295 [&] {
296 uint16_t key{};
297 if (!oracle.empty() && provider.ConsumeBool()) {
298 auto it{oracle.begin()};
299 std::advance(it, provider.ConsumeIntegralInRange<size_t>(0, oracle.size() - 1));
300 key = it->first;
301 } else {
302 key = ConsumeKey(provider);
303 }
304 FailUnserialize wrong_type;
305 assert(!dbw->Read(key, wrong_type));
306 },
307 [&] {
308 const auto seek_key{provider.ConsumeBool()
309 ? std::optional<uint16_t>{ConsumeKey(provider)}
310 : std::nullopt};
311 VerifyIterator(*dbw, oracle, obfuscate, seek_key);
312 },
313 // --- Stats ---
314 [&] {
315 assert(dbw->IsEmpty() == (oracle.empty() && !obfuscate));
316 },
317 [&] {
318 const auto [k1, k2]{std::minmax({ConsumeKey(provider), ConsumeKey(provider)}, LevelDBBytewiseU16Cmp{})};
319 const size_t estimate_size{dbw->EstimateSize(k1, k2)};
320 if (k1 == k2) assert(estimate_size == 0);
321 },
322 [&] {
323 (void)dbw->DynamicMemoryUsage();
324 },
325 // --- Compaction control (no-op when run_one is no-op) ---
326 [&] {
327 run_one();
328 });
329 }
330
331 VerifyIterator(*dbw, oracle, obfuscate);
332 drain_work();
333}
334
335} // namespace
336
337FUZZ_TARGET(dbwrapper, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
338{
339 FuzzedDataProvider provider{buffer.data(), buffer.size()};
340
341 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
342 DeterministicEnv det_env{memenv.get()};
345 [&] { det_env.DrainWork(); },
346 [&] { return det_env.RunOne(); },
347 /*allow_force_compact=*/false);
348}
349
350FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
351{
352 FuzzedDataProvider provider{buffer.data(), buffer.size()};
353
354 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
356 provider, memenv.get(),
357 /*drain_work=*/[] {},
358 /*run_one=*/[] { return false; },
359 /*allow_force_compact=*/true);
360}
361
362FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
363{
364 StartReadPoolIfNeeded();
366
367 FuzzedDataProvider provider{buffer.data(), buffer.size()};
368
369 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
370 DeterministicEnv det_env{memenv.get()};
371
372 CDBWrapper db{ConsumeDBParams(provider, &det_env, /*obfuscate=*/provider.ConsumeBool())};
373
374 // Seed the DB. Drain work after small batches so we don't deadlock on a
375 // scheduled compaction.
376 const size_t num_entries{provider.ConsumeIntegralInRange<size_t>(100, 5'000)};
377 std::vector<uint16_t> keys;
379 Oracle oracle;
380 constexpr size_t SEED_BATCH_SIZE{400};
381 for (size_t start{0}; start < num_entries; start += SEED_BATCH_SIZE) {
382 CDBBatch batch{db};
383 const size_t end{std::min(start + SEED_BATCH_SIZE, num_entries)};
384 for (size_t i{start}; i < end; ++i) {
385 const auto k{ConsumeKey(provider)};
386 const auto size{ConsumeValueSize(provider)};
387 batch.Write(k, MakeValue(k, size));
388 keys.push_back(k);
389 oracle[k] = size;
390 }
391 det_env.DrainWork();
392 db.WriteBatch(batch, /*fSync=*/true);
393 }
394
395 while (provider.ConsumeBool() && det_env.RunOne()) {}
396
397 // Build query list from seeded and random keys.
398 const size_t num_queries{provider.ConsumeIntegralInRange<size_t>(1, 2'000)};
399 enum class ReadOp { Read, Exists, IteratorSeek };
400 std::vector<std::tuple<ReadOp, uint16_t>> queries;
402 for (size_t i{0}; i < num_queries; ++i) {
404 const uint16_t key{provider.ConsumeBool()
405 ? keys[provider.ConsumeIntegralInRange<size_t>(0, keys.size() - 1)]
406 : ConsumeKey(provider)};
407 queries.emplace_back(op, key);
408 }
409
410
411 // Workers + main thread synchronize on the latch so all reads start together.
412 std::latch start_latch{static_cast<ptrdiff_t>(MAX_READ_WORKERS + 1)};
413 std::vector<std::function<void()>> tasks(MAX_READ_WORKERS);
416 return [&, seed = rng.rand256()] {
417 FastRandomContext thread_rng{seed};
418 std::vector<size_t> order(queries.size());
419 std::iota(order.begin(), order.end(), size_t{0});
420 std::ranges::shuffle(order, thread_rng);
421 const size_t queries_to_run{std::min(queries.size(), MAX_READ_QUERIES_PER_WORKER)};
422 std::vector<uint8_t> v;
423 std::string key_str;
424 start_latch.arrive_and_wait();
425 const std::unique_ptr<CDBIterator> it{db.NewIterator()};
426 // Every read must agree with the oracle, the source of truth.
427 for (const auto i : std::span{order}.first(queries_to_run)) {
428 const auto& [op, key] = queries[i];
429 switch (op) {
430 case ReadOp::Read:
431 if (const auto oit{oracle.find(key)}; oit != oracle.end()) {
432 assert(db.Read(key, v) && v == MakeValue(key, oit->second));
433 } else {
434 assert(!db.Read(key, v));
435 }
436 break;
437 case ReadOp::Exists:
438 assert(db.Exists(key) == oracle.contains(key));
439 break;
441 it->Seek(key);
442 // Skip the obfuscation metadata entry (a non-uint16_t key) if we land
443 // on it, so the result matches the oracle, which only tracks user keys.
444 if (it->Valid() && it->GetKey(key_str) && key_str == OBFUSCATION_KEY) it->Next();
445 if (const auto oit{oracle.lower_bound(key)}; oit != oracle.end()) {
446 assert(it->Valid());
447 uint16_t actual_key;
448 assert(it->GetKey(actual_key) && actual_key == oit->first);
449 assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second));
450 } else {
451 assert(!it->Valid());
452 }
453 break;
454 }
455 }
456 };
457 });
458 auto futures{*Assert(g_read_pool.Submit(std::move(tasks)))};
459
460 // Release the workers and immediately run the queued compaction on this
461 // thread, so compaction races against the concurrent reads.
462 start_latch.arrive_and_wait();
463 det_env.DrainWork();
464
465 for (auto& fut : futures) fut.get();
466 det_env.DrainWork();
467}
BSWAP_CONSTEXPR uint16_t internal_bswap_16(uint16_t x)
Definition: byteswap.h:44
#define Assert(val)
Identity function.
Definition: check.h:116
Batch of changes queued to be written to a CDBWrapper.
Definition: dbwrapper.h:83
size_t DynamicMemoryUsage() const
Definition: dbwrapper.cpp:312
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:215
CDBIterator * NewIterator()
Definition: dbwrapper.cpp:380
bool Exists(const K &key) const
Definition: dbwrapper.h:243
void Erase(const K &key, bool fSync=false)
Definition: dbwrapper.h:252
void WriteBatch(CDBBatch &batch, bool fSync=false)
Definition: dbwrapper.cpp:288
void Write(const K &key, const V &value, bool fSync=false)
Definition: dbwrapper.h:235
bool IsEmpty()
Return true if the database managed by this class contains no entries.
Definition: dbwrapper.cpp:361
size_t EstimateSize(const K &key_begin, const K &key_end) const
Definition: dbwrapper.h:278
Fast randomness source.
Definition: random.h:386
T ConsumeIntegralInRange(T min, T max)
T PickValueInArray(const T(&array)[size])
uint256 rand256() noexcept
generate a random uint256.
Definition: random.h:317
Fixed-size thread pool for running arbitrary tasks concurrently.
Definition: threadpool.h:48
void Start(int num_workers) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Start worker threads.
Definition: threadpool.h:105
static const size_t DBWRAPPER_MAX_FILE_SIZE
Definition: dbwrapper.h:29
#define FUZZ_TARGET(...)
Definition: fuzz.h:35
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
headers Read(reader)
Definition: basic.cpp:8
static RPCMethod generate()
Definition: mining.cpp:288
void Unserialize(Stream &, V)=delete
User-controlled performance and debug options.
Definition: dbwrapper.h:32
bool force_compact
Compact database on startup.
Definition: dbwrapper.h:34
Application-specific storage settings.
Definition: dbwrapper.h:38
fs::path path
Location in the filesystem where leveldb data will be stored.
Definition: dbwrapper.h:40
#define LOCK(cs)
Definition: sync.h:268
det_env DrainWork()
std::vector< uint16_t > keys
Definition: dbwrapper.cpp:377
Oracle oracle
Definition: dbwrapper.cpp:379
CDBWrapper db
Definition: dbwrapper.cpp:372
TestDbWrapper(provider, &det_env, [&] { det_env.DrainWork();}, [&] { return det_env.RunOne();}, false)
DeterministicEnv det_env
Definition: dbwrapper.cpp:342
constexpr size_t SEED_BATCH_SIZE
Definition: dbwrapper.cpp:380
const size_t num_entries
Definition: dbwrapper.cpp:376
std::vector< std::function< void()> > tasks(MAX_READ_WORKERS)
ReadOp
Definition: dbwrapper.cpp:399
@ IteratorSeek
FastRandomContext rng
Definition: dbwrapper.cpp:414
std::latch start_latch
Definition: dbwrapper.cpp:412
const size_t num_queries
Definition: dbwrapper.cpp:398
auto futures
Definition: dbwrapper.cpp:458
std::vector< std::tuple< ReadOp, uint16_t > > queries
Definition: dbwrapper.cpp:400
SeedRandomStateForTest(SeedRand::ZEROS)
const auto memenv
Definition: dbwrapper.cpp:341
FuzzedDataProvider provider
Definition: dbwrapper.cpp:367
uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept
Definition: util.h:195
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:37
@ ZEROS
Seed with a compile time constant of zeros.
static int setup(void)
Definition: tests.c:8056
#define EXCLUSIVE_LOCKS_REQUIRED(...)
Definition: threadsafety.h:49
#define GUARDED_BY(x)
Definition: threadsafety.h:37
assert(!tx.IsCoinBase())