mirror of
https://github.com/deuxfleurs-org/garage.git
synced 2026-09-04 11:15:41 +00:00
Merge pull request 'First CRDT fuzz: MPU and version tables' (#1411) from krtab/garage:fuzz_crdts into main-v2
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/1411
This commit is contained in:
Generated
+43
@@ -145,6 +145,15 @@ version = "1.0.102"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
|
||||||
|
dependencies = [
|
||||||
|
"derive_arbitrary",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "1.9.1"
|
version = "1.9.1"
|
||||||
@@ -1116,6 +1125,17 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_arbitrary"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_more"
|
name = "derive_more"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -1523,6 +1543,17 @@ dependencies = [
|
|||||||
"utoipa",
|
"utoipa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "garage-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"garage_db",
|
||||||
|
"garage_model",
|
||||||
|
"garage_table",
|
||||||
|
"libfuzzer-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "garage_api_admin"
|
name = "garage_api_admin"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
@@ -1702,6 +1733,7 @@ dependencies = [
|
|||||||
name = "garage_model"
|
name = "garage_model"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
"argon2",
|
"argon2",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
@@ -1810,6 +1842,7 @@ dependencies = [
|
|||||||
name = "garage_util"
|
name = "garage_util"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
"arc-swap",
|
"arc-swap",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"blake2",
|
"blake2",
|
||||||
@@ -2934,6 +2967,16 @@ version = "0.2.185"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libfuzzer-sys"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
|
||||||
|
dependencies = [
|
||||||
|
"arbitrary",
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libsodium-sys"
|
name = "libsodium-sys"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ members = [
|
|||||||
"src/garage",
|
"src/garage",
|
||||||
"src/k2v-client",
|
"src/k2v-client",
|
||||||
"src/format-table",
|
"src/format-table",
|
||||||
|
"fuzz",
|
||||||
]
|
]
|
||||||
|
|
||||||
default-members = ["src/garage"]
|
default-members = ["src/garage"]
|
||||||
@@ -40,6 +41,7 @@ k2v-client = { version = "0.0.4", path = "src/k2v-client" }
|
|||||||
|
|
||||||
# External crates from crates.io
|
# External crates from crates.io
|
||||||
arc-swap = "1.8"
|
arc-swap = "1.8"
|
||||||
|
arbitrary = { version = "1.4.2"}
|
||||||
argon2 = "0.5"
|
argon2 = "0.5"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
backtrace = "0.3"
|
backtrace = "0.3"
|
||||||
@@ -59,6 +61,7 @@ hmac = "0.12"
|
|||||||
itertools = "0.14"
|
itertools = "0.14"
|
||||||
ipnet = "2.11"
|
ipnet = "2.11"
|
||||||
lazy_static = "1.5"
|
lazy_static = "1.5"
|
||||||
|
libfuzzer-sys = "0.4"
|
||||||
md-5 = "0.10"
|
md-5 = "0.10"
|
||||||
mktemp = "0.5"
|
mktemp = "0.5"
|
||||||
nix = { version = "0.31", default-features = false, features = ["fs"] }
|
nix = { version = "0.31", default-features = false, features = ["fs"] }
|
||||||
|
|||||||
@@ -95,6 +95,14 @@
|
|||||||
killall
|
killall
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# dev shell for fuzzing
|
||||||
|
fuzz = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
targets.toolchainNightly
|
||||||
|
cargo-fuzz
|
||||||
|
];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
target
|
||||||
|
corpus
|
||||||
|
artifacts
|
||||||
|
coverage
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "garage-fuzz"
|
||||||
|
version = "0.0.0"
|
||||||
|
publish = false
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[package.metadata]
|
||||||
|
cargo-fuzz = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
arbitrary = { workspace = true, features = ["derive"]}
|
||||||
|
libfuzzer-sys = { workspace = true }
|
||||||
|
|
||||||
|
garage_db.workspace = true
|
||||||
|
garage_table.workspace = true
|
||||||
|
garage_model = { workspace = true, default-features = false, features = ["arbitrary"] }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "version_crdt"
|
||||||
|
path = "fuzz_targets/version_crdt.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mpu_crdt"
|
||||||
|
path = "fuzz_targets/mpu_crdt.rs"
|
||||||
|
test = false
|
||||||
|
doc = false
|
||||||
|
bench = false
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Fuzzing
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Install cargo fuzz: `cargo install cargo-fuzz`
|
||||||
|
|
||||||
|
## Launch
|
||||||
|
|
||||||
|
Run `cargo fuzz run <fuzz_target>` where `<fuzz_target>` is the name (without extension) of one of the `.rs` files in the `fuzz_targets` directory.
|
||||||
|
|
||||||
|
If you launch the command outside of the fuzz directory, you need to force the nightly toolchain with `cargo +nightly`.
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#![no_main]
|
||||||
|
|
||||||
|
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
|
||||||
|
/// upload_id/bucket_id/key so that CRDT state can be compared across merge results.
|
||||||
|
/// `MpuPart.version` is fixed to a constant since it is identity data, not CRDT state:
|
||||||
|
/// two replicas of the same part (same MpuPartKey) always share the same version UUID.
|
||||||
|
/// If deleted, parts are cleared to ensure a valid initial CRDT state.
|
||||||
|
fn make_mpu(deleted: bool, parts: Vec<(MpuPartKey, MpuPart)>) -> MultipartUpload {
|
||||||
|
let mut mpu = MultipartUpload::new(
|
||||||
|
[0u8; 32].into(),
|
||||||
|
0,
|
||||||
|
[0u8; 32].into(),
|
||||||
|
String::new(),
|
||||||
|
deleted,
|
||||||
|
);
|
||||||
|
for (key, mut part) in parts {
|
||||||
|
part.version = [0u8; 32].into();
|
||||||
|
mpu.parts.put(key, part);
|
||||||
|
}
|
||||||
|
if mpu.deleted.get() {
|
||||||
|
mpu.parts.clear();
|
||||||
|
}
|
||||||
|
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:#?}"
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -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:#?}"
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
@@ -148,6 +148,14 @@ let
|
|||||||
in rec {
|
in rec {
|
||||||
toolchain = toolchainFn pkgs;
|
toolchain = toolchainFn pkgs;
|
||||||
|
|
||||||
|
toolchainNightly = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
|
||||||
|
targets = lib.optionals (target != null) [ rustTarget ];
|
||||||
|
extensions = [
|
||||||
|
"rust-src"
|
||||||
|
"rustfmt"
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
devShell = pkgs.mkShell {
|
devShell = pkgs.mkShell {
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
toolchain
|
toolchain
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ garage_block.workspace = true
|
|||||||
garage_util.workspace = true
|
garage_util.workspace = true
|
||||||
garage_net.workspace = true
|
garage_net.workspace = true
|
||||||
|
|
||||||
|
arbitrary = { optional = true, workspace = true }
|
||||||
argon2.workspace = true
|
argon2.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
blake2.workspace = true
|
blake2.workspace = true
|
||||||
@@ -46,6 +47,7 @@ k2v = ["garage_util/k2v"]
|
|||||||
lmdb = ["garage_db/lmdb"]
|
lmdb = ["garage_db/lmdb"]
|
||||||
sqlite = ["garage_db/sqlite"]
|
sqlite = ["garage_db/sqlite"]
|
||||||
fjall = ["garage_db/fjall"]
|
fjall = ["garage_db/fjall"]
|
||||||
|
arbitrary = ["dep:arbitrary","garage_util/arbitrary"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ mod v09 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub struct MpuPartKey {
|
pub struct MpuPartKey {
|
||||||
/// Number of the part
|
/// Number of the part
|
||||||
pub part_number: u64,
|
pub part_number: u64,
|
||||||
@@ -57,6 +58,7 @@ mod v09 {
|
|||||||
|
|
||||||
/// The version of an uploaded part
|
/// The version of an uploaded part
|
||||||
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub struct MpuPart {
|
pub struct MpuPart {
|
||||||
/// Links to a Version in `VersionTable`
|
/// Links to a Version in `VersionTable`
|
||||||
pub version: Uuid,
|
pub version: Uuid,
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ mod v010 {
|
|||||||
|
|
||||||
/// Checksum value for x-amz-checksum-algorithm
|
/// Checksum value for x-amz-checksum-algorithm
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub enum ChecksumValue {
|
pub enum ChecksumValue {
|
||||||
Crc32(#[serde(with = "serde_bytes")] [u8; 4]),
|
Crc32(#[serde(with = "serde_bytes")] [u8; 4]),
|
||||||
Crc32c(#[serde(with = "serde_bytes")] [u8; 4]),
|
Crc32c(#[serde(with = "serde_bytes")] [u8; 4]),
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ mod v08 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub struct VersionBlockKey {
|
pub struct VersionBlockKey {
|
||||||
/// Number of the part
|
/// Number of the part
|
||||||
pub part_number: u64,
|
pub part_number: u64,
|
||||||
@@ -51,6 +52,7 @@ mod v08 {
|
|||||||
|
|
||||||
/// Information about a single block
|
/// Information about a single block
|
||||||
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub struct VersionBlock {
|
pub struct VersionBlock {
|
||||||
/// Blake2 sum of the block
|
/// Blake2 sum of the block
|
||||||
pub hash: Hash,
|
pub hash: Hash,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ path = "lib.rs"
|
|||||||
garage_db.workspace = true
|
garage_db.workspace = true
|
||||||
garage_net.workspace = true
|
garage_net.workspace = true
|
||||||
|
|
||||||
|
arbitrary = { optional = true, workspace = true }
|
||||||
arc-swap.workspace = true
|
arc-swap.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
blake2.workspace = true
|
blake2.workspace = true
|
||||||
@@ -52,6 +53,7 @@ mktemp.workspace = true
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
k2v = []
|
k2v = []
|
||||||
|
arbitrary = ["dep:arbitrary"]
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use std::fmt;
|
|||||||
|
|
||||||
/// An array of 32 bytes
|
/// An array of 32 bytes
|
||||||
#[derive(Default, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
#[derive(Default, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Copy)]
|
||||||
|
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
|
||||||
pub struct FixedBytes32([u8; 32]);
|
pub struct FixedBytes32([u8; 32]);
|
||||||
|
|
||||||
impl From<[u8; 32]> for FixedBytes32 {
|
impl From<[u8; 32]> for FixedBytes32 {
|
||||||
|
|||||||
+1
-1
@@ -3,4 +3,4 @@ PN = "PN"
|
|||||||
substituters = "substituters"
|
substituters = "substituters"
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
extend-exclude = ["CHANGELOG.md", "**.js", "**.svg", "doc/talks/*"]
|
extend-exclude = ["CHANGELOG.md", "**.js", "**.svg", "doc/talks/*", "fuzz/*"]
|
||||||
|
|||||||
Reference in New Issue
Block a user