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>
8#include <test/fuzz/fuzz.h>
9#include <test/fuzz/util.h>
10#include <test/util/random.h>
12#include <util/byte_units.h>
13
14#include <leveldb/env.h>
15#include <leveldb/helpers/memenv/memenv.h>
16
17#include <algorithm>
18#include <cassert>
19#include <cstdint>
20#include <deque>
21#include <map>
22#include <memory>
23#include <numeric>
24#include <optional>
25#include <set>
26#include <string>
27#include <vector>
28
29namespace {
30
51class DeterministicEnv final : public leveldb::EnvWrapper
52{
53 using WorkFunction = void (*)(void*);
54
55 struct Work {
56 WorkFunction function;
57 void* arg;
58 };
59
60 std::deque<Work> m_queue;
61
62public:
63 explicit DeterministicEnv(leveldb::Env* base) : EnvWrapper(base) {}
64
65 void Schedule(WorkFunction function, void* arg) override
66 {
67 m_queue.push_back({function, arg});
68 }
69
72 bool RunOne()
73 {
74 if (m_queue.empty()) return false;
75 const Work work{m_queue.front()};
76 m_queue.pop_front();
77 work.function(work.arg);
78 return true;
79 }
80
82 void DrainWork() { while (RunOne()) {} }
83};
84
85constexpr size_t MAX_VALUE_LEN{4096};
86constexpr uint8_t MAX_VALUE_MULTIPLIER{8};
87constexpr size_t WRITE_BATCH_HEADER{12}; // See kHeader in db/write_batch.cc
88
91const std::string OBFUSCATION_KEY{"\000obfuscate_key", 14};
92
96std::vector<uint8_t> MakeValue(uint16_t key, uint32_t size)
97{
98 std::vector<uint8_t> v(size);
99 std::iota(v.begin(), v.end(), static_cast<uint8_t>(key ^ (key >> 8)));
100 return v;
101}
102
105struct LevelDBBytewiseU16Cmp {
106 bool operator()(uint16_t a, uint16_t b) const { return internal_bswap_16(a) < internal_bswap_16(b); }
107};
108
110using Oracle = std::map<uint16_t, uint32_t, LevelDBBytewiseU16Cmp>;
111
112struct FailUnserialize {
113 template <typename Stream>
114 void Unserialize(Stream&) { throw std::ios_base::failure{"always fail"}; }
115};
116
117uint16_t ConsumeKey(FuzzedDataProvider& provider) { return provider.ConsumeIntegral<uint16_t>(); }
118uint32_t ConsumeValueSize(FuzzedDataProvider& provider)
119{
120 const uint16_t len{provider.ConsumeIntegralInRange<uint16_t>(0, MAX_VALUE_LEN)};
121 const uint8_t multiplier{provider.ConsumeIntegralInRange<uint8_t>(1, MAX_VALUE_MULTIPLIER)};
122 return static_cast<uint32_t>(len) * multiplier;
123}
124
127void VerifyIterator(CDBWrapper& dbw, const Oracle& oracle,
128 bool obfuscate, std::optional<uint16_t> seek_key = std::nullopt)
129{
130 const std::unique_ptr<CDBIterator> it{dbw.NewIterator()};
131 auto oracle_it{seek_key ? oracle.lower_bound(*seek_key) : oracle.begin()};
132 if (seek_key) {
133 it->Seek(*seek_key);
134 } else {
135 it->SeekToFirst();
136 }
137 for (; it->Valid(); it->Next()) {
138 uint16_t db_key;
139 assert(it->GetKey(db_key));
140 if (oracle_it != oracle.end() && db_key == oracle_it->first) {
141 std::vector<uint8_t> db_value;
142 assert(it->GetValue(db_value));
143 assert(db_value == MakeValue(db_key, oracle_it->second));
144 ++oracle_it;
145 } else {
146 assert(obfuscate);
147 std::string key_str;
148 assert(it->GetKey(key_str));
149 assert(key_str == OBFUSCATION_KEY);
150 }
151 }
152 assert(oracle_it == oracle.end());
153}
154
155template <typename DrainWorkFn, typename RunOneFn>
156void TestDbWrapper(FuzzedDataProvider& provider,
157 leveldb::Env* testing_env,
158 DrainWorkFn drain_work,
159 RunOneFn run_one,
160 bool allow_force_compact)
161{
163
164 const bool obfuscate{provider.ConsumeBool()};
165
166 const auto make_db{[&](DBOptions options = {}) {
167 return std::make_unique<CDBWrapper>(DBParams{
168 .path = "dbwrapper_fuzz",
169 .cache_bytes = provider.ConsumeIntegralInRange<size_t>(64 << 10, 1_MiB),
170 .obfuscate = obfuscate,
171 .options = options,
172 .testing_env = testing_env,
173 .max_file_size = provider.ConsumeBool()
175 : provider.ConsumeIntegralInRange<size_t>(1_MiB, 4_MiB),
176 });
177 }};
178 std::unique_ptr<CDBWrapper> dbw{make_db()};
179
180 // Oracle: key → value size. Content is reconstructed via MakeValue().
181 Oracle oracle;
182
183 LIMITED_WHILE(provider.ConsumeBool(), 1'000)
184 {
185 CallOneOf(
186 provider,
187 // --- Mutations ---
188 [&] {
189 const auto key{ConsumeKey(provider)};
190 const auto size{ConsumeValueSize(provider)};
191 drain_work();
192 dbw->Write(key, MakeValue(key, size), /*fSync=*/provider.ConsumeBool());
193 oracle[key] = size;
194 },
195 [&] {
196 const auto key{ConsumeKey(provider)};
197 drain_work();
198 dbw->Erase(key, /*fSync=*/provider.ConsumeBool());
199 oracle.erase(key);
200 },
201 [&] {
202 CDBBatch batch{*dbw};
203 std::map<uint16_t, uint32_t> batch_writes;
204 std::set<uint16_t> batch_erases;
205 const auto fill{[&] {
206 LIMITED_WHILE(provider.ConsumeBool(), 20)
207 {
208 const auto key{ConsumeKey(provider)};
209 if (provider.ConsumeBool()) {
210 const auto size{ConsumeValueSize(provider)};
211 batch.Write(key, MakeValue(key, size));
212 batch_writes[key] = size;
213 batch_erases.erase(key);
214 } else {
215 batch.Erase(key);
216 batch_erases.insert(key);
217 batch_writes.erase(key);
218 }
219 }
220 }};
221 fill();
222 if (provider.ConsumeBool()) {
223 assert(batch.ApproximateSize() >= WRITE_BATCH_HEADER);
224 batch.Clear();
225 assert(batch.ApproximateSize() == WRITE_BATCH_HEADER);
226 batch_writes.clear();
227 batch_erases.clear();
228 fill();
229 }
230 drain_work();
231 dbw->WriteBatch(batch, /*fSync=*/provider.ConsumeBool());
232 for (const auto& [k, v] : batch_writes) oracle[k] = v;
233 for (const auto& k : batch_erases) oracle.erase(k);
234 },
235 [&] {
236 drain_work();
237 dbw.reset();
238 DBOptions options{};
239 if (allow_force_compact && provider.ConsumeBool()) {
240 options.force_compact = true;
241 }
242 dbw = make_db(options);
243 VerifyIterator(*dbw, oracle, obfuscate);
244 },
245 // --- Reads ---
246 [&] {
247 const auto key{ConsumeKey(provider)};
248 std::vector<uint8_t> value;
249 const bool found{dbw->Read(key, value)};
250 if (const auto it{oracle.find(key)}; it != oracle.end()) {
251 assert(found && value == MakeValue(key, it->second));
252 } else {
253 assert(!found);
254 }
255 },
256 [&] {
257 const auto key{ConsumeKey(provider)};
258 assert(dbw->Exists(key) == oracle.contains(key));
259 },
260 [&] {
261 uint16_t key{};
262 if (!oracle.empty() && provider.ConsumeBool()) {
263 auto it{oracle.begin()};
264 std::advance(it, provider.ConsumeIntegralInRange<size_t>(0, oracle.size() - 1));
265 key = it->first;
266 } else {
267 key = ConsumeKey(provider);
268 }
269 FailUnserialize wrong_type;
270 assert(!dbw->Read(key, wrong_type));
271 },
272 [&] {
273 const auto seek_key{provider.ConsumeBool()
274 ? std::optional<uint16_t>{ConsumeKey(provider)}
275 : std::nullopt};
276 VerifyIterator(*dbw, oracle, obfuscate, seek_key);
277 },
278 // --- Stats ---
279 [&] {
280 assert(dbw->IsEmpty() == (oracle.empty() && !obfuscate));
281 },
282 [&] {
283 const auto [k1, k2]{std::minmax({ConsumeKey(provider), ConsumeKey(provider)}, LevelDBBytewiseU16Cmp{})};
284 const size_t estimate_size{dbw->EstimateSize(k1, k2)};
285 if (k1 == k2) assert(estimate_size == 0);
286 },
287 [&] {
288 (void)dbw->DynamicMemoryUsage();
289 },
290 // --- Compaction control (no-op when run_one is no-op) ---
291 [&] {
292 run_one();
293 });
294 }
295
296 VerifyIterator(*dbw, oracle, obfuscate);
297 drain_work();
298}
299
300} // namespace
301
302FUZZ_TARGET(dbwrapper, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
303{
304 FuzzedDataProvider provider{buffer.data(), buffer.size()};
305
306 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
307 DeterministicEnv det_env{memenv.get()};
309 provider, &det_env,
310 [&] { det_env.DrainWork(); },
311 [&] { return det_env.RunOne(); },
312 /*allow_force_compact=*/false);
313}
314
315FUZZ_TARGET(dbwrapper_threaded, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
316{
317 FuzzedDataProvider provider{buffer.data(), buffer.size()};
318
319 const auto memenv{std::unique_ptr<leveldb::Env>{leveldb::NewMemEnv(leveldb::Env::Default())}};
321 provider, memenv.get(),
322 /*drain_work=*/[] {},
323 /*run_one=*/[] { return false; },
324 /*allow_force_compact=*/true);
325}
BSWAP_CONSTEXPR uint16_t internal_bswap_16(uint16_t x)
Definition: byteswap.h:44
Batch of changes queued to be written to a CDBWrapper.
Definition: dbwrapper.h:83
void Seek(const K &key)
Definition: dbwrapper.h:153
size_t DynamicMemoryUsage() const
Definition: dbwrapper.cpp:304
bool Read(const K &key, V &value) const
Definition: dbwrapper.h:215
CDBIterator * NewIterator()
Definition: dbwrapper.cpp:373
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:354
size_t EstimateSize(const K &key_begin, const K &key_end) const
Definition: dbwrapper.h:272
T ConsumeIntegralInRange(T min, T max)
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
Definition: basic.cpp:8
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
TestDbWrapper(provider, &det_env, [&] { det_env.DrainWork();}, [&] { return det_env.RunOne();}, false)
DeterministicEnv det_env
Definition: dbwrapper.cpp:307
const auto memenv
Definition: dbwrapper.cpp:306
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:37
void SeedRandomStateForTest(SeedRand seedtype)
Seed the global RNG state for testing and log the seed value.
Definition: random.cpp:19
@ ZEROS
Seed with a compile time constant of zeros.
static int setup(void)
Definition: tests.c:8040
assert(!tx.IsCoinBase())