Add version table fuzz

This commit is contained in:
Arthur Carcano
2026-04-27 16:59:13 +02:00
committed by Alex
parent 9a18259419
commit 6ddae5397c
3 changed files with 103 additions and 0 deletions
+6
View File
@@ -18,3 +18,9 @@ path = "../src/table"
path = "../src/model"
features = ["arbitrary"]
[[bin]]
name = "version_crdt"
path = "fuzz_targets/version_crdt.rs"
test = false
doc = false
bench = false
+94
View File
@@ -0,0 +1,94 @@
#![no_main]
use garage_model::s3::version_table::{Version, VersionBacklink, VersionBlock, VersionBlockKey};
use garage_table::crdt::Crdt;
use libfuzzer_sys::fuzz_target;
/// Build a Version from an arbitrary deleted flag and block list, using a fixed uuid/backlink
/// so that CRDT state can be compared across merge results.
/// Duplicate block keys are dropped before construction.
/// If deleted, blocks are cleared to ensure a valid initial CRDT state.
fn make_version(deleted: bool, mut blocks: Vec<(VersionBlockKey, VersionBlock)>) -> Version {
blocks.sort_by_key(|(k, _)| *k);
blocks.dedup_by_key(|(k, _)| *k);
let mut v = Version::new(
[0u8; 32].into(),
VersionBacklink::Object {
bucket_id: [0u8; 32].into(),
key: String::new(),
},
deleted,
);
for (key, block) in blocks {
v.blocks.put(key, block);
}
if v.deleted.get() {
v.blocks.clear();
}
v
}
fn crdt_state(v: &Version) -> (bool, &[(VersionBlockKey, VersionBlock)]) {
(v.deleted.get(), v.blocks.items())
}
fuzz_target!(|inputs: (
(bool, Vec<(VersionBlockKey, VersionBlock)>),
(bool, Vec<(VersionBlockKey, VersionBlock)>),
(bool, Vec<(VersionBlockKey, VersionBlock)>)
)| {
let ((d1, b1), (d2, b2), (d3, b3)) = inputs;
let a = make_version(d1, b1);
let b = make_version(d2, b2);
let c = make_version(d3, b3);
// Idempotency: merge(a, a) == a
{
let mut a2 = a.clone();
a2.merge(&a.clone());
assert_eq!(
crdt_state(&a2),
crdt_state(&a),
"merge is not idempotent: {a2:#?} != {a:#?}"
);
}
// Commutativity: crdt_state(merge(a, b)) == crdt_state(merge(b, a))
let ab = {
let mut t = a.clone();
t.merge(&b);
t
};
let ba = {
let mut t = b.clone();
t.merge(&a);
t
};
assert_eq!(
crdt_state(&ab),
crdt_state(&ba),
"merge is not commutative: {ab:#?} != {ba:#?}"
);
// Associativity: crdt_state(merge(merge(a, b), c)) == crdt_state(merge(a, merge(b, c)))
let ab_c = {
let mut t = ab.clone();
t.merge(&c);
t
};
let bc = {
let mut t = b.clone();
t.merge(&c);
t
};
let a_bc = {
let mut t = a.clone();
t.merge(&bc);
t
};
assert_eq!(
crdt_state(&ab_c),
crdt_state(&a_bc),
"merge is not associative: {ab_c:#?} != {a_bc:#?}"
);
});
+3
View File
@@ -41,6 +41,7 @@ mod v08 {
}
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct VersionBlockKey {
/// Number of the part
pub part_number: u64,
@@ -51,6 +52,7 @@ mod v08 {
/// Information about a single block
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct VersionBlock {
/// Blake2 sum of the block
pub hash: Hash,
@@ -91,6 +93,7 @@ pub(crate) mod v09 {
}
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum VersionBacklink {
Object {
/// Bucket in which the related object is stored