From 58205bd416b3cd53dd6fc08ff7e8de444e236422 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 2 Sep 2023 12:37:36 +0200 Subject: [PATCH 1/5] make run_block_generator2() backwards compatible with run_block_generator(), by simulating the cost of running the CLVM implementation of the generator ROM --- clvm-utils/src/tree_hash.rs | 217 ++++++++++++++++++++++++++++++ src/gen/run_block_generator.rs | 159 ++++++++++++++++++++-- tests/test_run_block_generator.py | 2 +- 3 files changed, 368 insertions(+), 10 deletions(-) diff --git a/clvm-utils/src/tree_hash.rs b/clvm-utils/src/tree_hash.rs index 5e8abdeb9..05f9538b4 100644 --- a/clvm-utils/src/tree_hash.rs +++ b/clvm-utils/src/tree_hash.rs @@ -1,4 +1,5 @@ use clvmr::allocator::{Allocator, NodePtr, SExp}; +use clvmr::cost::Cost; use clvmr::sha2::{Digest, Sha256}; enum TreeOp { @@ -6,6 +7,222 @@ enum TreeOp { Cons, } +/* + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 44 + traverse: {traverse_cost} + cons_cost: 50 + traverse: 48 + cons_cost: 50 + traverse: {traverse_cost2} + apply_cost: 90 + op_cost: 1 + traverse: 44 + op_cost: 1 + quote_cost: 20 + quote_cost: 20 + op_cost: 1 + traverse: 52 + listp_cost: 19 + if_cost: 33 + apply_cost: 90 + op_cost: 1 +*/ +const HASH_TRAVERSE_COST: Cost = 567; + +fn subtract_cost(cost_left: &mut Cost, subtract: Cost) -> Option<()> { + if subtract > *cost_left { + None + } else { + *cost_left -= subtract; + Some(()) + } +} + +// TODO: simulate object allocation counter, to correctly implement +// LIMIT_OBJECTS restriction + +// TODO: simulate CLVM stack depth to correctly implement the LIMIT_STACK +// restriction + +// The traverse cost seem to depend on the environment of the caller. The base +// cost is 40 and then 4 for every bit we parse. +// When calling sha256tree directly from the `mod`, it's 48. +// When calling it from the ROM generator, it's 60 +pub fn tree_hash_with_cost( + a: &Allocator, + node: NodePtr, + traverse_cost2: Cost, + cost_left: &mut Cost, +) -> Option<[u8; 32]> { + let mut hashes: Vec<[u8; 32]> = vec![]; + let mut ops = vec![TreeOp::SExp(node)]; + + let mut traverse_cost = 60; + + const SHA256_BASE_COST: Cost = 87; + const SHA256_COST_PER_ARG: Cost = 134; + const SHA256_COST_PER_BYTE: Cost = 2; + const MALLOC_COST_PER_BYTE: Cost = 10; + + while let Some(op) = ops.pop() { + match op { + TreeOp::SExp(node) => { + subtract_cost( + cost_left, + HASH_TRAVERSE_COST + traverse_cost + traverse_cost2, + )?; + traverse_cost = 56; + + match a.sexp(node) { + SExp::Atom => { + // traverse: 52 + // quote_cost: 20 + subtract_cost(cost_left, 72)?; + let mut sha256 = Sha256::new(); + sha256.update([1_u8]); + let buf = a.atom(node); + sha256.update(buf); + hashes.push(sha256.finalize().into()); + let mut sha_cost = SHA256_BASE_COST; + sha_cost += 2 * SHA256_COST_PER_ARG; + sha_cost += (1 + buf.len() as Cost) * SHA256_COST_PER_BYTE; + // sha256_cost: ... + // malloc_cost: 320 + subtract_cost(cost_left, sha_cost + 32 * MALLOC_COST_PER_BYTE)?; + } + SExp::Pair(left, right) => { + ops.push(TreeOp::Cons); + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + } + } + TreeOp::Cons => { + // quote_cost: 20 + subtract_cost(cost_left, 20)?; + + let mut sha256 = Sha256::new(); + sha256.update([2_u8]); + sha256.update(hashes.pop().unwrap()); + sha256.update(hashes.pop().unwrap()); + hashes.push(sha256.finalize().into()); + + const SHA_COST: Cost = SHA256_BASE_COST; + // sha256_cost: 619 + // malloc_cost: 320 + subtract_cost( + cost_left, + 3 * SHA256_COST_PER_ARG + + (1 + 32 + 32) * SHA256_COST_PER_BYTE + + SHA_COST + + 32 * MALLOC_COST_PER_BYTE, + )?; + } + } + } + + assert!(hashes.len() == 1); + Some(hashes[0]) +} + +#[cfg(test)] +pub fn cmp_hash(a: &mut Allocator, root: NodePtr) { + use clvmr::chia_dialect::ChiaDialect; + use clvmr::reduction::Reduction; + use clvmr::run_program::run_program; + use clvmr::serde::node_from_bytes; + + /* + This is the compiled version of: + (mod (TREE) + (defun sha256tree (TREE) + (if (l TREE) + (sha256 2 (sha256tree (f TREE)) (sha256tree (r TREE))) + (sha256 1 TREE) + ) + ) + (sha256tree TREE) + ) + + CLVM: + + (a (q 2 2 (c 2 (c 5 ()))) + (c (q 2 (i (l 5) + (q #sha256 (q . 2) + (a 2 (c 2 (c 9 ()))) + (a 2 (c 2 (c 13 ()))) + ) + (q #sha256 (q . 1) 5) + ) 1) 1) + ) + */ + let tree_hash_clvm: Vec = hex::decode( + "\ +ff02ffff01ff02ff02ffff04ff02ffff04ff05ff80808080ffff04ffff01ff02\ +ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff02ffff04ff02ffff04ff\ +09ff80808080ffff02ff02ffff04ff02ffff04ff0dff8080808080ffff01ff0b\ +ffff0101ff058080ff0180ff018080", + ) + .expect("hex::decode"); + + let program = node_from_bytes(a, &tree_hash_clvm).expect("node_from_bytes"); + + let argument = a.new_pair(root, a.null()).unwrap(); + + let dialect = ChiaDialect::new(0); + let Reduction(cost1, ret1) = + run_program(a, &dialect, program, argument, 11000000000).expect("run_program"); + + // 226 is the overhead of starting the program and calling into the mod + // When running it directly in the mod scope, the first iteration + // environment lookups (path traversal) needs 2 fewer bits, and so is 8 cost + // cheaper. To make the cost match, we therefore deduct 8 + let mut expect_cost = cost1 - 226 + 8; + let ret2 = tree_hash_with_cost(a, root, 48, &mut expect_cost).unwrap(); + + assert_eq!(a.atom(ret1), ret2); + println!("clvm cost: {cost1} rust cost: {}", cost1 - expect_cost); + assert_eq!(expect_cost, 0); +} + +#[test] +fn test_tree_hash_cost() { + use clvmr::Allocator; + + let mut a = Allocator::new(); + + let atom3 = a.new_atom(&[1, 2, 3]).unwrap(); + let atom2 = a.new_atom(&[4, 5]).unwrap(); + let atom1 = a.new_atom(&[6]).unwrap(); + let atom_c = a.new_atom(&[0xcc, 0xcc]).unwrap(); + let atom_a = a.new_atom(&[0xaa, 0xaa]).unwrap(); + + cmp_hash(&mut a, atom3); + + let root1 = a.new_pair(atom1, atom1).unwrap(); + let root2 = a.new_pair(atom1, atom1).unwrap(); + let root = a.new_pair(root1, root2).unwrap(); + cmp_hash(&mut a, root); + + let root = a.new_pair(atom1, atom2).unwrap(); + cmp_hash(&mut a, root); + + let root = a.new_pair(atom2, atom3).unwrap(); + cmp_hash(&mut a, root); + + let root = a.new_pair(atom1, atom3).unwrap(); + cmp_hash(&mut a, root); + + let root = a.new_pair(atom2, root).unwrap(); + cmp_hash(&mut a, root); + + let root = a.new_pair(atom_c, atom_a).unwrap(); + cmp_hash(&mut a, root); +} + pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); sha256.update([1]); diff --git a/src/gen/run_block_generator.rs b/src/gen/run_block_generator.rs index b65e8891b..d3aab08fe 100644 --- a/src/gen/run_block_generator.rs +++ b/src/gen/run_block_generator.rs @@ -2,10 +2,10 @@ use crate::gen::conditions::{ parse_spends, process_single_spend, validate_conditions, ParseState, SpendBundleConditions, }; use crate::gen::flags::ALLOW_BACKREFS; -use crate::gen::validation_error::{first, ErrorCode, ValidationErr}; +use crate::gen::validation_error::{ErrorCode, ValidationErr}; use crate::generator_rom::{CLVM_DESERIALIZER, COST_PER_BYTE, GENERATOR_ROM}; -use clvm_utils::tree_hash; -use clvmr::allocator::{Allocator, NodePtr}; +use clvm_utils::tree_hash_with_cost; +use clvmr::allocator::{Allocator, NodePtr, SExp}; use clvmr::chia_dialect::ChiaDialect; use clvmr::cost::Cost; use clvmr::reduction::Reduction; @@ -102,12 +102,144 @@ fn extract_n( Ok(ret) } +/* + This is the start-up cost of the generator ROM: + op_cost: 1 + op_cost: 1 + traverse: 44 + quote_cost: 20 + cons_cost: 50 + quote_cost: 20 + apply_cost: 90 + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 44 + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 44 + traverse: 60 + cons_cost: 50 + traverse: 56 + cons_cost: 50 + traverse: 52 + apply_cost: 90 +*/ +const ROM_STARTUP_COST: Cost = + 1 + 1 + 44 + 20 + 50 + 20 + 90 + 1 + 1 + 1 + 44 + 1 + 1 + 1 + 44 + 60 + 50 + 56 + 50 + 52 + 90; + +/* + cons_cost: 50 + traverse: 48 + cons_cost: 50 + traverse: 56 + apply_cost: 90 + op_cost: 1 + traverse: 56 + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 44 + traverse: 56 + cons_cost: 50 + traverse: 48 + cons_cost: 50 + traverse: 60 + apply_cost: 90 +*/ +const ROM_GENERATOR_TAIL: Cost = + 50 + 48 + 50 + 56 + 90 + 1 + 56 + 1 + 1 + 1 + 44 + 56 + 50 + 48 + 50 + 60 + 90; + +/* + op_cost: 1 + traverse: 44 + op_cost: 1 + traverse: 44 + quote_cost: 20 + traverse: 52 + if_cost: 33 + apply_cost: 90 + traverse: 44 +*/ +const ROM_SETUP_LOOP_COST: Cost = 1 + 44 + 1 + 44 + 20 + 52 + 33 + 90 + 44; + +/* + op_cost: 1 + traverse: 44 + op_cost: 1 + traverse: 44 + quote_cost: 20 + traverse: 52 + if_cost: 33 + apply_cost: 90 + op_cost: 1 + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 44 + traverse: 56 + cons_cost: 50 + traverse: 48 + cons_cost: 50 + traverse: 60 + apply_cost: 90 + + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 44 + traverse: 56 + cons_cost: 50 + traverse: 48 + cons_cost: 50 + traverse: 56 + apply_cost: 90 + op_cost: 1 + op_cost: 1 + op_cost: 1 + op_cost: 1 + traverse: 68 + op_cost: 1 + traverse: 68 + traverse: 60 + apply_cost: 90 +*/ +#[rustfmt::skip] +const ROM_ITERATE_COST: Cost = + 1 + 44 + 1 + 44 + 20 + 52 + 33 + 90 + 1 + 1 + 1 + 1 + 44 + 56 + 50 + 48 + 50 + 60 + 90 + + 1 + 1 + 1 + 44 + 56 + 50 + 48 + 50 + 56 + 90 + 1 + 1 + 1 + 1 + 68 + 1 + 68 + 60 + 90; + +/* + cons_cost: 50 + traverse: 64 + cons_cost: 50 +*/ +const ROM_TREE_HASH_SETUP: Cost = 50 + 64 + 50; + +/* + cons_cost: 50 + traverse: 56 + cons_cost: 50 + cons_cost: 50 +*/ +const ROM_TREE_HASH_TAIL: Cost = 50 + 56 + 50 + 50; + +/* + cons_cost: 50 +*/ +const ROM_TRAVERSE_TAIL: Cost = 50; + +// helper functions that fail with ValidationErr +fn first(a: &Allocator, n: NodePtr) -> Result { + match a.sexp(n) { + SExp::Pair(left, _) => Ok(left), + _ => Err(ValidationErr(n, ErrorCode::GeneratorRuntimeError)), + } +} + // This has the same behavior as run_block_generator() but implements the // generator ROM in rust instead of using the CLVM implementation. -// it is not backwards compatible in the CLVM cost computation (in this version -// you only pay cost for the generator, the puzzles and the conditions). -// it also does not apply the stack depth or object allocation limits the same, -// as each puzzle run in its own environment. pub fn run_block_generator2>( a: &mut Allocator, program: &[u8], @@ -119,6 +251,7 @@ pub fn run_block_generator2>( let mut cost_left = max_cost; subtract_cost(a, &mut cost_left, byte_cost)?; + subtract_cost(a, &mut cost_left, ROM_STARTUP_COST)?; let clvm_deserializer = node_from_bytes(a, &CLVM_DESERIALIZER)?; let program = if (flags & ALLOW_BACKREFS) != 0 { @@ -146,6 +279,7 @@ pub fn run_block_generator2>( subtract_cost(a, &mut cost_left, clvm_cost)?; all_spends = first(a, all_spends)?; + subtract_cost(a, &mut cost_left, ROM_GENERATOR_TAIL)?; // at this point all_spends is a list of: // (parent-coin-id puzzle-reveal amount solution . extra) @@ -153,19 +287,25 @@ pub fn run_block_generator2>( let mut ret = SpendBundleConditions::default(); let mut state = ParseState::default(); + subtract_cost(a, &mut cost_left, ROM_SETUP_LOOP_COST)?; while let Some((spend, rest)) = a.next(all_spends) { all_spends = rest; + subtract_cost(a, &mut cost_left, ROM_ITERATE_COST)?; + // process the spend let [parent_id, puzzle, amount, solution, _spend_level_extra] = - extract_n::<5>(a, spend, ErrorCode::InvalidCondition)?; + extract_n::<5>(a, spend, ErrorCode::GeneratorRuntimeError)?; let Reduction(clvm_cost, conditions) = run_program(a, &dialect, puzzle, solution, cost_left)?; subtract_cost(a, &mut cost_left, clvm_cost)?; + subtract_cost(a, &mut cost_left, ROM_TREE_HASH_SETUP)?; - let buf = tree_hash(a, puzzle); + let buf = tree_hash_with_cost(a, puzzle, 60, &mut cost_left) + .ok_or(ValidationErr(a.null(), ErrorCode::CostExceeded))?; + subtract_cost(a, &mut cost_left, ROM_TREE_HASH_TAIL)?; let puzzle_hash = a.new_atom(&buf)?; process_single_spend( @@ -184,6 +324,7 @@ pub fn run_block_generator2>( return Err(ValidationErr(all_spends, ErrorCode::GeneratorRuntimeError)); } + subtract_cost(a, &mut cost_left, ROM_TRAVERSE_TAIL)?; validate_conditions(a, &ret, state, a.null(), flags)?; ret.cost = max_cost - cost_left; diff --git a/tests/test_run_block_generator.py b/tests/test_run_block_generator.py index f5ee4abac..79458373d 100644 --- a/tests/test_run_block_generator.py +++ b/tests/test_run_block_generator.py @@ -9,7 +9,7 @@ def test_run_block_generator_cost() -> None: original_consensus_cost = 635805370 # once the hard fork activates, the cost will be lower, because you no # longer pay the cost of the generator ROM - hard_fork_consensus_cost = 596498808 + hard_fork_consensus_cost = original_consensus_cost generator = bytes.fromhex(open("generator-tests/block-834768.txt", "r").read().split("\n")[0]) err, conds = run_block_generator(generator, [], original_consensus_cost, 0) From 0ace3674c91e915eb830d50fb7e38bf9da4edef6 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 2 Sep 2023 12:42:07 +0200 Subject: [PATCH 2/5] update test for block_generator2() to check the new, stricter behavior --- src/gen/test_generators.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/gen/test_generators.rs b/src/gen/test_generators.rs index 15eed4515..8c2bd3c6e 100644 --- a/src/gen/test_generators.rs +++ b/src/gen/test_generators.rs @@ -217,18 +217,10 @@ fn run_generator(#[case] name: &str) { Err(code) => (0, format!("FAILED: {}\n", u32::from(code.1))), }; - let output_hard_fork = + let output2 = match run_block_generator2(&mut a, &generator, &block_refs, 11000000000, *flags) { - Ok(mut conditions) => { - // in the hard fork, the cost of running the genrator + - // puzzles should never be higher than before the hard-fork - // but it's likely less. - assert!(conditions.cost <= expected_cost); - assert!(conditions.cost > 0); - // update the cost we print here, just to be compatible with - // the test cases we have. We've already ensured the cost is - // lower - conditions.cost = expected_cost; + Ok(conditions) => { + assert_eq!(conditions.cost, expected_cost); print_conditions(&a, &conditions) } Err(code) => { @@ -236,8 +228,8 @@ fn run_generator(#[case] name: &str) { } }; - if output != output_hard_fork { - print_diff(&output, &output_hard_fork); + if output != output2 { + print_diff(&output, &output2); panic!("run_block_generator2 produced a different result!"); } From aceb6ae8bce7efe2c58c47845d74c50a8c770099 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 2 Sep 2023 12:43:17 +0200 Subject: [PATCH 3/5] update test-block-generators to check the new, stricter, behavior of run_block_generator2() --- chia-tools/src/bin/test-block-generators.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/chia-tools/src/bin/test-block-generators.rs b/chia-tools/src/bin/test-block-generators.rs index 3b33f0005..6169d8a99 100644 --- a/chia-tools/src/bin/test-block-generators.rs +++ b/chia-tools/src/bin/test-block-generators.rs @@ -244,17 +244,7 @@ fn main() { let mut conditions = block_runner(&mut a, generator, &block_refs, ti.cost, flags) .expect("failed to run block generator"); - if args.rust_generator || args.test_backrefs { - assert!(conditions.cost <= ti.cost); - assert!(conditions.cost > 0); - - // in order for the comparison below the hold, we need to - // patch up the cost of the rust generator to look like the - // baseline - conditions.cost = ti.cost; - } else { - assert_eq!(conditions.cost, ti.cost); - } + assert_eq!(conditions.cost, ti.cost); if args.validate { let mut baseline = From 3167f8c22ec3e350a8bf1c7f133c8a30e1243a30 Mon Sep 17 00:00:00 2001 From: arvidn Date: Sat, 2 Sep 2023 12:45:44 +0200 Subject: [PATCH 4/5] update fuzzer for run_block_generator to check the new, stricter, behavior --- fuzz/fuzz_targets/run-generator.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/fuzz/fuzz_targets/run-generator.rs b/fuzz/fuzz_targets/run-generator.rs index da1f70c78..676151938 100644 --- a/fuzz/fuzz_targets/run-generator.rs +++ b/fuzz/fuzz_targets/run-generator.rs @@ -2,7 +2,6 @@ use chia::allocator::make_allocator; use chia::gen::flags::{ALLOW_BACKREFS, LIMIT_OBJECTS}; use chia::gen::run_block_generator::{run_block_generator, run_block_generator2}; -use chia::gen::validation_error::{ErrorCode, ValidationErr}; use clvmr::chia_dialect::LIMIT_HEAP; use libfuzzer_sys::fuzz_target; @@ -16,18 +15,13 @@ fuzz_target!(|data: &[u8]| { drop(a2); match (r1, r2) { - (Err(ValidationErr(_, ErrorCode::CostExceeded)), Ok(_)) => { - // Since run_block_generator2 cost less, it's not a problem if the - // original generator runs our of cost while the rust implementation - // succeeds. This is part of its features. - } (Err(_), Err(_)) => { // The specific error may not match, because // run_block_generator2() parses conditions after each spend // instead of after running all spends } (Ok(a), Ok(b)) => { - assert!(a.cost >= b.cost); + assert_eq!(a.cost, b.cost); assert_eq!(a.reserve_fee, b.reserve_fee); assert_eq!(a.removal_amount, b.removal_amount); assert_eq!(a.addition_amount, b.addition_amount); From d0da7220c94a1c818b2ad37b4d5e6d6521a7b3e9 Mon Sep 17 00:00:00 2001 From: arvidn Date: Thu, 31 Aug 2023 10:55:12 +0200 Subject: [PATCH 5/5] add fuzzer for tree_hash_with_cost --- Cargo.lock | 1 + clvm-utils/Cargo.toml | 2 -- clvm-utils/fuzz/Cargo.toml | 7 +++++++ clvm-utils/fuzz/fuzz_targets/tree-hash-with-cost.rs | 13 +++++++++++++ clvm-utils/src/tree_hash.rs | 2 +- 5 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 clvm-utils/fuzz/fuzz_targets/tree-hash-with-cost.rs diff --git a/Cargo.lock b/Cargo.lock index a262166e6..6e79f3aa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -475,6 +475,7 @@ dependencies = [ "clvm-traits", "clvm-utils", "clvmr", + "hex", "libfuzzer-sys", ] diff --git a/clvm-utils/Cargo.toml b/clvm-utils/Cargo.toml index 978a0d447..3f945b7e9 100644 --- a/clvm-utils/Cargo.toml +++ b/clvm-utils/Cargo.toml @@ -11,6 +11,4 @@ repository = "https://github.com/Chia-Network/chia_rs/clvm-utils" [dependencies] clvmr = "0.3.0" clvm-traits = { version = "0.1.0", path = "../clvm-traits" } - -[dev-dependencies] hex = "0.4.3" diff --git a/clvm-utils/fuzz/Cargo.toml b/clvm-utils/fuzz/Cargo.toml index e0e318a5e..ca216b967 100644 --- a/clvm-utils/fuzz/Cargo.toml +++ b/clvm-utils/fuzz/Cargo.toml @@ -15,6 +15,7 @@ chia-fuzz = { path = "../../fuzz" } clvm-utils = { path = ".." } clvm-traits = { path = "../../clvm-traits" } chia = { path = "../.." } +hex = "0.4.3" [[bin]] name = "tree-hash" @@ -29,3 +30,9 @@ path = "fuzz_targets/curry.rs" test = false doc = false bench = false + +[[bin]] +name = "tree-hash-with-cost" +path = "fuzz_targets/tree-hash-with-cost.rs" +test = false +doc = false diff --git a/clvm-utils/fuzz/fuzz_targets/tree-hash-with-cost.rs b/clvm-utils/fuzz/fuzz_targets/tree-hash-with-cost.rs new file mode 100644 index 000000000..e96870158 --- /dev/null +++ b/clvm-utils/fuzz/fuzz_targets/tree-hash-with-cost.rs @@ -0,0 +1,13 @@ +#![no_main] +#[cfg(fuzzing)] +use clvm_utils::cmp_hash; +use clvmr::Allocator; +use fuzzing_utils::{make_tree, BitCursor}; +use libfuzzer_sys::fuzz_target; + +fuzz_target!(|data: &[u8]| { + let mut a = Allocator::new(); + let input = make_tree(&mut a, &mut BitCursor::new(data), false); + #[cfg(fuzzing)] + cmp_hash(&mut a, input); +}); diff --git a/clvm-utils/src/tree_hash.rs b/clvm-utils/src/tree_hash.rs index 05f9538b4..4fe9fc6cc 100644 --- a/clvm-utils/src/tree_hash.rs +++ b/clvm-utils/src/tree_hash.rs @@ -128,7 +128,7 @@ pub fn tree_hash_with_cost( Some(hashes[0]) } -#[cfg(test)] +#[cfg(any(test, fuzzing))] pub fn cmp_hash(a: &mut Allocator, root: NodePtr) { use clvmr::chia_dialect::ChiaDialect; use clvmr::reduction::Reduction;