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 .bloom_filter = provider.ConsumeBool(),
192 .options = options,
193 .testing_env = testing_env,
194 .max_file_size = provider.ConsumeBool()
196 : provider.ConsumeIntegralInRange<size_t>(1_MiB, 4_MiB),
197 };
198}
199
200template <typename DrainWorkFn, typename RunOneFn>
202 leveldb::Env* testing_env,
203 DrainWorkFn drain_work,
204 RunOneFn run_one,
205 bool allow_force_compact)
206{
208
209 const bool obfuscate{provider.ConsumeBool()};
210
211 const auto make_db{[&](DBOptions options = {}) {
212 return std::make_unique<CDBWrapper>(ConsumeDBParams(provider, testing_env, obfuscate, options));
213 }};
214 std::unique_ptr<CDBWrapper> dbw{make_db()};
215
216 // Oracle: key → value size. Content is reconstructed via MakeValue().
217 Oracle oracle;
218
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 const auto key{ConsumeKey(provider)};
243 if (provider.ConsumeBool()) {
244 const auto size{ConsumeValueSize(provider)};
245 batch.Write(key, MakeValue(key, size));
246 batch_writes[key] = size;
247 batch_erases.erase(key);
248 } else {
249 batch.Erase(key);
250 batch_erases.insert(key);
251 batch_writes.erase(key);
252 }
253 }
254 }};
255 fill();
256 if (provider.ConsumeBool()) {
257 assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
258 batch.Clear();
259 assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
260 batch_writes.clear();
261 batch_erases.clear();
262 fill();
263 }
264 drain_work();
265 dbw->WriteBatch(batch, /*fSync=*/provider.ConsumeBool());
266 for (const auto& [k, v] : batch_writes) oracle[k] = v;
267 for (const auto& k : batch_erases) oracle.erase(k);
268 },
269 [&] {
270 drain_work();
271 dbw.reset();
272 DBOptions options{};
273 if (allow_force_compact && provider.ConsumeBool()) {
274 options.force_compact = true;
275 }
276 dbw = make_db(options);
277 VerifyIterator(*dbw, oracle, obfuscate);
278 },
279 // --- Reads ---
280 [&] {
281 const auto key{ConsumeKey(provider)};
282 std::vector<uint8_t> value;
283 const bool found{dbw->Read(key, value)};
284 if (const auto it{oracle.find(key)}; it != oracle.end()) {
285 assert(found && value == MakeValue(key, it->second));
286 } else {
287 assert(!found);
288 }
289 },
290 [&] {
291 const auto key{ConsumeKey(provider)};
292 assert(dbw->Exists(key) == oracle.contains(key));
293 },
294 [&] {
295 uint16_t key{};
296 if (!oracle.empty() && provider.ConsumeBool()) {
297 auto it{oracle.begin()};
298 std::advance(it, provider.ConsumeIntegralInRange<size_t>(0, oracle.size() - 1));
299 key = it->first;
300 } else {
301 key = ConsumeKey(provider);
302 }
303 FailUnserialize wrong_type;
304 assert(!dbw->Read(key, wrong_type));
305 },
306 [&] {
307 const auto seek_key{provider.ConsumeBool()
308 ? std::optional<uint16_t>{ConsumeKey(provider)}
309 : std::nullopt};
310 VerifyIterator(*dbw, oracle, obfuscate, seek_key);
311 },
312 // --- Stats ---
313 [&] {
314 assert(dbw->IsEmpty() == (oracle.empty() && !obfuscate));
315 },
316 [&] {
317 const auto [k1, k2]{std::minmax({ConsumeKey(provider), ConsumeKey(provider)}, LevelDBBytewiseU16Cmp{})};
318 const size_t estimate_size{dbw->EstimateSize(k1, k2)};
319 if (k1 == k2) assert(estimate_size == 0);
320 },
321 [&] {
322 (void)dbw->DynamicMemoryUsage();
323 },
324 // --- Compaction control (no-op when run_one is no-op) ---
325 [&] {
326 run_one();
327 });
328 }
329
330 VerifyIterator(*dbw, oracle, obfuscate);
331 drain_work();
332}
333
334} // namespace
335
336FUZZ_TARGET(dbwrapper, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
337{
338 FuzzedDataProvider provider{buffer.data(), buffer.size()};
339
340 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
341 DeterministicEnv det_env{memenv.get()};
344 [&] { det_env.DrainWork(); },
345 [&] { return det_env.RunOne(); },
346 /*allow_force_compact=*/false);
347}
348
349FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
350{
351 FuzzedDataProvider provider{buffer.data(), buffer.size()};
352
353 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
355 provider, memenv.get(),
356 /*drain_work=*/[] {},
357 /*run_one=*/[] { return false; },
358 /*allow_force_compact=*/true);
359}
360
361FUZZ_TARGET(dbwrapper_concurrent_reads, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
362{
363 StartReadPoolIfNeeded();
365
366 FuzzedDataProvider provider{buffer.data(), buffer.size()};
367
368 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
369 DeterministicEnv det_env{memenv.get()};
370
371 CDBWrapper db{ConsumeDBParams(provider, &det_env, /*obfuscate=*/provider.ConsumeBool())};
372
373 // Seed the DB. Drain work after small batches so we don't deadlock on a
374 // scheduled compaction.
375 const size_t num_entries{provider.ConsumeIntegralInRange<size_t>(100, 5'000)};
376 std::vector<uint16_t> keys;
378 Oracle oracle;
379 constexpr size_t SEED_BATCH_SIZE{400};
380 for (size_t start{0}; start < num_entries; start += SEED_BATCH_SIZE) {
381 CDBBatch batch{db};
382 const size_t end{std::min(start + SEED_BATCH_SIZE, num_entries)};
383 for (size_t i{start}; i < end; ++i) {
384 const auto k{ConsumeKey(provider)};
385 const auto size{ConsumeValueSize(provider)};
386 batch.Write(k, MakeValue(k, size));
387 keys.push_back(k);
388 oracle[k] = size;
389 }
390 det_env.DrainWork();
391 db.WriteBatch(batch, /*fSync=*/true);
392 }
393
394 while (provider.ConsumeBool() && det_env.RunOne()) {}
395
396 // Build query list from seeded and random keys.
397 const size_t num_queries{provider.ConsumeIntegralInRange<size_t>(1, 2'000)};
398 enum class ReadOp { Read, Exists, IteratorSeek };
399 std::vector<std::tuple<ReadOp, uint16_t>> queries;
401 for (size_t i{0}; i < num_queries; ++i) {
403 const uint16_t key{provider.ConsumeBool()
404 ? keys[provider.ConsumeIntegralInRange<size_t>(0, keys.size() - 1)]
405 : ConsumeKey(provider)};
406 queries.emplace_back(op, key);
407 }
408
409
410 // Workers + main thread synchronize on the latch so all reads start together.
411 std::latch start_latch{static_cast<ptrdiff_t>(MAX_READ_WORKERS + 1)};
412 std::vector<std::function<void()>> tasks(MAX_READ_WORKERS);
415 return [&, seed = rng.rand256()] {
416 FastRandomContext thread_rng{seed};
417 std::vector<size_t> order(queries.size());
418 std::iota(order.begin(), order.end(), size_t{0});
419 std::ranges::shuffle(order, thread_rng);
420 const size_t queries_to_run{std::min(queries.size(), MAX_READ_QUERIES_PER_WORKER)};
421 std::vector<uint8_t> v;
422 std::string key_str;
423 start_latch.arrive_and_wait();
424 const std::unique_ptr<CDBIterator> it{db.NewIterator()};
425 // Every read must agree with the oracle, the source of truth.
426 for (const auto i : std::span{order}.first(queries_to_run)) {
427 const auto& [op, key] = queries[i];
428 switch (op) {
429 case ReadOp::Read:
430 if (const auto oit{oracle.find(key)}; oit != oracle.end()) {
431 assert(db.Read(key, v) && v == MakeValue(key, oit->second));
432 } else {
433 assert(!db.Read(key, v));
434 }
435 break;
436 case ReadOp::Exists:
437 assert(db.Exists(key) == oracle.contains(key));
438 break;
440 it->Seek(key);
441 // Skip the obfuscation metadata entry (a non-uint16_t key) if we land
442 // on it, so the result matches the oracle, which only tracks user keys.
443 if (it->Valid() && it->GetKey(key_str) && key_str == OBFUSCATION_KEY) it->Next();
444 if (const auto oit{oracle.lower_bound(key)}; oit != oracle.end()) {
445 assert(it->Valid());
446 uint16_t actual_key;
447 assert(it->GetKey(actual_key) && actual_key == oit->first);
448 assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second));
449 } else {
450 assert(!it->Valid());
451 }
452 break;
453 }
454 }
455 };
456 });
457 auto futures{*Assert(g_read_pool.Submit(std::move(tasks)))};
458
459 // Release the workers and immediately run the queued compaction on this
460 // thread, so compaction races against the concurrent reads.
461 start_latch.arrive_and_wait();
462 det_env.DrainWork();
463
464 for (auto& fut : futures) fut.get();
465 det_env.DrainWork();
466}
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:88
size_t DynamicMemoryUsage() const
Definition: dbwrapper.cpp:312
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:220
CDBIterator * NewIterator()
Definition: dbwrapper.cpp:380
bool Exists(const K &key) const
Definition: dbwrapper.h:248
void Erase(const K &key, bool fSync=false)
Definition: dbwrapper.h:257
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:240
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:283
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
LIMITED_WHILE(provider.remaining_bytes(), 10000)
static const size_t DBWRAPPER_MAX_FILE_SIZE
Definition: dbwrapper.h:32
#define FUZZ_TARGET(...)
Definition: fuzz.h:35
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:35
bool force_compact
Compact database on startup.
Definition: dbwrapper.h:37
Application-specific storage settings.
Definition: dbwrapper.h:41
fs::path path
Location in the filesystem where leveldb data will be stored.
Definition: dbwrapper.h:43
#define LOCK(cs)
Definition: sync.h:268
det_env DrainWork()
std::vector< uint16_t > keys
Definition: dbwrapper.cpp:376
Oracle oracle
Definition: dbwrapper.cpp:378
CDBWrapper db
Definition: dbwrapper.cpp:371
TestDbWrapper(provider, &det_env, [&] { det_env.DrainWork();}, [&] { return det_env.RunOne();}, false)
DeterministicEnv det_env
Definition: dbwrapper.cpp:341
constexpr size_t SEED_BATCH_SIZE
Definition: dbwrapper.cpp:379
const size_t num_entries
Definition: dbwrapper.cpp:375
std::vector< std::function< void()> > tasks(MAX_READ_WORKERS)
ReadOp
Definition: dbwrapper.cpp:398
@ IteratorSeek
FastRandomContext rng
Definition: dbwrapper.cpp:413
std::latch start_latch
Definition: dbwrapper.cpp:411
const size_t num_queries
Definition: dbwrapper.cpp:397
auto futures
Definition: dbwrapper.cpp:457
std::vector< std::tuple< ReadOp, uint16_t > > queries
Definition: dbwrapper.cpp:399
SeedRandomStateForTest(SeedRand::ZEROS)
const auto memenv
Definition: dbwrapper.cpp:340
FuzzedDataProvider provider
Definition: dbwrapper.cpp:366
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())