From da714125c1a142af07953947dd308d86e866e8f6 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Thu, 22 Oct 2026 11:14:08 +0200 Subject: [PATCH 1/3] fuzz: widen api_surface values and check root against a reference Values now cover inline sizes, both sides of the leaf overflow threshold and multi-page overflow values (up to 32 KiB) instead of only 0/1/4096/4097 bytes. After every commit the database root is compared with build_trie over a model of the committed state. Co-Authored-By: Claude Opus 5.5 (1M context) --- Cargo.lock | 1 + fuzz/Cargo.toml | 1 + fuzz/fuzz_targets/api_surface.rs | 45 +++++++++++++++++++++++++++----- 3 files changed, 40 insertions(+), 7 deletions(-) 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..4b2481e1a7 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, + 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,28 @@ 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, value.clone()); + } + 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, Blake3Hasher::hash_value(v))), + |_| {}, + ); + assert_eq!(db.root().into_inner(), expected_root); break; } SessionCall::Drop => { @@ -81,10 +105,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())) From 6cbab6f6fbdca033b4c53c9aa0f5f7f573b34b13 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 26 Oct 2026 11:03:32 +0100 Subject: [PATCH 2/3] cargo fmt --- fuzz/fuzz_targets/api_surface.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/fuzz/fuzz_targets/api_surface.rs b/fuzz/fuzz_targets/api_surface.rs index 4b2481e1a7..b76e519b38 100644 --- a/fuzz/fuzz_targets/api_surface.rs +++ b/fuzz/fuzz_targets/api_surface.rs @@ -52,9 +52,7 @@ fuzz_target!(|run: Run| { let _ = session.finish(keys).unwrap().commit(&db).unwrap(); let expected_root = build_trie::( 0, - model - .iter() - .map(|(k, v)| (*k, Blake3Hasher::hash_value(v))), + model.iter().map(|(k, v)| (*k, Blake3Hasher::hash_value(v))), |_| {}, ); assert_eq!(db.root().into_inner(), expected_root); From 642c489a5628a85f300d3ed3c4fa170d5cc6c8f5 Mon Sep 17 00:00:00 2001 From: Nikolai Golub Date: Mon, 26 Oct 2026 15:35:08 +0100 Subject: [PATCH 3/3] clean up --- fuzz/fuzz_targets/api_surface.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fuzz/fuzz_targets/api_surface.rs b/fuzz/fuzz_targets/api_surface.rs index b76e519b38..9a958933ef 100644 --- a/fuzz/fuzz_targets/api_surface.rs +++ b/fuzz/fuzz_targets/api_surface.rs @@ -7,14 +7,14 @@ use libfuzzer_sys::fuzz_target; use nomt::{ hasher::{Blake3Hasher, ValueHasher}, - trie::KeyPath, + 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(); + let mut model = BTreeMap::::new(); for call in run.calls.calls { match call { @@ -40,7 +40,7 @@ fuzz_target!(|run: Run| { match key_rw { KeyReadWrite::Write(Some(value)) | KeyReadWrite::ReadThenWrite(_, Some(value)) => { - model.insert(*key_path, value.clone()); + model.insert(*key_path, Blake3Hasher::hash_value(value)); } KeyReadWrite::Write(None) | KeyReadWrite::ReadThenWrite(_, None) => { @@ -52,7 +52,7 @@ fuzz_target!(|run: Run| { let _ = session.finish(keys).unwrap().commit(&db).unwrap(); let expected_root = build_trie::( 0, - model.iter().map(|(k, v)| (*k, Blake3Hasher::hash_value(v))), + model.iter().map(|(k, v)| (*k, *v)), |_| {}, ); assert_eq!(db.root().into_inner(), expected_root);