18#include <leveldb/env.h>
19#include <leveldb/helpers/memenv/memenv.h>
60class DeterministicEnv final :
public leveldb::EnvWrapper
62 using WorkFunction = void (*)(
void*);
65 WorkFunction function;
73 explicit DeterministicEnv(leveldb::Env* base) : EnvWrapper(base) {}
78 m_queue.push_back({function, arg});
88 if (m_queue.empty())
return false;
89 work = m_queue.front();
92 work.function(work.arg);
100constexpr size_t MAX_VALUE_LEN{4096};
101constexpr uint8_t MAX_VALUE_MULTIPLIER{8};
102constexpr size_t WRITE_BATCH_HEADER{12};
106const std::string OBFUSCATION_KEY{
"\000obfuscate_key", 14};
111std::vector<uint8_t> MakeValue(uint16_t key, uint32_t size)
113 std::vector<uint8_t> v(size);
114 std::iota(v.begin(), v.end(),
static_cast<uint8_t
>(key ^ (key >> 8)));
120struct LevelDBBytewiseU16Cmp {
125using Oracle = std::map<uint16_t, uint32_t, LevelDBBytewiseU16Cmp>;
127struct FailUnserialize {
128 template <
typename Stream>
129 void Unserialize(Stream&) {
throw std::ios_base::failure{
"always fail"}; }
137 return static_cast<uint32_t
>(len) * multiplier;
143 bool obfuscate, std::optional<uint16_t> seek_key = std::nullopt)
145 const std::unique_ptr<CDBIterator> it{dbw.
NewIterator()};
146 auto oracle_it{seek_key ?
oracle.lower_bound(*seek_key) :
oracle.begin()};
152 for (; it->Valid(); it->Next()) {
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));
163 assert(it->GetKey(key_str));
164 assert(key_str == OBFUSCATION_KEY);
171constexpr size_t MAX_READ_WORKERS{8};
174constexpr size_t MAX_READ_QUERIES_PER_WORKER{128};
178void StartReadPoolIfNeeded()
180 if (!g_read_pool.WorkersCount()) g_read_pool.
Start(MAX_READ_WORKERS);
188 .
path =
"dbwrapper_fuzz",
190 .obfuscate = obfuscate,
192 .testing_env = testing_env,
199template <
typename DrainWorkFn,
typename RunOneFn>
201 leveldb::Env* testing_env,
202 DrainWorkFn drain_work,
204 bool allow_force_compact)
210 const auto make_db{[&](
DBOptions options = {}) {
211 return std::make_unique<CDBWrapper>(ConsumeDBParams(
provider, testing_env, obfuscate, options));
213 std::unique_ptr<CDBWrapper> dbw{make_db()};
224 const auto key{ConsumeKey(
provider)};
225 const auto size{ConsumeValueSize(
provider)};
231 const auto key{ConsumeKey(
provider)};
238 std::map<uint16_t, uint32_t> batch_writes;
239 std::set<uint16_t> batch_erases;
240 const auto fill{[&] {
243 const auto key{ConsumeKey(
provider)};
245 const auto size{ConsumeValueSize(
provider)};
246 batch.Write(key, MakeValue(key, size));
247 batch_writes[key] = size;
248 batch_erases.erase(key);
251 batch_erases.insert(key);
252 batch_writes.erase(key);
258 assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
260 assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
261 batch_writes.clear();
262 batch_erases.clear();
267 for (
const auto& [k, v] : batch_writes)
oracle[
k] = v;
268 for (
const auto& k : batch_erases)
oracle.erase(k);
277 dbw = make_db(options);
278 VerifyIterator(*dbw,
oracle, obfuscate);
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));
292 const auto key{ConsumeKey(
provider)};
304 FailUnserialize wrong_type;
309 ? std::optional<uint16_t>{ConsumeKey(
provider)}
311 VerifyIterator(*dbw,
oracle, obfuscate, seek_key);
318 const auto [k1, k2]{std::minmax({ConsumeKey(
provider), ConsumeKey(
provider)}, LevelDBBytewiseU16Cmp{})};
320 if (k1 == k2)
assert(estimate_size == 0);
331 VerifyIterator(*dbw,
oracle, obfuscate);
341 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
346 [&] {
return det_env.RunOne(); },
354 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
358 [] { return false; },
362FUZZ_TARGET(dbwrapper_concurrent_reads, .
init = [] {
static auto setup{MakeNoLogFileContext<>()}; })
364 StartReadPoolIfNeeded();
369 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
384 for (
size_t i{start}; i < end; ++i) {
386 const auto size{ConsumeValueSize(
provider)};
387 batch.Write(k, MakeValue(k, size));
400 std::vector<std::tuple<ReadOp, uint16_t>>
queries;
412 std::latch
start_latch{
static_cast<ptrdiff_t
>(MAX_READ_WORKERS + 1)};
413 std::vector<std::function<void()>>
tasks(MAX_READ_WORKERS);
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;
427 for (
const auto i : std::span{order}.first(queries_to_run)) {
428 const auto& [op, key] =
queries[i];
431 if (
const auto oit{
oracle.find(key)}; oit !=
oracle.end()) {
432 assert(
db.
Read(key, v) && v == MakeValue(key, oit->second));
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()) {
448 assert(it->GetKey(actual_key) && actual_key == oit->first);
449 assert(it->GetValue(v) && v == MakeValue(actual_key, oit->second));
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])
uint256 rand256() noexcept
generate a random uint256.
Fixed-size thread pool for running arbitrary tasks concurrently.
void Start(int num_workers) EXCLUSIVE_LOCKS_REQUIRED(!m_mutex)
Start worker threads.
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.
std::vector< uint16_t > keys
TestDbWrapper(provider, &det_env, [&] { det_env.DrainWork();}, [&] { return det_env.RunOne();}, false)
constexpr size_t SEED_BATCH_SIZE
std::vector< std::function< void()> > tasks(MAX_READ_WORKERS)
std::vector< std::tuple< ReadOp, uint16_t > > queries
SeedRandomStateForTest(SeedRand::ZEROS)
FuzzedDataProvider provider
uint256 ConsumeUInt256(FuzzedDataProvider &fuzzed_data_provider) noexcept
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
@ ZEROS
Seed with a compile time constant of zeros.
#define EXCLUSIVE_LOCKS_REQUIRED(...)