Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/chia-consensus/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,17 @@ path = "fuzz_targets/puzzle-fingerprint.rs"
test = false
doc = false
bench = false

[[bin]]
name = "run-generator-cache"
path = "fuzz_targets/run-generator-cache.rs"
test = false
doc = false
bench = false

[[bin]]
name = "conditions-cache"
path = "fuzz_targets/conditions-cache.rs"
test = false
doc = false
bench = false
105 changes: 105 additions & 0 deletions crates/chia-consensus/fuzz/fuzz_targets/conditions-cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#![no_main]
use chia_consensus::conditions::{
ConditionsCache, EmptyVisitor, ParseState, SpendBundleConditions, process_single_spend,
validate_conditions,
};
use chia_consensus::consensus_constants::TEST_CONSTANTS;
use chia_consensus::flags::ConsensusFlags;
use chia_consensus::owned_conditions::OwnedSpendBundleConditions;
use chia_consensus::validation_error::ErrorCode;
use clvm_fuzzing::ArbitraryClvmTree;
use libfuzzer_sys::fuzz_target;

const NUM_SPENDS: usize = 4;

fuzz_target!(|args: (ArbitraryClvmTree, [[u8; 32]; NUM_SPENDS])| {
let (conds, parent_ids) = args;
let mut a = conds.allocator;

let puzzle_hash_bytes = [0xab_u8; 32];
let puzzle_hash = a.new_atom(&puzzle_hash_bytes).expect("new_atom");
let amount = a.new_number(1u64.into()).expect("new_atom");

let parent_nodes: Vec<_> = parent_ids
.iter()
.map(|id| a.new_atom(id).expect("new_atom"))
.collect();

// Run A: without cache
let mut ret_a = SpendBundleConditions::default();
let mut state_a = ParseState::default();
let mut errors_a: Vec<Option<ErrorCode>> = Vec::new();

for &parent_id in &parent_nodes {
let mut cost_left = 110_000_000_u64;
let r = process_single_spend::<EmptyVisitor>(
&a,
&mut ret_a,
&mut state_a,
parent_id,
puzzle_hash,
amount,
conds.tree,
ConsensusFlags::empty(),
&mut cost_left,
0,
&TEST_CONSTANTS,
None,
);
errors_a.push(r.err().map(|e| e.error_code()));
}

// Run B: with cache
let mut ret_b = SpendBundleConditions::default();
let mut state_b = ParseState::default();
let mut cache = ConditionsCache::default();
let mut errors_b: Vec<Option<ErrorCode>> = Vec::new();

for &parent_id in &parent_nodes {
let mut cost_left = 110_000_000_u64;
let r = process_single_spend::<EmptyVisitor>(
&a,
&mut ret_b,
&mut state_b,
parent_id,
puzzle_hash,
amount,
conds.tree,
ConsensusFlags::empty(),
&mut cost_left,
0,
&TEST_CONSTANTS,
Some(&mut cache),
);
errors_b.push(r.err().map(|e| e.error_code()));
}

assert_eq!(errors_a, errors_b, "per-spend errors differ");

if errors_a.iter().all(Option::is_none) {
let validate_a = validate_conditions(&a, &ret_a, &state_a, ConsensusFlags::empty())
.err()
.map(|e| e.error_code());
let validate_b = validate_conditions(&a, &ret_b, &state_b, ConsensusFlags::empty())
.err()
.map(|e| e.error_code());
assert_eq!(
validate_a, validate_b,
"validate_conditions results differ between cached and uncached runs"
);

let mut owned_a = OwnedSpendBundleConditions::from(&a, ret_a);
let mut owned_b = OwnedSpendBundleConditions::from(&a, ret_b);
// HashSet iteration order is non-deterministic; normalize before comparing
for spend in &mut owned_a.spends {
spend.create_coin.sort();
}
for spend in &mut owned_b.spends {
spend.create_coin.sort();
}
assert_eq!(
owned_a, owned_b,
"conditions differ between cached and uncached runs"
);
}
});
1 change: 1 addition & 0 deletions crates/chia-consensus/fuzz/fuzz_targets/fast-forward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ fn run_puzzle(
&mut cost_left,
&TEST_CONSTANTS,
&mut visitor,
None,
)?;
ret.cost = max_cost - cost_left;
Ok(ret)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ fuzz_target!(|data: &[u8]| {
&mut max_cost,
&TEST_CONSTANTS,
&mut visitor,
None,
);
}
});
1 change: 1 addition & 0 deletions crates/chia-consensus/fuzz/fuzz_targets/process-spend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fuzz_target!(|args: (ArbitraryClvmTree, [u8; 32], [u8; 32], u64)| {
&mut cost_left,
0, // clvm_cost
&TEST_CONSTANTS,
None,
);
}
});
59 changes: 59 additions & 0 deletions crates/chia-consensus/fuzz/fuzz_targets/run-generator-cache.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#![no_main]
use chia_bls::Signature;
use chia_consensus::consensus_constants::TEST_CONSTANTS;
use chia_consensus::flags::ConsensusFlags;
use chia_consensus::owned_conditions::OwnedSpendBundleConditions;
use chia_consensus::run_block_generator::run_block_generator2;
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
let base_flags = ConsensusFlags::LIMIT_HEAP;

let r_no_cache = run_block_generator2::<&[u8], _>(
data,
[],
110_000_000,
base_flags,
&Signature::default(),
None,
&TEST_CONSTANTS,
);

let r_cache = run_block_generator2::<&[u8], _>(
data,
[],
110_000_000,
base_flags | ConsensusFlags::CONDITIONS_CACHE,
&Signature::default(),
None,
&TEST_CONSTANTS,
);

match (r_no_cache, r_cache) {
(Err(e1), Err(e2)) => {
assert_eq!(
e1.error_code(),
e2.error_code(),
"error codes differ: no_cache={e1:?} cache={e2:?}"
);
}
(Ok((a1, conds1)), Ok((a2, conds2))) => {
let mut owned1 = OwnedSpendBundleConditions::from(&a1, conds1);
let mut owned2 = OwnedSpendBundleConditions::from(&a2, conds2);
// HashSet iteration order is non-deterministic; normalize before comparing
for spend in &mut owned1.spends {
spend.create_coin.sort();
}
for spend in &mut owned2.spends {
spend.create_coin.sort();
}
assert_eq!(
owned1, owned2,
"conditions differ between cached and uncached runs"
);
}
(r1, r2) => {
panic!("one run succeeded and the other failed:\n no_cache: {r1:?}\n cache: {r2:?}");
}
}
});
Loading
Loading