diff --git a/Cargo.lock b/Cargo.lock index c7fd4be0b4..9b98a8c5bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1115,6 +1115,7 @@ dependencies = [ "bitvec", "libfuzzer-sys", "nomt", + "nomt-core", "tempfile", ] diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 805d37341d..3de32b578f 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -12,6 +12,7 @@ libfuzzer-sys = "0.4" arbitrary = { version = "1.3.1", features = ["derive"] } tempfile.workspace = true bitvec.workspace = true +nomt-core = { path = "../core" } [dependencies.nomt] path = "../nomt" diff --git a/fuzz/fuzz_targets/api_surface.rs b/fuzz/fuzz_targets/api_surface.rs index dd62963d65..9a958933ef 100644 --- a/fuzz/fuzz_targets/api_surface.rs +++ b/fuzz/fuzz_targets/api_surface.rs @@ -1,17 +1,20 @@ #![no_main] -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use arbitrary::Arbitrary; use libfuzzer_sys::fuzz_target; use nomt::{ - hasher::Blake3Hasher, trie::KeyPath, KeyReadWrite, Nomt, Options, SessionParams, Value, - WitnessMode, + hasher::{Blake3Hasher, ValueHasher}, + trie::{KeyPath, ValueHash}, + KeyReadWrite, Nomt, Options, SessionParams, Value, WitnessMode, }; +use nomt_core::update::build_trie; fuzz_target!(|run: Run| { let db = open_db(run.commit_concurrency); + let mut model = BTreeMap::::new(); for call in run.calls.calls { match call { @@ -33,7 +36,26 @@ fuzz_target!(|run: Run| { session.warm_up(key_path); } SessionCall::CommitAndProve { keys } => { + for (key_path, key_rw) in &keys { + match key_rw { + KeyReadWrite::Write(Some(value)) + | KeyReadWrite::ReadThenWrite(_, Some(value)) => { + model.insert(*key_path, Blake3Hasher::hash_value(value)); + } + KeyReadWrite::Write(None) + | KeyReadWrite::ReadThenWrite(_, None) => { + model.remove(key_path); + } + KeyReadWrite::Read(_) => {} + } + } let _ = session.finish(keys).unwrap().commit(&db).unwrap(); + let expected_root = build_trie::( + 0, + model.iter().map(|(k, v)| (*k, *v)), + |_| {}, + ); + assert_eq!(db.root().into_inner(), expected_root); break; } SessionCall::Drop => { @@ -81,10 +103,17 @@ impl Context { input: &mut arbitrary::Unstructured<'_>, ) -> arbitrary::Result> { if input.arbitrary()? { - let mut size = *input.choose(&[0, 4096])?; - if input.arbitrary()? { - size += 1; - } + // Inline leaf values, both sides of the overflow threshold (MAX_LEAF_VALUE_SIZE = 1332) + // and overflow values spanning several pages. + let size = match input.int_in_range(0..=6)? { + 0 => 0, + 1 => input.int_in_range(1..=32)?, + 2 => input.int_in_range(33..=1331)?, + 3 => input.int_in_range(1332..=1333)?, + 4 => input.int_in_range(1334..=4096)?, + 5 => input.int_in_range(4097..=8192)?, + _ => input.int_in_range(8193..=32 * 1024)?, + }; let mut buf = vec![0; size]; input.fill_buffer(&mut buf)?; Ok(Some(buf.into()))