diff --git a/fuzz/fuzz_targets/mpu_crdt.rs b/fuzz/fuzz_targets/mpu_crdt.rs index 82d6b613..86eb9900 100644 --- a/fuzz/fuzz_targets/mpu_crdt.rs +++ b/fuzz/fuzz_targets/mpu_crdt.rs @@ -1,7 +1,7 @@ #![no_main] +use garage_fuzz::check_crdt_laws; use garage_model::s3::mpu_table::{MpuPart, MpuPartKey, MultipartUpload}; -use garage_table::crdt::Crdt; use libfuzzer_sys::fuzz_target; /// Build a MultipartUpload from an arbitrary deleted flag and parts list, using a fixed @@ -27,67 +27,11 @@ fn make_mpu(deleted: bool, parts: Vec<(MpuPartKey, MpuPart)>) -> MultipartUpload mpu } -fn crdt_state(mpu: &MultipartUpload) -> (bool, &[(MpuPartKey, MpuPart)]) { - (mpu.deleted.get(), mpu.parts.items()) -} - fuzz_target!(|inputs: ( (bool, Vec<(MpuPartKey, MpuPart)>), (bool, Vec<(MpuPartKey, MpuPart)>), (bool, Vec<(MpuPartKey, MpuPart)>) )| { let ((d1, p1), (d2, p2), (d3, p3)) = inputs; - let a = make_mpu(d1, p1); - let b = make_mpu(d2, p2); - let c = make_mpu(d3, p3); - - // 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:#?}" - ); + check_crdt_laws(make_mpu(d1, p1), make_mpu(d2, p2), make_mpu(d3, p3)); }); diff --git a/fuzz/fuzz_targets/version_crdt.rs b/fuzz/fuzz_targets/version_crdt.rs index 4be47666..cb0649bc 100644 --- a/fuzz/fuzz_targets/version_crdt.rs +++ b/fuzz/fuzz_targets/version_crdt.rs @@ -1,7 +1,7 @@ #![no_main] +use garage_fuzz::check_crdt_laws; 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 @@ -28,67 +28,15 @@ fn make_version(deleted: bool, mut blocks: Vec<(VersionBlockKey, VersionBlock)>) 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:#?}" + check_crdt_laws( + make_version(d1, b1), + make_version(d2, b2), + make_version(d3, b3), ); }); diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs new file mode 100644 index 00000000..288ccced --- /dev/null +++ b/fuzz/src/lib.rs @@ -0,0 +1,56 @@ +use garage_table::crdt::Crdt; +use std::fmt::Debug; + +pub fn check_crdt_laws(a: T, b: T, c: T) +where + T: Crdt + PartialEq + Clone + Debug, +{ + // Idempotency: merge(a, a) == a + { + let mut a2 = a.clone(); + a2.merge(&a); + assert_eq!(a2, a, "merge is not idempotent: {a2:#?} != {a:#?}"); + } + + // Commutativity: merge(a, b) == 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!(ab, ba, "merge is not commutative: {ab:#?} != {ba:#?}"); + + // LX's corrolary: merge(merge(a,b),b) = merge(a,b) + let ab_b = { + let mut t = ab.clone(); + t.merge(&b); + t + }; + assert_eq!(ab, ab_b); + + // Associativity: merge(merge(a, b), c) == merge(a, merge(b, c)) + let ab_c = { + let mut t = ab; + t.merge(&c); + t + }; + let bc = { + let mut t = b; + t.merge(&c); + t + }; + let a_bc = { + let mut t = a; + t.merge(&bc); + t + }; + assert_eq!( + ab_c, a_bc, + "merge is not associative: {ab_c:#?} != {a_bc:#?}" + ); +}