18#include <leveldb/env.h>
19#include <leveldb/helpers/memenv/memenv.h>
59class DeterministicEnv final :
public leveldb::EnvWrapper
61 using WorkFunction = void (*)(
void*);
64 WorkFunction function;
72 explicit DeterministicEnv(leveldb::Env* base) : EnvWrapper(base) {}
77 m_queue.push_back({function, arg});
87 if (m_queue.empty())
return false;
88 work = m_queue.front();
91 work.function(work.arg);
99constexpr size_t MAX_VALUE_LEN{4096};
100constexpr uint8_t MAX_VALUE_MULTIPLIER{8};
101constexpr size_t WRITE_BATCH_HEADER{12};
105const std::string OBFUSCATION_KEY{
"\000obfuscate_key", 14};
110std::vector<uint8_t> MakeValue(uint16_t key, uint32_t size)
112 std::vector<uint8_t> v(size);
113 std::iota(v.begin(), v.end(),
static_cast<uint8_t
>(key ^ (key >> 8)));
119struct LevelDBBytewiseU16Cmp {
124using Oracle = std::map<uint16_t, uint32_t, LevelDBBytewiseU16Cmp>;
126struct FailUnserialize {
127 template <
typename Stream>
128 void Unserialize(Stream&) {
throw std::ios_base::failure{
"always fail"}; }
136 return static_cast<uint32_t
>(len) * multiplier;
141void VerifyIterator(
CDBWrapper& dbw,
const Oracle& oracle,
142 bool obfuscate, std::optional<uint16_t> seek_key = std::nullopt)
144 const std::unique_ptr<CDBIterator> it{dbw.
NewIterator()};
145 auto oracle_it{seek_key ? oracle.lower_bound(*seek_key) : oracle.begin()};
151 for (; it->Valid(); it->Next()) {
153 assert(it->GetKey(db_key));
154 if (oracle_it != oracle.end() && db_key == oracle_it->first) {
155 std::vector<uint8_t> db_value;
156 assert(it->GetValue(db_value));
157 assert(db_value == MakeValue(db_key, oracle_it->second));
162 assert(it->GetKey(key_str));
163 assert(key_str == OBFUSCATION_KEY);
166 assert(oracle_it == oracle.end());
170constexpr size_t MAX_READ_WORKERS{8};
173Mutex g_read_pool_mutex;
177 LOCK(g_read_pool_mutex);
178 if (!g_read_pool.WorkersCount()) g_read_pool.Start(MAX_READ_WORKERS);
186 .
path =
"dbwrapper_fuzz",
188 .obfuscate = obfuscate,
190 .testing_env = testing_env,
197template <
typename DrainWorkFn,
typename RunOneFn>
199 leveldb::Env* testing_env,
200 DrainWorkFn drain_work,
202 bool allow_force_compact)
208 const auto make_db{[&](
DBOptions options = {}) {
209 return std::make_unique<CDBWrapper>(ConsumeDBParams(provider, testing_env, obfuscate, options));
211 std::unique_ptr<CDBWrapper> dbw{make_db()};
222 const auto key{ConsumeKey(provider)};
223 const auto size{ConsumeValueSize(provider)};
229 const auto key{ConsumeKey(provider)};
236 std::map<uint16_t, uint32_t> batch_writes;
237 std::set<uint16_t> batch_erases;
238 const auto fill{[&] {
241 const auto key{ConsumeKey(provider)};
243 const auto size{ConsumeValueSize(provider)};
244 batch.Write(key, MakeValue(key, size));
245 batch_writes[key] = size;
246 batch_erases.erase(key);
249 batch_erases.insert(key);
250 batch_writes.erase(key);
256 assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
258 assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
259 batch_writes.clear();
260 batch_erases.clear();
265 for (
const auto& [k, v] : batch_writes) oracle[
k] = v;
266 for (
const auto& k : batch_erases) oracle.erase(k);
272 if (allow_force_compact && provider.
ConsumeBool()) {
275 dbw = make_db(options);
276 VerifyIterator(*dbw, oracle, obfuscate);
280 const auto key{ConsumeKey(provider)};
281 std::vector<uint8_t> value;
282 const bool found{dbw->
Read(key, value)};
283 if (
const auto it{oracle.find(key)}; it != oracle.end()) {
284 assert(found && value == MakeValue(key, it->second));
290 const auto key{ConsumeKey(provider)};
296 auto it{oracle.begin()};
300 key = ConsumeKey(provider);
302 FailUnserialize wrong_type;
307 ? std::optional<uint16_t>{ConsumeKey(provider)}
309 VerifyIterator(*dbw, oracle, obfuscate, seek_key);
316 const auto [k1, k2]{std::minmax({ConsumeKey(provider), ConsumeKey(provider)}, LevelDBBytewiseU16Cmp{})};
318 if (k1 == k2)
assert(estimate_size == 0);
329 VerifyIterator(*dbw, oracle, obfuscate);
339 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
344 [&] {
return det_env.RunOne(); },
352 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
356 [] { return false; },
362 StartReadPoolIfNeeded();
367 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
375 std::vector<uint16_t> keys;
376 keys.reserve(num_entries);
378 constexpr size_t SEED_BATCH_SIZE{400};
379 for (
size_t start{0}; start < num_entries; start += SEED_BATCH_SIZE) {
381 const size_t end{std::min(start + SEED_BATCH_SIZE, num_entries)};
382 for (
size_t i{start}; i < end; ++i) {
383 const auto k{ConsumeKey(provider)};
384 const auto size{ConsumeValueSize(provider)};
385 batch.Write(k, MakeValue(k, size));
390 db.WriteBatch(batch,
true);
397 enum class ReadOp { Read, Exists, IteratorSeek };
398 std::vector<std::tuple<ReadOp, uint16_t>> queries;
399 queries.reserve(num_queries);
400 for (
size_t i{0}; i < num_queries; ++i) {
401 const auto op{provider.
PickValueInArray({ReadOp::Read, ReadOp::Exists, ReadOp::IteratorSeek})};
404 : ConsumeKey(provider)};
405 queries.emplace_back(op, key);
410 std::latch start_latch{
static_cast<ptrdiff_t
>(MAX_READ_WORKERS + 1)};
411 std::vector<std::function<void()>> tasks(MAX_READ_WORKERS);
414 return [&, seed = rng.rand256()] {
416 std::vector<size_t> order(queries.size());
417 std::iota(order.begin(), order.end(),
size_t{0});
418 std::ranges::shuffle(order, thread_rng);
419 std::vector<uint8_t> v;
421 start_latch.arrive_and_wait();
422 const std::unique_ptr<CDBIterator> it{db.NewIterator()};
424 for (
const auto i : order) {
425 const auto& [op, key] = queries[i];
428 if (
const auto oit{oracle.find(key)}; oit != oracle.end()) {
429 assert(db.Read(key, v) && v == MakeValue(key, oit->second));
435 assert(db.Exists(key) == oracle.contains(key));
437 case ReadOp::IteratorSeek:
441 if (it->Valid() && it->GetKey(key_str) && key_str == OBFUSCATION_KEY) it->Next();
442 if (
const auto oit{oracle.lower_bound(key)}; oit != oracle.end()) {
445 assert(it->GetKey(actual_key) && actual_key == oit->first);
446 assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second));
455 auto futures{*
Assert(g_read_pool.Submit(std::move(tasks)))};
459 start_latch.arrive_and_wait();
462 for (
auto& fut : futures) fut.get();
BSWAP_CONSTEXPR uint16_t internal_bswap_16(uint16_t x)
#define Assert(val)
Identity function.
Batch of changes queued to be written to a CDBWrapper.
size_t DynamicMemoryUsage() const
bool Read(const K &key, V &value) const
CDBIterator * NewIterator()
bool Exists(const K &key) const
void Erase(const K &key, bool fSync=false)
void WriteBatch(CDBBatch &batch, bool fSync=false)
void Write(const K &key, const V &value, bool fSync=false)
bool IsEmpty()
Return true if the database managed by this class contains no entries.
size_t EstimateSize(const K &key_begin, const K &key_end) const
T ConsumeIntegralInRange(T min, T max)
T PickValueInArray(const T(&array)[size])
Fixed-size thread pool for running arbitrary tasks concurrently.
static const size_t DBWRAPPER_MAX_FILE_SIZE
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
static RPCMethod generate()
void Unserialize(Stream &, V)=delete
User-controlled performance and debug options.
bool force_compact
Compact database on startup.
Application-specific storage settings.
fs::path path
Location in the filesystem where leveldb data will be stored.
FUZZ_TARGET(dbwrapper_concurrent_reads,.init=[] { static auto setup{MakeNoLogFileContext<>()};}) EXCLUSIVE_LOCKS_REQUIRED(!g_read_pool_mutex)
TestDbWrapper(provider, &det_env, [&] { det_env.DrainWork();}, [&] { return det_env.RunOne();}, false)
uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
@ ZEROS
Seed with a compile time constant of zeros.
#define EXCLUSIVE_LOCKS_REQUIRED(...)