Skip to content

Commit 5b8c18f

Browse files
[storage/fuzz] generalize fuzz tests on merkle family (#3646)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 04fb289 commit 5b8c18f

11 files changed

Lines changed: 353 additions & 291 deletions

storage/fuzz/fuzz_targets/current_crash_recovery.rs

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use commonware_runtime::{
1515
};
1616
use commonware_storage::{
1717
journal::contiguous::variable::Config as VConfig,
18-
mmr::{self, journaled::Config as MmrConfig, Location},
18+
merkle::{journaled::Config as MerkleConfig, mmb, mmr, Graftable, Location},
1919
qmdb::current::{unordered::variable::Db as Current, VariableConfig},
2020
translator::TwoCap,
2121
};
@@ -34,7 +34,7 @@ type RawValue = [u8; 32];
3434
/// Maximum write buffer size.
3535
const MAX_WRITE_BUF: usize = 2048;
3636

37-
type Db = Current<mmr::Family, deterministic::Context, Key, Value, Sha256, TwoCap, 32>;
37+
type Db<F> = Current<F, deterministic::Context, Key, Value, Sha256, TwoCap, 32>;
3838

3939
fn bounded_page_size(u: &mut Unstructured<'_>) -> Result<u16> {
4040
u.int_in_range(1..=256)
@@ -75,7 +75,7 @@ struct FuzzInput {
7575
#[arbitrary(with = bounded_page_cache_size)]
7676
page_cache_size: usize,
7777
#[arbitrary(with = bounded_items_per_blob)]
78-
mmr_items_per_blob: u64,
78+
merkle_items_per_blob: u64,
7979
#[arbitrary(with = bounded_items_per_blob)]
8080
log_items_per_blob: u64,
8181
#[arbitrary(with = bounded_write_buffer)]
@@ -92,16 +92,16 @@ fn make_config(
9292
suffix: &str,
9393
page_size: NonZeroU16,
9494
page_cache_size: NonZeroUsize,
95-
mmr_items_per_blob: u64,
95+
merkle_items_per_blob: u64,
9696
log_items_per_blob: u64,
9797
write_buffer: NonZeroUsize,
9898
) -> VariableConfig<TwoCap, ((), ())> {
9999
let page_cache = CacheRef::from_pooler(ctx, page_size, page_cache_size);
100100
VariableConfig {
101-
merkle_config: MmrConfig {
102-
journal_partition: format!("crash-mmr-journal-{suffix}"),
103-
metadata_partition: format!("crash-mmr-metadata-{suffix}"),
104-
items_per_blob: NZU64!(mmr_items_per_blob),
101+
merkle_config: MerkleConfig {
102+
journal_partition: format!("crash-merkle-journal-{suffix}"),
103+
metadata_partition: format!("crash-merkle-metadata-{suffix}"),
104+
items_per_blob: NZU64!(merkle_items_per_blob),
105105
write_buffer,
106106
thread_pool: None,
107107
page_cache: page_cache.clone(),
@@ -114,7 +114,7 @@ fn make_config(
114114
codec_config: ((), ()),
115115
page_cache,
116116
},
117-
grafted_metadata_partition: format!("crash-grafted-mmr-metadata-{suffix}"),
117+
grafted_metadata_partition: format!("crash-grafted-merkle-metadata-{suffix}"),
118118
translator: TwoCap,
119119
}
120120
}
@@ -147,8 +147,8 @@ fn apply_pending(
147147
}
148148

149149
/// Commit pending writes. Returns `true` on success, `false` on error.
150-
async fn commit_pending(
151-
db: &mut Db,
150+
async fn commit_pending<F: Graftable>(
151+
db: &mut Db<F>,
152152
pending_writes: &mut Vec<(Key, Option<Value>)>,
153153
pending: &mut HashMap<RawKey, Option<RawValue>>,
154154
committed: &mut HashMap<RawKey, RawValue>,
@@ -177,20 +177,20 @@ async fn commit_pending(
177177
true
178178
}
179179

180-
fn fuzz(input: FuzzInput) {
180+
fn fuzz_family<F: Graftable>(input: &FuzzInput, suffix_base: &str) {
181181
if input.operations.is_empty() {
182182
return;
183183
}
184184

185185
let page_size = NonZeroU16::new(input.page_size).unwrap();
186186
let page_cache_size = NonZeroUsize::new(input.page_cache_size).unwrap();
187-
let mmr_items_per_blob = input.mmr_items_per_blob;
187+
let merkle_items_per_blob = input.merkle_items_per_blob;
188188
let log_items_per_blob = input.log_items_per_blob;
189189
let write_buffer = NonZeroUsize::new(input.write_buffer).unwrap();
190190
let sync_failure_rate = input.sync_failure_rate;
191191
let write_failure_rate = input.write_failure_rate;
192-
let operations = input.operations;
193-
let suffix = format!("current_{}", input.seed);
192+
let operations = input.operations.clone();
193+
let suffix = format!("{suffix_base}_{}", input.seed);
194194

195195
let cfg = deterministic::Config::default().with_seed(input.seed);
196196
let runner = deterministic::Runner::new(cfg);
@@ -201,14 +201,14 @@ fn fuzz(input: FuzzInput) {
201201
let suffix = suffix.clone();
202202
let operations = operations.clone();
203203
async move {
204-
let mut db = Db::init(
204+
let mut db: Db<F> = Db::init(
205205
ctx.with_label("db"),
206206
make_config(
207207
&ctx,
208208
&suffix,
209209
page_size,
210210
page_cache_size,
211-
mmr_items_per_blob,
211+
merkle_items_per_blob,
212212
log_items_per_blob,
213213
write_buffer,
214214
),
@@ -283,14 +283,14 @@ fn fuzz(input: FuzzInput) {
283283
async move {
284284
*ctx.storage_fault_config().write() = deterministic::FaultConfig::default();
285285

286-
let mut db = Db::init(
286+
let mut db: Db<F> = Db::init(
287287
ctx.with_label("recovered"),
288288
make_config(
289289
&ctx,
290290
&suffix,
291291
page_size,
292292
page_cache_size,
293-
mmr_items_per_blob,
293+
merkle_items_per_blob,
294294
log_items_per_blob,
295295
write_buffer,
296296
),
@@ -318,7 +318,7 @@ fn fuzz(input: FuzzInput) {
318318
.await
319319
.expect("proof generation should not fail for committed key");
320320
assert!(
321-
Db::verify_key_value_proof(&mut hasher, k, v, &proof, &root),
321+
Db::<F>::verify_key_value_proof(&mut hasher, k, v, &proof, &root),
322322
"key value proof failed to verify after crash recovery"
323323
);
324324
}
@@ -327,13 +327,13 @@ fn fuzz(input: FuzzInput) {
327327
let floor = *db.sync_boundary();
328328
let size = *db.bounds().await.end;
329329
for i in floor..size {
330-
let loc = Location::new(i);
330+
let loc = Location::<F>::new(i);
331331
let (proof, ops, chunks) = db
332332
.range_proof(&mut hasher, loc, NZU64!(4))
333333
.await
334334
.expect("range proof should not fail after recovery");
335335
assert!(
336-
Db::verify_range_proof(&mut hasher, &proof, loc, &ops, &chunks, &root),
336+
Db::<F>::verify_range_proof(&mut hasher, &proof, loc, &ops, &chunks, &root),
337337
"range proof failed to verify after crash recovery at loc {loc}"
338338
);
339339
}
@@ -362,5 +362,6 @@ fn fuzz(input: FuzzInput) {
362362
}
363363

364364
fuzz_target!(|input: FuzzInput| {
365-
fuzz(input);
365+
fuzz_family::<mmr::Family>(&input, "current-crash-mmr");
366+
fuzz_family::<mmb::Family>(&input, "current-crash-mmb");
366367
});

storage/fuzz/fuzz_targets/current_ordered_operations.rs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use commonware_cryptography::{sha256::Digest, Hasher, Sha256};
55
use commonware_runtime::{buffer::paged::CacheRef, deterministic, Runner};
66
use commonware_storage::{
77
journal::contiguous::fixed::Config as FConfig,
8-
mmr::{self, journaled::Config as MmrConfig, Location},
8+
merkle::{journaled::Config as MerkleConfig, mmb, mmr, Graftable, Location},
99
qmdb::current::{ordered::fixed::Db as CurrentDb, FixedConfig as Config},
1010
translator::TwoCap,
1111
};
@@ -20,7 +20,7 @@ type Key = FixedBytes<32>;
2020
type Value = FixedBytes<32>;
2121
type RawKey = [u8; 32];
2222
type RawValue = [u8; 32];
23-
type Db = CurrentDb<mmr::Family, deterministic::Context, Key, Value, Sha256, TwoCap, 32>;
23+
type Db<F> = CurrentDb<F, deterministic::Context, Key, Value, Sha256, TwoCap, 32>;
2424

2525
#[derive(Arbitrary, Debug, Clone)]
2626
enum CurrentOperation {
@@ -78,12 +78,12 @@ impl<'a> Arbitrary<'a> for FuzzInput {
7878

7979
const PAGE_SIZE: NonZeroU16 = NZU16!(91);
8080
const PAGE_CACHE_SIZE: usize = 8;
81-
const MMR_ITEMS_PER_BLOB: u64 = 11;
81+
const MERKLE_ITEMS_PER_BLOB: u64 = 11;
8282
const LOG_ITEMS_PER_BLOB: u64 = 7;
8383
const WRITE_BUFFER_SIZE: usize = 1024;
8484

85-
async fn commit_pending(
86-
db: &mut Db,
85+
async fn commit_pending<F: Graftable>(
86+
db: &mut Db<F>,
8787
pending_writes: &mut Vec<(Key, Option<Value>)>,
8888
committed_state: &mut HashMap<RawKey, RawValue>,
8989
pending_inserts: &mut HashMap<RawKey, RawValue>,
@@ -104,9 +104,11 @@ async fn commit_pending(
104104
committed_state.extend(pending_inserts.drain());
105105
}
106106

107-
fn fuzz(data: FuzzInput) {
107+
fn fuzz_family<F: Graftable>(data: &FuzzInput, suffix: &str) {
108108
let runner = deterministic::Runner::default();
109109

110+
let suffix = suffix.to_string();
111+
let operations = data.operations.clone();
110112
runner.start(|context| async move {
111113
let mut hasher = Sha256::new();
112114
let page_cache = CacheRef::from_pooler(
@@ -115,25 +117,25 @@ fn fuzz(data: FuzzInput) {
115117
NZUsize!(PAGE_CACHE_SIZE),
116118
);
117119
let cfg = Config {
118-
merkle_config: MmrConfig {
119-
journal_partition: "fuzz-current-mmr-journal".into(),
120-
metadata_partition: "fuzz-current-mmr-metadata".into(),
121-
items_per_blob: NZU64!(MMR_ITEMS_PER_BLOB),
120+
merkle_config: MerkleConfig {
121+
journal_partition: format!("fuzz-current-ord-{suffix}-merkle-journal"),
122+
metadata_partition: format!("fuzz-current-ord-{suffix}-merkle-metadata"),
123+
items_per_blob: NZU64!(MERKLE_ITEMS_PER_BLOB),
122124
write_buffer: NZUsize!(WRITE_BUFFER_SIZE),
123125
thread_pool: None,
124126
page_cache: page_cache.clone(),
125127
},
126128
journal_config: FConfig {
127-
partition: "fuzz-current-log-journal".into(),
129+
partition: format!("fuzz-current-ord-{suffix}-log-journal"),
128130
items_per_blob: NZU64!(LOG_ITEMS_PER_BLOB),
129131
write_buffer: NZUsize!(WRITE_BUFFER_SIZE),
130132
page_cache,
131133
},
132-
grafted_metadata_partition: "fuzz-current-grafted-mmr-metadata".into(),
134+
grafted_metadata_partition: format!("fuzz-current-ord-{suffix}-grafted-merkle-metadata"),
133135
translator: TwoCap,
134136
};
135137

136-
let mut db = Db::init(context.clone(), cfg)
138+
let mut db: Db<F> = Db::init(context.clone(), cfg)
137139
.await
138140
.expect("Failed to initialize Current database");
139141

@@ -144,9 +146,9 @@ fn fuzz(data: FuzzInput) {
144146
let mut pending_deletes: HashSet<RawKey> = HashSet::new();
145147
let mut all_keys = HashSet::new();
146148
let mut pending_writes: Vec<(Key, Option<Value>)> = Vec::new();
147-
let mut committed_op_count = Location::new(1);
149+
let mut committed_op_count = Location::<F>::new(1);
148150

149-
for op in &data.operations {
151+
for op in &operations {
150152
match op {
151153
CurrentOperation::Update { key, value } => {
152154
let k = Key::new(*key);
@@ -241,7 +243,7 @@ fn fuzz(data: FuzzInput) {
241243
let current_root = db.root();
242244

243245
let current_op_count = db.bounds().await.end;
244-
let start_loc = Location::new(start_loc % *current_op_count);
246+
let start_loc = Location::<F>::new(start_loc % *current_op_count);
245247

246248
let oldest_loc = db.sync_boundary();
247249
if start_loc >= oldest_loc {
@@ -251,7 +253,7 @@ fn fuzz(data: FuzzInput) {
251253
.expect("Range proof should not fail");
252254

253255
assert!(
254-
Db::verify_range_proof(
256+
Db::<F>::verify_range_proof(
255257
&mut hasher,
256258
&proof,
257259
start_loc,
@@ -276,7 +278,7 @@ fn fuzz(data: FuzzInput) {
276278
committed_op_count = db.bounds().await.end;
277279

278280
let current_op_count = db.bounds().await.end;
279-
let start_loc = Location::new(start_loc % current_op_count.as_u64());
281+
let start_loc = Location::<F>::new(start_loc % current_op_count.as_u64());
280282
let root = db.root();
281283

282284
if let Ok((range_proof, ops, chunks)) = db
@@ -287,7 +289,7 @@ fn fuzz(data: FuzzInput) {
287289
if range_proof.proof.digests != bad_digests {
288290
let mut bad_proof = range_proof.clone();
289291
bad_proof.proof.digests = bad_digests;
290-
assert!(!Db::verify_range_proof(
292+
assert!(!Db::<F>::verify_range_proof(
291293
&mut hasher,
292294
&bad_proof,
293295
start_loc,
@@ -299,7 +301,7 @@ fn fuzz(data: FuzzInput) {
299301

300302
// Try to verify the proof when providing bad input chunks.
301303
if &chunks != bad_chunks {
302-
assert!(!Db::verify_range_proof(
304+
assert!(!Db::<F>::verify_range_proof(
303305
&mut hasher,
304306
&range_proof,
305307
start_loc,
@@ -324,7 +326,7 @@ fn fuzz(data: FuzzInput) {
324326
match db.key_value_proof(&mut hasher, k.clone()).await {
325327
Ok(proof) => {
326328
let value = db.get(&k).await.expect("get should not fail").expect("key should exist");
327-
let verification_result = Db::verify_key_value_proof(
329+
let verification_result = Db::<F>::verify_key_value_proof(
328330
&mut hasher,
329331
k,
330332
value,
@@ -354,7 +356,7 @@ fn fuzz(data: FuzzInput) {
354356

355357
match db.exclusion_proof(&mut hasher, &k).await {
356358
Ok(proof) => {
357-
let verification_result = Db::verify_exclusion_proof(
359+
let verification_result = Db::<F>::verify_exclusion_proof(
358360
&mut hasher,
359361
&k,
360362
&proof,
@@ -404,5 +406,6 @@ fn fuzz(data: FuzzInput) {
404406
}
405407

406408
fuzz_target!(|input: FuzzInput| {
407-
fuzz(input);
409+
fuzz_family::<mmr::Family>(&input, "mmr");
410+
fuzz_family::<mmb::Family>(&input, "mmb");
408411
});

storage/fuzz/fuzz_targets/current_unordered_batch_root.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use commonware_cryptography::Sha256;
55
use commonware_runtime::{buffer::paged::CacheRef, deterministic, BufferPooler, Runner};
66
use commonware_storage::{
77
journal::contiguous::fixed::Config as FConfig,
8-
mmr::{self, journaled::Config as MmrConfig},
8+
merkle::{journaled::Config as MerkleConfig, mmb, mmr, Graftable},
99
qmdb::current::{unordered::fixed::Db as CurrentDb, FixedConfig as Config},
1010
translator::OneCap,
1111
};
@@ -15,7 +15,7 @@ use std::num::NonZeroU16;
1515

1616
type Key = FixedBytes<32>;
1717
type Value = FixedBytes<32>;
18-
type Db = CurrentDb<mmr::Family, deterministic::Context, Key, Value, Sha256, OneCap, 32>;
18+
type Db<F> = CurrentDb<F, deterministic::Context, Key, Value, Sha256, OneCap, 32>;
1919

2020
const PAGE_SIZE: NonZeroU16 = NZU16!(137);
2121
const COLLISION_GROUPS: u8 = 4;
@@ -76,8 +76,8 @@ impl<'a> Arbitrary<'a> for FuzzInput {
7676
fn test_config(name: &str, pooler: &impl BufferPooler) -> Config<OneCap> {
7777
let page_cache = CacheRef::from_pooler(pooler, PAGE_SIZE, NZUsize!(2));
7878
Config {
79-
merkle_config: MmrConfig {
80-
journal_partition: format!("{name}-mmr"),
79+
merkle_config: MerkleConfig {
80+
journal_partition: format!("{name}-merkle"),
8181
metadata_partition: format!("{name}-meta"),
8282
items_per_blob: NZU64!(17),
8383
write_buffer: NZUsize!(1024),
@@ -107,12 +107,13 @@ fn value_from_bytes(bytes: [u8; 32]) -> Value {
107107
Value::new(bytes)
108108
}
109109

110-
fn fuzz(input: FuzzInput) {
110+
fn fuzz_family<F: Graftable>(input: &FuzzInput, test_name: &str) {
111111
let runner = deterministic::Runner::default();
112112

113+
let test_name = test_name.to_string();
113114
runner.start(|context| async move {
114-
let cfg = test_config("fuzz-current-unordered-pending-vs-committed-root", &context);
115-
let mut db = Db::init(context.clone(), cfg)
115+
let cfg = test_config(&test_name, &context);
116+
let mut db: Db<F> = Db::init(context.clone(), cfg)
116117
.await
117118
.expect("init current unordered db");
118119

@@ -196,5 +197,6 @@ fn fuzz(input: FuzzInput) {
196197
}
197198

198199
fuzz_target!(|input: FuzzInput| {
199-
fuzz(input);
200+
fuzz_family::<mmr::Family>(&input, "fuzz-mmr-current-unordered-batch-root");
201+
fuzz_family::<mmb::Family>(&input, "fuzz-mmb-current-unordered-batch-root");
200202
});

0 commit comments

Comments
 (0)