From c4b88fed892a007961d26cac0f08489a083e72df Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 25 Sep 2025 14:00:15 +0100 Subject: [PATCH 001/110] initial commit --- src/chia_dialect.rs | 7 +++ src/f_table.rs | 2 + src/lib.rs | 1 + src/sha_tree_op.rs | 111 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 src/sha_tree_op.rs diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index 63aaa2dba..5f822f57c 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -16,6 +16,7 @@ use crate::more_ops::{ }; use crate::reduction::Response; use crate::secp_ops::{op_secp256k1_verify, op_secp256r1_verify}; +use crate::sha_tree_op::op_sha256_tree; // unknown operators are disallowed // (otherwise they are no-ops with well defined cost) @@ -29,6 +30,8 @@ pub const LIMIT_HEAP: u32 = 0x0004; // This is a hard-fork and should only be enabled when it activates pub const ENABLE_KECCAK_OPS_OUTSIDE_GUARD: u32 = 0x0100; +pub const ENABLE_SHA256_TREE: u32 = 0x0200; + // The default mode when running grnerators in mempool-mode (i.e. the stricter // mode) pub const MEMPOOL_MODE: u32 = NO_UNKNOWN_OPS | LIMIT_HEAP; @@ -167,6 +170,10 @@ impl Dialect for ChiaDialect { _ => { return unknown_operator(allocator, o, argument_list, flags, max_cost); } + 63 if (flags & ENABLE_SHA256_TREE) != 0 => op_sha256_tree, + _ => { + return unknown_operator(allocator, o, argument_list, flags, max_cost); + } }; f(allocator, argument_list, max_cost) } diff --git a/src/f_table.rs b/src/f_table.rs index 2d584aa39..3b82a21ec 100644 --- a/src/f_table.rs +++ b/src/f_table.rs @@ -6,6 +6,7 @@ use crate::bls_ops::{ op_bls_g2_negate, op_bls_g2_subtract, op_bls_map_to_g1, op_bls_map_to_g2, op_bls_pairing_identity, op_bls_verify, }; +use crate::sha_tree_op::op_sha256_tree; use crate::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_rest}; use crate::cost::Cost; use crate::more_ops::{ @@ -66,6 +67,7 @@ pub fn opcode_by_name(name: &str) -> Option { (op_bls_verify, "op_bls_verify"), (op_secp256k1_verify, "op_secp256k1_verify"), (op_secp256r1_verify, "op_secp256r1_verify"), + (op_sha256_tree, "op_sha256_tree") ]; let name: &[u8] = name.as_ref(); for (f, op) in opcode_lookup.iter() { diff --git a/src/lib.rs b/src/lib.rs index 10d6fdbb3..3d8f21b33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub mod run_program; pub mod runtime_dialect; pub mod secp_ops; pub mod serde; +pub mod sha_tree_op; pub mod traverse_path; pub use allocator::{Allocator, Atom, NodePtr, ObjectType, SExp}; diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs new file mode 100644 index 000000000..4e9ed9e32 --- /dev/null +++ b/src/sha_tree_op.rs @@ -0,0 +1,111 @@ +use crate::allocator::{Allocator, Atom, NodePtr}; +use crate::cost::{check_cost, Cost}; +use crate::error::EvalErr; +use crate::op_utils::{ + atom, first, get_args, get_varargs, int_atom, mod_group_order, new_atom_and_cost, nilp, rest, + MALLOC_COST_PER_BYTE, +}; +use crate::reduction::{Reduction, Response}; +use chia_bls::{ + aggregate_pairing, aggregate_verify, hash_to_g1_with_dst, hash_to_g2_with_dst, G1Element, + G2Element, PublicKey, +}; + +const SHA256TREE_BASE_COST: Cost = 50; +const SHA256TREE_COST_PER_CALL: Cost = 160; +const SHA256TREE_COST_PER_BYTE: Cost = 2; + +pub fn tree_hash_cached_costed( + a: &Allocator, + node: NodePtr, + cache: &mut TreeCache, + cost_left: &mut u64, + cost_per_call: Cost, + cost_per_byte: Cost, +) -> Result { + cache.visit_tree(a, node); + + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; + let mut cost = SHA256TREE_BASE_COST; + + // we will call check_cost throughout the runtime so we can exit immediately if we go over cost + while let Some(op) = ops.pop() { + cost = cost + SHA256TREE_COST_PER_CALL; + check_cost(cost, cost_left)?; + + match op { + TreeOp::SExp(node) => match a.node(node) { + NodeVisitor::Buffer(bytes) => { + cost = cost + (SHA256TREE_COST_PER_BYTE * bytes.len() as u64); + check_cost(cost, cost_left)?; + let hash = tree_hash_atom(bytes); + hashes.push(hash); + } + NodeVisitor::U32(val) => { + cost = cost + (SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64); + check_cost(cost, cost_left)?; + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); + } + } + NodeVisitor::Pair(left, right) => { + cost = cost + (SHA256TREE_COST_PER_BYTE * 65_u64); + check_cost(cost, cost_left)?; + if let Some(hash) = cache.get(node) { + hashes.push(*hash); + } else { + if cache.should_memoize(node) { + ops.push(TreeOp::ConsAddCache(node)); + } else { + ops.push(TreeOp::Cons); + } + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + } + }, + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(tree_hash_pair(first, rest)); + } + TreeOp::ConsAddCache(original_node) => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + let hash = tree_hash_pair(first, rest); + hashes.push(hash); + cache.insert(original_node, &hash); + } + } + } + + assert!(hashes.len() == 1); + Ok(hashes[0]) +} + + +pub fn op_sha256_tree(a: &mut Allocator, mut input: NodePtr, max_cost: Cost) -> Response { + let mut cost = SHA256TREE_BASE_COST; + check_cost(cost, max_cost)?; + let mut total = G1Element::default(); + let mut is_first = true; + while let Some((arg, rest)) = a.next(input) { + input = rest; + let point = a.g1(arg)?; + cost += BLS_G1_SUBTRACT_COST_PER_ARG; + check_cost(cost, max_cost)?; + if is_first { + total = point; + } else { + total -= &point; + }; + is_first = false; + } + Ok(Reduction( + cost + 48 * MALLOC_COST_PER_BYTE, + a.new_g1(total)?, + )) +} \ No newline at end of file From d1e93dfc2e1f4a9880b7486f535094cba9f889de Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 25 Sep 2025 14:52:02 +0100 Subject: [PATCH 002/110] bring the cache over --- src/f_table.rs | 4 +- src/sha_tree_op.rs | 106 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/src/f_table.rs b/src/f_table.rs index 3b82a21ec..28338786a 100644 --- a/src/f_table.rs +++ b/src/f_table.rs @@ -6,7 +6,6 @@ use crate::bls_ops::{ op_bls_g2_negate, op_bls_g2_subtract, op_bls_map_to_g1, op_bls_map_to_g2, op_bls_pairing_identity, op_bls_verify, }; -use crate::sha_tree_op::op_sha256_tree; use crate::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_rest}; use crate::cost::Cost; use crate::more_ops::{ @@ -16,6 +15,7 @@ use crate::more_ops::{ }; use crate::reduction::Response; use crate::secp_ops::{op_secp256k1_verify, op_secp256r1_verify}; +use crate::sha_tree_op::op_sha256_tree; type OpFn = fn(&mut Allocator, NodePtr, Cost) -> Response; @@ -67,7 +67,7 @@ pub fn opcode_by_name(name: &str) -> Option { (op_bls_verify, "op_bls_verify"), (op_secp256k1_verify, "op_secp256k1_verify"), (op_secp256r1_verify, "op_secp256r1_verify"), - (op_sha256_tree, "op_sha256_tree") + (op_sha256_tree, "op_sha256_tree"), ]; let name: &[u8] = name.as_ref(); for (f, op) in opcode_lookup.iter() { diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index 4e9ed9e32..33e34489b 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -15,6 +15,109 @@ const SHA256TREE_BASE_COST: Cost = 50; const SHA256TREE_COST_PER_CALL: Cost = 160; const SHA256TREE_COST_PER_BYTE: Cost = 2; +#[derive(Default)] +pub struct TreeCache { + hashes: Vec, + // each entry is an index into hashes, or one of 3 special values: + // u16::MAX if the pair has not been visited + // u16::MAX - 1 if the pair has been seen once + // u16::MAX - 2 if the pair has been seen at least twice (this makes it a + // candidate for memoization) + pairs: Vec, +} + +const NOT_VISITED: u16 = u16::MAX; +const SEEN_ONCE: u16 = u16::MAX - 1; +const SEEN_MULTIPLE: u16 = u16::MAX - 2; + +impl TreeCache { + pub fn get(&self, n: NodePtr) -> Option<&TreeHash> { + // We only cache pairs (for now) + if !matches!(n.object_type(), ObjectType::Pair) { + return None; + } + + let idx = n.index() as usize; + let slot = *self.pairs.get(idx)?; + if slot >= SEEN_MULTIPLE { + return None; + } + Some(&self.hashes[slot as usize]) + } + + pub fn insert(&mut self, n: NodePtr, hash: &TreeHash) { + // If we've reached the max size, just ignore new cache items + if self.hashes.len() == SEEN_MULTIPLE as usize { + return; + } + + if !matches!(n.object_type(), ObjectType::Pair) { + return; + } + + let idx = n.index() as usize; + if idx >= self.pairs.len() { + self.pairs.resize(idx + 1, NOT_VISITED); + } + + let slot = self.hashes.len(); + self.hashes.push(*hash); + self.pairs[idx] = slot as u16; + } + + /// mark the node as being visited. Returns true if we need to + /// traverse visitation down this node. + fn visit(&mut self, n: NodePtr) -> bool { + if !matches!(n.object_type(), ObjectType::Pair) { + return false; + } + let idx = n.index() as usize; + if idx >= self.pairs.len() { + self.pairs.resize(idx + 1, NOT_VISITED); + } + if self.pairs[idx] > SEEN_MULTIPLE { + self.pairs[idx] -= 1; + } + self.pairs[idx] == SEEN_ONCE + } + + pub fn should_memoize(&mut self, n: NodePtr) -> bool { + if !matches!(n.object_type(), ObjectType::Pair) { + return false; + } + let idx = n.index() as usize; + if idx >= self.pairs.len() { + false + } else { + self.pairs[idx] <= SEEN_MULTIPLE + } + } + + pub fn visit_tree(&mut self, a: &Allocator, node: NodePtr) { + if !self.visit(node) { + return; + } + let mut nodes = vec![node]; + while let Some(n) = nodes.pop() { + let SExp::Pair(left, right) = a.sexp(n) else { + continue; + }; + if self.visit(left) { + nodes.push(left); + } + if self.visit(right) { + nodes.push(right); + } + } + } +} + +enum TreeOp { + SExp(NodePtr), + Cons, + ConsAddCache(NodePtr), +} + pub fn tree_hash_cached_costed( a: &Allocator, node: NodePtr, @@ -86,7 +189,6 @@ pub fn tree_hash_cached_costed( Ok(hashes[0]) } - pub fn op_sha256_tree(a: &mut Allocator, mut input: NodePtr, max_cost: Cost) -> Response { let mut cost = SHA256TREE_BASE_COST; check_cost(cost, max_cost)?; @@ -108,4 +210,4 @@ pub fn op_sha256_tree(a: &mut Allocator, mut input: NodePtr, max_cost: Cost) -> cost + 48 * MALLOC_COST_PER_BYTE, a.new_g1(total)?, )) -} \ No newline at end of file +} From 4ff94fce6c2d8b0df0c52e39c63f571231f7609b Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 25 Sep 2025 14:54:29 +0100 Subject: [PATCH 003/110] more functionality --- src/chia_dialect.rs | 3 --- src/sha_tree_op.rs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index 5f822f57c..d1f59bb38 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -167,9 +167,6 @@ impl Dialect for ChiaDialect { 60 => op_modpow, 61 => op_mod, 62 if (flags & ENABLE_KECCAK_OPS_OUTSIDE_GUARD) != 0 => op_keccak256, - _ => { - return unknown_operator(allocator, o, argument_list, flags, max_cost); - } 63 if (flags & ENABLE_SHA256_TREE) != 0 => op_sha256_tree, _ => { return unknown_operator(allocator, o, argument_list, flags, max_cost); diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index 33e34489b..6682ecddb 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -15,6 +15,24 @@ const SHA256TREE_BASE_COST: Cost = 50; const SHA256TREE_COST_PER_CALL: Cost = 160; const SHA256TREE_COST_PER_BYTE: Cost = 2; +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TreeHash([u8; 32]); + +pub fn tree_hash_atom(bytes: &[u8]) -> TreeHash { + let mut sha256 = Sha256::new(); + sha256.update([1]); + sha256.update(bytes); + TreeHash::new(sha256.finalize()) +} + +pub fn tree_hash_pair(first: TreeHash, rest: TreeHash) -> TreeHash { + let mut sha256 = Sha256::new(); + sha256.update([2]); + sha256.update(first); + sha256.update(rest); + TreeHash::new(sha256.finalize()) +} + #[derive(Default)] pub struct TreeCache { hashes: Vec, From 850e5d5ee94e5d78ecdfe60145e5043e23124f70 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 25 Sep 2025 16:09:30 +0100 Subject: [PATCH 004/110] separate treehash into own file and further correctness --- fuzz/fuzz_targets/operators.rs | 5 +- src/f_table.rs | 2 - src/lib.rs | 1 + src/sha_tree_op.rs | 178 +++------------------------ src/treehash.rs | 212 +++++++++++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 165 deletions(-) create mode 100644 src/treehash.rs diff --git a/fuzz/fuzz_targets/operators.rs b/fuzz/fuzz_targets/operators.rs index 28f79f70f..1c8a9d524 100644 --- a/fuzz/fuzz_targets/operators.rs +++ b/fuzz/fuzz_targets/operators.rs @@ -20,10 +20,11 @@ use clvmr::more_ops::{ }; use clvmr::reduction::Response; use clvmr::secp_ops::{op_secp256k1_verify, op_secp256r1_verify}; +use clvmr::sha_tree_op::op_sha256_tree; type Opf = fn(&mut Allocator, NodePtr, Cost) -> Response; -const FUNS: [Opf; 46] = [ +const FUNS: [Opf; 47] = [ op_if as Opf, op_cons as Opf, op_first as Opf, @@ -73,6 +74,8 @@ const FUNS: [Opf; 46] = [ op_secp256r1_verify as Opf, // keccak operator op_keccak256 as Opf, + // shatree operator + op_sha256_tree as Opf, ]; fuzz_target!(|data: &[u8]| { diff --git a/src/f_table.rs b/src/f_table.rs index 28338786a..2d584aa39 100644 --- a/src/f_table.rs +++ b/src/f_table.rs @@ -15,7 +15,6 @@ use crate::more_ops::{ }; use crate::reduction::Response; use crate::secp_ops::{op_secp256k1_verify, op_secp256r1_verify}; -use crate::sha_tree_op::op_sha256_tree; type OpFn = fn(&mut Allocator, NodePtr, Cost) -> Response; @@ -67,7 +66,6 @@ pub fn opcode_by_name(name: &str) -> Option { (op_bls_verify, "op_bls_verify"), (op_secp256k1_verify, "op_secp256k1_verify"), (op_secp256r1_verify, "op_secp256r1_verify"), - (op_sha256_tree, "op_sha256_tree"), ]; let name: &[u8] = name.as_ref(); for (f, op) in opcode_lookup.iter() { diff --git a/src/lib.rs b/src/lib.rs index 3d8f21b33..11f936781 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,7 @@ pub mod secp_ops; pub mod serde; pub mod sha_tree_op; pub mod traverse_path; +pub mod treehash; pub use allocator::{Allocator, Atom, NodePtr, ObjectType, SExp}; pub use chia_dialect::ChiaDialect; diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index 6682ecddb..b9d22c724 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -1,149 +1,20 @@ -use crate::allocator::{Allocator, Atom, NodePtr}; +use crate::allocator::NodeVisitor; +use crate::allocator::{Allocator, NodePtr}; use crate::cost::{check_cost, Cost}; -use crate::error::EvalErr; -use crate::op_utils::{ - atom, first, get_args, get_varargs, int_atom, mod_group_order, new_atom_and_cost, nilp, rest, - MALLOC_COST_PER_BYTE, -}; +use crate::op_utils::get_args; use crate::reduction::{Reduction, Response}; -use chia_bls::{ - aggregate_pairing, aggregate_verify, hash_to_g1_with_dst, hash_to_g2_with_dst, G1Element, - G2Element, PublicKey, -}; +use crate::treehash::*; const SHA256TREE_BASE_COST: Cost = 50; const SHA256TREE_COST_PER_CALL: Cost = 160; const SHA256TREE_COST_PER_BYTE: Cost = 2; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TreeHash([u8; 32]); - -pub fn tree_hash_atom(bytes: &[u8]) -> TreeHash { - let mut sha256 = Sha256::new(); - sha256.update([1]); - sha256.update(bytes); - TreeHash::new(sha256.finalize()) -} - -pub fn tree_hash_pair(first: TreeHash, rest: TreeHash) -> TreeHash { - let mut sha256 = Sha256::new(); - sha256.update([2]); - sha256.update(first); - sha256.update(rest); - TreeHash::new(sha256.finalize()) -} - -#[derive(Default)] -pub struct TreeCache { - hashes: Vec, - // each entry is an index into hashes, or one of 3 special values: - // u16::MAX if the pair has not been visited - // u16::MAX - 1 if the pair has been seen once - // u16::MAX - 2 if the pair has been seen at least twice (this makes it a - // candidate for memoization) - pairs: Vec, -} - -const NOT_VISITED: u16 = u16::MAX; -const SEEN_ONCE: u16 = u16::MAX - 1; -const SEEN_MULTIPLE: u16 = u16::MAX - 2; - -impl TreeCache { - pub fn get(&self, n: NodePtr) -> Option<&TreeHash> { - // We only cache pairs (for now) - if !matches!(n.object_type(), ObjectType::Pair) { - return None; - } - - let idx = n.index() as usize; - let slot = *self.pairs.get(idx)?; - if slot >= SEEN_MULTIPLE { - return None; - } - Some(&self.hashes[slot as usize]) - } - - pub fn insert(&mut self, n: NodePtr, hash: &TreeHash) { - // If we've reached the max size, just ignore new cache items - if self.hashes.len() == SEEN_MULTIPLE as usize { - return; - } - - if !matches!(n.object_type(), ObjectType::Pair) { - return; - } - - let idx = n.index() as usize; - if idx >= self.pairs.len() { - self.pairs.resize(idx + 1, NOT_VISITED); - } - - let slot = self.hashes.len(); - self.hashes.push(*hash); - self.pairs[idx] = slot as u16; - } - - /// mark the node as being visited. Returns true if we need to - /// traverse visitation down this node. - fn visit(&mut self, n: NodePtr) -> bool { - if !matches!(n.object_type(), ObjectType::Pair) { - return false; - } - let idx = n.index() as usize; - if idx >= self.pairs.len() { - self.pairs.resize(idx + 1, NOT_VISITED); - } - if self.pairs[idx] > SEEN_MULTIPLE { - self.pairs[idx] -= 1; - } - self.pairs[idx] == SEEN_ONCE - } - - pub fn should_memoize(&mut self, n: NodePtr) -> bool { - if !matches!(n.object_type(), ObjectType::Pair) { - return false; - } - let idx = n.index() as usize; - if idx >= self.pairs.len() { - false - } else { - self.pairs[idx] <= SEEN_MULTIPLE - } - } - - pub fn visit_tree(&mut self, a: &Allocator, node: NodePtr) { - if !self.visit(node) { - return; - } - let mut nodes = vec![node]; - while let Some(n) = nodes.pop() { - let SExp::Pair(left, right) = a.sexp(n) else { - continue; - }; - if self.visit(left) { - nodes.push(left); - } - if self.visit(right) { - nodes.push(right); - } - } - } -} - -enum TreeOp { - SExp(NodePtr), - Cons, - ConsAddCache(NodePtr), -} - pub fn tree_hash_cached_costed( - a: &Allocator, + a: &mut Allocator, node: NodePtr, cache: &mut TreeCache, - cost_left: &mut u64, - cost_per_call: Cost, - cost_per_byte: Cost, -) -> Result { + cost_left: u64, +) -> Response { cache.visit_tree(a, node); let mut hashes = Vec::new(); @@ -152,19 +23,19 @@ pub fn tree_hash_cached_costed( // we will call check_cost throughout the runtime so we can exit immediately if we go over cost while let Some(op) = ops.pop() { - cost = cost + SHA256TREE_COST_PER_CALL; + cost += SHA256TREE_COST_PER_CALL; check_cost(cost, cost_left)?; match op { TreeOp::SExp(node) => match a.node(node) { NodeVisitor::Buffer(bytes) => { - cost = cost + (SHA256TREE_COST_PER_BYTE * bytes.len() as u64); + cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; check_cost(cost, cost_left)?; let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { - cost = cost + (SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64); + cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; check_cost(cost, cost_left)?; if (val as usize) < PRECOMPUTED_HASHES.len() { hashes.push(PRECOMPUTED_HASHES[val as usize]); @@ -173,7 +44,7 @@ pub fn tree_hash_cached_costed( } } NodeVisitor::Pair(left, right) => { - cost = cost + (SHA256TREE_COST_PER_BYTE * 65_u64); + cost += SHA256TREE_COST_PER_BYTE * 65_u64; check_cost(cost, cost_left)?; if let Some(hash) = cache.get(node) { hashes.push(*hash); @@ -204,28 +75,11 @@ pub fn tree_hash_cached_costed( } assert!(hashes.len() == 1); - Ok(hashes[0]) + Ok(Reduction(cost, a.new_atom(&hashes[0])?)) } -pub fn op_sha256_tree(a: &mut Allocator, mut input: NodePtr, max_cost: Cost) -> Response { - let mut cost = SHA256TREE_BASE_COST; - check_cost(cost, max_cost)?; - let mut total = G1Element::default(); - let mut is_first = true; - while let Some((arg, rest)) = a.next(input) { - input = rest; - let point = a.g1(arg)?; - cost += BLS_G1_SUBTRACT_COST_PER_ARG; - check_cost(cost, max_cost)?; - if is_first { - total = point; - } else { - total -= &point; - }; - is_first = false; - } - Ok(Reduction( - cost + 48 * MALLOC_COST_PER_BYTE, - a.new_g1(total)?, - )) +pub fn op_sha256_tree(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { + let [n] = get_args::<1>(a, input, "sha256tree")?; + let mut cache = TreeCache::default(); + tree_hash_cached_costed(a, n, &mut cache, max_cost) } diff --git a/src/treehash.rs b/src/treehash.rs new file mode 100644 index 000000000..4b9d84f43 --- /dev/null +++ b/src/treehash.rs @@ -0,0 +1,212 @@ +use crate::allocator::{Allocator, NodePtr}; +use crate::ObjectType; +use crate::SExp; +use chia_sha2::Sha256; +use hex_literal::hex; +use std::fmt; +use std::ops::Deref; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TreeHash([u8; 32]); + +impl TreeHash { + pub const fn new(hash: [u8; 32]) -> Self { + Self(hash) + } + + pub const fn to_bytes(&self) -> [u8; 32] { + self.0 + } + + pub fn to_vec(&self) -> Vec { + self.0.to_vec() + } +} + +impl fmt::Debug for TreeHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TreeHash({self})") + } +} + +impl fmt::Display for TreeHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.0)) + } +} + +impl From<[u8; 32]> for TreeHash { + fn from(hash: [u8; 32]) -> Self { + Self::new(hash) + } +} + +impl From for [u8; 32] { + fn from(hash: TreeHash) -> [u8; 32] { + hash.0 + } +} + +impl AsRef<[u8]> for TreeHash { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl Deref for TreeHash { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +pub fn tree_hash_atom(bytes: &[u8]) -> TreeHash { + let mut sha256 = Sha256::new(); + sha256.update([1]); + sha256.update(bytes); + TreeHash::new(sha256.finalize()) +} + +pub fn tree_hash_pair(first: TreeHash, rest: TreeHash) -> TreeHash { + let mut sha256 = Sha256::new(); + sha256.update([2]); + sha256.update(first); + sha256.update(rest); + TreeHash::new(sha256.finalize()) +} + +#[derive(Default)] +pub struct TreeCache { + hashes: Vec, + // each entry is an index into hashes, or one of 3 special values: + // u16::MAX if the pair has not been visited + // u16::MAX - 1 if the pair has been seen once + // u16::MAX - 2 if the pair has been seen at least twice (this makes it a + // candidate for memoization) + pairs: Vec, +} + +const NOT_VISITED: u16 = u16::MAX; +const SEEN_ONCE: u16 = u16::MAX - 1; +const SEEN_MULTIPLE: u16 = u16::MAX - 2; + +impl TreeCache { + pub fn get(&self, n: NodePtr) -> Option<&TreeHash> { + // We only cache pairs (for now) + if !matches!(n.object_type(), ObjectType::Pair) { + return None; + } + + let idx = n.index() as usize; + let slot = *self.pairs.get(idx)?; + if slot >= SEEN_MULTIPLE { + return None; + } + Some(&self.hashes[slot as usize]) + } + + pub fn insert(&mut self, n: NodePtr, hash: &TreeHash) { + // If we've reached the max size, just ignore new cache items + if self.hashes.len() == SEEN_MULTIPLE as usize { + return; + } + + if !matches!(n.object_type(), ObjectType::Pair) { + return; + } + + let idx = n.index() as usize; + if idx >= self.pairs.len() { + self.pairs.resize(idx + 1, NOT_VISITED); + } + + let slot = self.hashes.len(); + self.hashes.push(*hash); + self.pairs[idx] = slot as u16; + } + + /// mark the node as being visited. Returns true if we need to + /// traverse visitation down this node. + fn visit(&mut self, n: NodePtr) -> bool { + if !matches!(n.object_type(), ObjectType::Pair) { + return false; + } + let idx = n.index() as usize; + if idx >= self.pairs.len() { + self.pairs.resize(idx + 1, NOT_VISITED); + } + if self.pairs[idx] > SEEN_MULTIPLE { + self.pairs[idx] -= 1; + } + self.pairs[idx] == SEEN_ONCE + } + + pub fn should_memoize(&mut self, n: NodePtr) -> bool { + if !matches!(n.object_type(), ObjectType::Pair) { + return false; + } + let idx = n.index() as usize; + if idx >= self.pairs.len() { + false + } else { + self.pairs[idx] <= SEEN_MULTIPLE + } + } + + pub fn visit_tree(&mut self, a: &Allocator, node: NodePtr) { + if !self.visit(node) { + return; + } + let mut nodes = vec![node]; + while let Some(n) = nodes.pop() { + let SExp::Pair(left, right) = a.sexp(n) else { + continue; + }; + if self.visit(left) { + nodes.push(left); + } + if self.visit(right) { + nodes.push(right); + } + } + } +} + +pub(crate) enum TreeOp { + SExp(NodePtr), + Cons, + ConsAddCache(NodePtr), +} + +macro_rules! th { + ($hash:expr) => { + TreeHash::new(hex!($hash)) + }; +} +pub const PRECOMPUTED_HASHES: [TreeHash; 24] = [ + th!("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"), + th!("9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2"), + th!("a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222"), + th!("c79b932e1e1da3c0e098e5ad2c422937eb904a76cf61d83975a74a68fbb04b99"), + th!("a8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e7298a91ce119a63400ade7c5"), + th!("bc5959f43bc6e47175374b6716e53c9a7d72c59424c821336995bad760d9aeb3"), + th!("44602a999abbebedf7de0ae1318e4f57e3cb1d67e482a65f9657f7541f3fe4bb"), + th!("ca6c6588fa01171b200740344d354e8548b7470061fb32a34f4feee470ec281f"), + th!("9e6282e4f25e370ce617e21d6fe265e88b9e7b8682cf00059b9d128d9381f09d"), + th!("ac9e61d54eb6967e212c06aab15408292f8558c48f06f9d705150063c68753b0"), + th!("c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80"), + th!("57bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9"), + th!("6b6daa8334bbcc8f6b5906b6c04be041d92700b74024f73f50e0a9f0dae5f06f"), + th!("c7b89cfb9abf2c4cb212a4840b37d762f4c880b8517b0dadb0c310ded24dd86d"), + th!("653b3bb3e18ef84d5b1e8ff9884aecf1950c7a1c98715411c22b987663b86dda"), + th!("24255ef5d941493b9978f3aabb0ed07d084ade196d23f463ff058954cbf6e9b6"), + th!("af340aa58ea7d72c2f9a7405f3734167bb27dd2a520d216addef65f8362102b6"), + th!("26e7f98cfafee5b213726e22632923bf31bf3e988233235f8f5ca5466b3ac0ed"), + th!("115b498ce94335826baa16386cd1e2fde8ca408f6f50f3785964f263cdf37ebe"), + th!("d8c50d6282a1ba47f0a23430d177bbfbb72e2b84713745e894f575570f1f3d6e"), + th!("dbe726e81a7221a385e007ef9e834a975a4b528c6f55a5d2ece288bee831a3d1"), + th!("764c8a3561c7cf261771b4e1969b84c210836f3c034baebac5e49a394a6ee0a9"), + th!("dce37f3512b6337d27290436ba9289e2fd6c775494c33668dd177cf811fbd47a"), + th!("5809addc9f6926fc5c4e20cf87958858c4454c21cdfc6b02f377f12c06b35cca"), +]; From 790cc9b9b65cb36484eae6772727d3695095f413 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 26 Sep 2025 16:40:26 +0100 Subject: [PATCH 005/110] test test --- op-tests/test-sha256tree.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 op-tests/test-sha256tree.txt diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt new file mode 100644 index 000000000..c0a8c0cad --- /dev/null +++ b/op-tests/test-sha256tree.txt @@ -0,0 +1 @@ +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 \ No newline at end of file From 814478036de2ece50654b9336e73b6f4aa34dd56 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 27 Oct 2025 10:38:25 +0000 Subject: [PATCH 006/110] fix testing and add new tests --- op-tests/test-sha256tree.txt | 4 +++- src/test_ops.rs | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index c0a8c0cad..3ac33aaaf 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1 +1,3 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 \ No newline at end of file +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 212 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 210 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 832 \ No newline at end of file diff --git a/src/test_ops.rs b/src/test_ops.rs index 3cf385342..885687114 100644 --- a/src/test_ops.rs +++ b/src/test_ops.rs @@ -15,6 +15,7 @@ use crate::more_ops::{ use crate::number::Number; use crate::reduction::{Reduction, Response}; use crate::secp_ops::{op_secp256k1_verify, op_secp256r1_verify}; +use crate::sha_tree_op::op_sha256_tree; use crate::error::EvalErr; use hex::FromHex; @@ -106,6 +107,7 @@ fn parse_atom(a: &mut Allocator, v: &str) -> NodePtr { "secp256k1_verify" => a.new_atom(&[0x13, 0xd6, 0x1f, 0x00]).unwrap(), "secp256r1_verify" => a.new_atom(&[0x1c, 0x3a, 0x8f, 0x00]).unwrap(), "keccak256" => a.new_atom(&[62]).unwrap(), + "sha256tree" => a.new_atom(&[63]).unwrap(), _ => { panic!("atom not supported \"{v}\""); } @@ -191,6 +193,9 @@ pub fn node_eq(allocator: &Allocator, s1: NodePtr, s2: NodePtr) -> bool { (SExp::Atom, SExp::Atom) => { if !allocator.atom_eq(l, r) { return false; + // let left = allocator.atom(l); + // let right = allocator.atom(r); + // panic!("left val {:?} - right val {:?}", left, right); } } _ => { @@ -264,6 +269,7 @@ mod tests { #[case("test-secp256r1")] #[case("test-modpow")] #[case("test-sha256")] + #[case("test-sha256tree")] #[case("test-keccak256")] #[case("test-keccak256-generated")] fn test_ops(#[case] filename: &str) { @@ -320,6 +326,7 @@ mod tests { ("secp256r1_verify", op_secp256r1_verify as Opf), ("modpow", op_modpow as Opf), ("keccak256", op_keccak256 as Opf), + ("sha256tree", op_sha256_tree as Opf), ]); println!("Test cases from: {filename}"); From b02106fd8433a5bb2550c9777845e0820ca1e023 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 27 Oct 2025 12:36:16 +0000 Subject: [PATCH 007/110] add more tests --- op-tests/test-sha256tree.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 3ac33aaaf..52f827173 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,3 +1,5 @@ sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 212 sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 210 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 832 \ No newline at end of file +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 832 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 2664 +sha256tree (202 254 (240 13)) => 5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 3274 \ No newline at end of file From feb2f0f854435196a7320441f7bb610d29a5875a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 27 Oct 2025 13:12:35 +0000 Subject: [PATCH 008/110] adjust costs --- op-tests/test-sha256tree.txt | 10 +++++----- src/sha_tree_op.rs | 6 +++--- src/test_ops.rs | 3 --- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 52f827173..deb5bc44a 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,5 +1,5 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 212 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 210 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 832 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 2664 -sha256tree (202 254 (240 13)) => 5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 3274 \ No newline at end of file +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1310 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1300 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 5910 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 19570 +sha256tree (202 254 (240 13)) => 5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 24120 \ No newline at end of file diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index b9d22c724..8bbdc071f 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -5,9 +5,9 @@ use crate::op_utils::get_args; use crate::reduction::{Reduction, Response}; use crate::treehash::*; -const SHA256TREE_BASE_COST: Cost = 50; -const SHA256TREE_COST_PER_CALL: Cost = 160; -const SHA256TREE_COST_PER_BYTE: Cost = 2; +const SHA256TREE_BASE_COST: Cost = 0; +const SHA256TREE_COST_PER_CALL: Cost = 1300; +const SHA256TREE_COST_PER_BYTE: Cost = 10; pub fn tree_hash_cached_costed( a: &mut Allocator, diff --git a/src/test_ops.rs b/src/test_ops.rs index 885687114..7cc5db382 100644 --- a/src/test_ops.rs +++ b/src/test_ops.rs @@ -193,9 +193,6 @@ pub fn node_eq(allocator: &Allocator, s1: NodePtr, s2: NodePtr) -> bool { (SExp::Atom, SExp::Atom) => { if !allocator.atom_eq(l, r) { return false; - // let left = allocator.atom(l); - // let right = allocator.atom(r); - // panic!("left val {:?} - right val {:?}", left, right); } } _ => { From 0c023d93c6b61c9ab746ed0bb08c0ff9e6fbca97 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 27 Oct 2025 13:39:49 +0000 Subject: [PATCH 009/110] add 0x --- op-tests/test-sha256tree.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index deb5bc44a..5958074cf 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -2,4 +2,4 @@ sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7 sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1300 sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 5910 sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 19570 -sha256tree (202 254 (240 13)) => 5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 24120 \ No newline at end of file +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 24120 \ No newline at end of file From f340214c6cd82c214f043ae8000120f882d66035 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 27 Oct 2025 15:30:27 +0000 Subject: [PATCH 010/110] cache cost --- src/sha_tree_op.rs | 24 ++++++++++++++++-------- src/treehash.rs | 17 ++++++++++++----- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index 8bbdc071f..dfff0048a 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -13,19 +13,18 @@ pub fn tree_hash_cached_costed( a: &mut Allocator, node: NodePtr, cache: &mut TreeCache, - cost_left: u64, + cost_left: Cost, ) -> Response { cache.visit_tree(a, node); let mut hashes = Vec::new(); let mut ops = vec![TreeOp::SExp(node)]; - let mut cost = SHA256TREE_BASE_COST; + let mut cost: Cost = SHA256TREE_BASE_COST; - // we will call check_cost throughout the runtime so we can exit immediately if we go over cost while let Some(op) = ops.pop() { + // charge a call cost for processing this op cost += SHA256TREE_COST_PER_CALL; check_cost(cost, cost_left)?; - match op { TreeOp::SExp(node) => match a.node(node) { NodeVisitor::Buffer(bytes) => { @@ -44,13 +43,18 @@ pub fn tree_hash_cached_costed( } } NodeVisitor::Pair(left, right) => { + // pair cost (65 bytes as before) cost += SHA256TREE_COST_PER_BYTE * 65_u64; check_cost(cost, cost_left)?; - if let Some(hash) = cache.get(node) { + if let Some((hash, cached_cost)) = cache.get(node) { + // when reusing a cached subtree, charge its cached cost + cost += cached_cost; + check_cost(cost, cost_left)?; hashes.push(*hash); } else { if cache.should_memoize(node) { - ops.push(TreeOp::ConsAddCache(node)); + // record the cost_left before traversing this subtree + ops.push(TreeOp::ConsAddCacheCost(node, cost)); } else { ops.push(TreeOp::Cons); } @@ -64,12 +68,16 @@ pub fn tree_hash_cached_costed( let rest = hashes.pop().unwrap(); hashes.push(tree_hash_pair(first, rest)); } - TreeOp::ConsAddCache(original_node) => { + TreeOp::ConsAddCacheCost(original_node, cost_before) => { let first = hashes.pop().unwrap(); let rest = hashes.pop().unwrap(); let hash = tree_hash_pair(first, rest); hashes.push(hash); - cache.insert(original_node, &hash); + // cost_before will be lower + // cost_left is the remaining after computing it + // the cost of this subtree = after - before + let used = cost - cost_before; + cache.insert(original_node, &hash, used); } } } diff --git a/src/treehash.rs b/src/treehash.rs index 4b9d84f43..3e7a56dd8 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -1,4 +1,5 @@ use crate::allocator::{Allocator, NodePtr}; +use crate::cost::Cost; use crate::ObjectType; use crate::SExp; use chia_sha2::Sha256; @@ -79,7 +80,9 @@ pub fn tree_hash_pair(first: TreeHash, rest: TreeHash) -> TreeHash { #[derive(Default)] pub struct TreeCache { hashes: Vec, - // each entry is an index into hashes, or one of 3 special values: + // parallel vector holding the cost used to compute the corresponding hash + costs: Vec, + // each entry is an index into hashes and costs, or one of 3 special values: // u16::MAX if the pair has not been visited // u16::MAX - 1 if the pair has been seen once // u16::MAX - 2 if the pair has been seen at least twice (this makes it a @@ -92,7 +95,8 @@ const SEEN_ONCE: u16 = u16::MAX - 1; const SEEN_MULTIPLE: u16 = u16::MAX - 2; impl TreeCache { - pub fn get(&self, n: NodePtr) -> Option<&TreeHash> { + /// Get cached hash and its associated cost (if present). + pub fn get(&self, n: NodePtr) -> Option<(&TreeHash, Cost)> { // We only cache pairs (for now) if !matches!(n.object_type(), ObjectType::Pair) { return None; @@ -103,10 +107,12 @@ impl TreeCache { if slot >= SEEN_MULTIPLE { return None; } - Some(&self.hashes[slot as usize]) + Some((&self.hashes[slot as usize], self.costs[slot as usize])) } - pub fn insert(&mut self, n: NodePtr, hash: &TreeHash) { + /// Insert a cached hash with its associated cost. If the cache is full we + /// ignore the insertion. + pub fn insert(&mut self, n: NodePtr, hash: &TreeHash, cost: Cost) { // If we've reached the max size, just ignore new cache items if self.hashes.len() == SEEN_MULTIPLE as usize { return; @@ -123,6 +129,7 @@ impl TreeCache { let slot = self.hashes.len(); self.hashes.push(*hash); + self.costs.push(cost); self.pairs[idx] = slot as u16; } @@ -176,7 +183,7 @@ impl TreeCache { pub(crate) enum TreeOp { SExp(NodePtr), Cons, - ConsAddCache(NodePtr), + ConsAddCacheCost(NodePtr, Cost), } macro_rules! th { From 0925c419fee3517d80d7bde0d8d923cf15475ffe Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 28 Oct 2025 10:26:17 +0000 Subject: [PATCH 011/110] no TreeHash type --- src/treehash.rs | 75 ++++++------------------------------------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 3e7a56dd8..69a1f5d39 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -4,82 +4,25 @@ use crate::ObjectType; use crate::SExp; use chia_sha2::Sha256; use hex_literal::hex; -use std::fmt; -use std::ops::Deref; -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct TreeHash([u8; 32]); - -impl TreeHash { - pub const fn new(hash: [u8; 32]) -> Self { - Self(hash) - } - - pub const fn to_bytes(&self) -> [u8; 32] { - self.0 - } - - pub fn to_vec(&self) -> Vec { - self.0.to_vec() - } -} - -impl fmt::Debug for TreeHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "TreeHash({self})") - } -} - -impl fmt::Display for TreeHash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", hex::encode(self.0)) - } -} - -impl From<[u8; 32]> for TreeHash { - fn from(hash: [u8; 32]) -> Self { - Self::new(hash) - } -} - -impl From for [u8; 32] { - fn from(hash: TreeHash) -> [u8; 32] { - hash.0 - } -} - -impl AsRef<[u8]> for TreeHash { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl Deref for TreeHash { - type Target = [u8]; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -pub fn tree_hash_atom(bytes: &[u8]) -> TreeHash { +pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); sha256.update([1]); sha256.update(bytes); - TreeHash::new(sha256.finalize()) + sha256.finalize() } -pub fn tree_hash_pair(first: TreeHash, rest: TreeHash) -> TreeHash { +pub fn tree_hash_pair(first: [u8; 32], rest: [u8; 32]) -> [u8; 32] { let mut sha256 = Sha256::new(); sha256.update([2]); sha256.update(first); sha256.update(rest); - TreeHash::new(sha256.finalize()) + sha256.finalize() } #[derive(Default)] pub struct TreeCache { - hashes: Vec, + hashes: Vec<[u8; 32]>, // parallel vector holding the cost used to compute the corresponding hash costs: Vec, // each entry is an index into hashes and costs, or one of 3 special values: @@ -96,7 +39,7 @@ const SEEN_MULTIPLE: u16 = u16::MAX - 2; impl TreeCache { /// Get cached hash and its associated cost (if present). - pub fn get(&self, n: NodePtr) -> Option<(&TreeHash, Cost)> { + pub fn get(&self, n: NodePtr) -> Option<(&[u8; 32], Cost)> { // We only cache pairs (for now) if !matches!(n.object_type(), ObjectType::Pair) { return None; @@ -112,7 +55,7 @@ impl TreeCache { /// Insert a cached hash with its associated cost. If the cache is full we /// ignore the insertion. - pub fn insert(&mut self, n: NodePtr, hash: &TreeHash, cost: Cost) { + pub fn insert(&mut self, n: NodePtr, hash: &[u8; 32], cost: Cost) { // If we've reached the max size, just ignore new cache items if self.hashes.len() == SEEN_MULTIPLE as usize { return; @@ -188,10 +131,10 @@ pub(crate) enum TreeOp { macro_rules! th { ($hash:expr) => { - TreeHash::new(hex!($hash)) + hex!($hash) }; } -pub const PRECOMPUTED_HASHES: [TreeHash; 24] = [ +pub const PRECOMPUTED_HASHES: [[u8; 32]; 24] = [ th!("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"), th!("9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2"), th!("a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222"), From ed420806191d3e306264b7674a1d59c26f254a13 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 28 Oct 2025 10:46:34 +0000 Subject: [PATCH 012/110] remove duplicate precomputed_hashes --- src/sha_tree_op.rs | 1 + src/treehash.rs | 33 --------------------------------- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index dfff0048a..00a39ae9e 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -1,6 +1,7 @@ use crate::allocator::NodeVisitor; use crate::allocator::{Allocator, NodePtr}; use crate::cost::{check_cost, Cost}; +use crate::more_ops::PRECOMPUTED_HASHES; use crate::op_utils::get_args; use crate::reduction::{Reduction, Response}; use crate::treehash::*; diff --git a/src/treehash.rs b/src/treehash.rs index 69a1f5d39..0704c1d90 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -3,7 +3,6 @@ use crate::cost::Cost; use crate::ObjectType; use crate::SExp; use chia_sha2::Sha256; -use hex_literal::hex; pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); @@ -128,35 +127,3 @@ pub(crate) enum TreeOp { Cons, ConsAddCacheCost(NodePtr, Cost), } - -macro_rules! th { - ($hash:expr) => { - hex!($hash) - }; -} -pub const PRECOMPUTED_HASHES: [[u8; 32]; 24] = [ - th!("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"), - th!("9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2"), - th!("a12871fee210fb8619291eaea194581cbd2531e4b23759d225f6806923f63222"), - th!("c79b932e1e1da3c0e098e5ad2c422937eb904a76cf61d83975a74a68fbb04b99"), - th!("a8d5dd63fba471ebcb1f3e8f7c1e1879b7152a6e7298a91ce119a63400ade7c5"), - th!("bc5959f43bc6e47175374b6716e53c9a7d72c59424c821336995bad760d9aeb3"), - th!("44602a999abbebedf7de0ae1318e4f57e3cb1d67e482a65f9657f7541f3fe4bb"), - th!("ca6c6588fa01171b200740344d354e8548b7470061fb32a34f4feee470ec281f"), - th!("9e6282e4f25e370ce617e21d6fe265e88b9e7b8682cf00059b9d128d9381f09d"), - th!("ac9e61d54eb6967e212c06aab15408292f8558c48f06f9d705150063c68753b0"), - th!("c04b5bb1a5b2eb3e9cd4805420dba5a9d133da5b7adeeafb5474c4adae9faa80"), - th!("57bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9"), - th!("6b6daa8334bbcc8f6b5906b6c04be041d92700b74024f73f50e0a9f0dae5f06f"), - th!("c7b89cfb9abf2c4cb212a4840b37d762f4c880b8517b0dadb0c310ded24dd86d"), - th!("653b3bb3e18ef84d5b1e8ff9884aecf1950c7a1c98715411c22b987663b86dda"), - th!("24255ef5d941493b9978f3aabb0ed07d084ade196d23f463ff058954cbf6e9b6"), - th!("af340aa58ea7d72c2f9a7405f3734167bb27dd2a520d216addef65f8362102b6"), - th!("26e7f98cfafee5b213726e22632923bf31bf3e988233235f8f5ca5466b3ac0ed"), - th!("115b498ce94335826baa16386cd1e2fde8ca408f6f50f3785964f263cdf37ebe"), - th!("d8c50d6282a1ba47f0a23430d177bbfbb72e2b84713745e894f575570f1f3d6e"), - th!("dbe726e81a7221a385e007ef9e834a975a4b528c6f55a5d2ece288bee831a3d1"), - th!("764c8a3561c7cf261771b4e1969b84c210836f3c034baebac5e49a394a6ee0a9"), - th!("dce37f3512b6337d27290436ba9289e2fd6c775494c33668dd177cf811fbd47a"), - th!("5809addc9f6926fc5c4e20cf87958858c4454c21cdfc6b02f377f12c06b35cca"), -]; From 056abac5213708fe5ea8bbe7f16109f70b5cb117 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 28 Oct 2025 11:50:50 +0000 Subject: [PATCH 013/110] make treeop private --- src/sha_tree_op.rs | 87 ++------------------------------------------- src/treehash.rs | 88 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index 00a39ae9e..69da0ed51 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -1,92 +1,9 @@ -use crate::allocator::NodeVisitor; use crate::allocator::{Allocator, NodePtr}; -use crate::cost::{check_cost, Cost}; -use crate::more_ops::PRECOMPUTED_HASHES; +use crate::cost::Cost; use crate::op_utils::get_args; -use crate::reduction::{Reduction, Response}; +use crate::reduction::Response; use crate::treehash::*; -const SHA256TREE_BASE_COST: Cost = 0; -const SHA256TREE_COST_PER_CALL: Cost = 1300; -const SHA256TREE_COST_PER_BYTE: Cost = 10; - -pub fn tree_hash_cached_costed( - a: &mut Allocator, - node: NodePtr, - cache: &mut TreeCache, - cost_left: Cost, -) -> Response { - cache.visit_tree(a, node); - - let mut hashes = Vec::new(); - let mut ops = vec![TreeOp::SExp(node)]; - let mut cost: Cost = SHA256TREE_BASE_COST; - - while let Some(op) = ops.pop() { - // charge a call cost for processing this op - cost += SHA256TREE_COST_PER_CALL; - check_cost(cost, cost_left)?; - match op { - TreeOp::SExp(node) => match a.node(node) { - NodeVisitor::Buffer(bytes) => { - cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; - check_cost(cost, cost_left)?; - let hash = tree_hash_atom(bytes); - hashes.push(hash); - } - NodeVisitor::U32(val) => { - cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; - check_cost(cost, cost_left)?; - if (val as usize) < PRECOMPUTED_HASHES.len() { - hashes.push(PRECOMPUTED_HASHES[val as usize]); - } else { - hashes.push(tree_hash_atom(a.atom(node).as_ref())); - } - } - NodeVisitor::Pair(left, right) => { - // pair cost (65 bytes as before) - cost += SHA256TREE_COST_PER_BYTE * 65_u64; - check_cost(cost, cost_left)?; - if let Some((hash, cached_cost)) = cache.get(node) { - // when reusing a cached subtree, charge its cached cost - cost += cached_cost; - check_cost(cost, cost_left)?; - hashes.push(*hash); - } else { - if cache.should_memoize(node) { - // record the cost_left before traversing this subtree - ops.push(TreeOp::ConsAddCacheCost(node, cost)); - } else { - ops.push(TreeOp::Cons); - } - ops.push(TreeOp::SExp(left)); - ops.push(TreeOp::SExp(right)); - } - } - }, - TreeOp::Cons => { - let first = hashes.pop().unwrap(); - let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(first, rest)); - } - TreeOp::ConsAddCacheCost(original_node, cost_before) => { - let first = hashes.pop().unwrap(); - let rest = hashes.pop().unwrap(); - let hash = tree_hash_pair(first, rest); - hashes.push(hash); - // cost_before will be lower - // cost_left is the remaining after computing it - // the cost of this subtree = after - before - let used = cost - cost_before; - cache.insert(original_node, &hash, used); - } - } - } - - assert!(hashes.len() == 1); - Ok(Reduction(cost, a.new_atom(&hashes[0])?)) -} - pub fn op_sha256_tree(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { let [n] = get_args::<1>(a, input, "sha256tree")?; let mut cache = TreeCache::default(); diff --git a/src/treehash.rs b/src/treehash.rs index 0704c1d90..b50ed4a4a 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -1,9 +1,18 @@ +use crate::allocator::NodeVisitor; use crate::allocator::{Allocator, NodePtr}; +use crate::cost::check_cost; use crate::cost::Cost; +use crate::more_ops::PRECOMPUTED_HASHES; +use crate::reduction::Reduction; +use crate::reduction::Response; use crate::ObjectType; use crate::SExp; use chia_sha2::Sha256; +const SHA256TREE_BASE_COST: Cost = 0; +const SHA256TREE_COST_PER_CALL: Cost = 1300; +const SHA256TREE_COST_PER_BYTE: Cost = 10; + pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); sha256.update([1]); @@ -122,8 +131,85 @@ impl TreeCache { } } -pub(crate) enum TreeOp { +enum TreeOp { SExp(NodePtr), Cons, ConsAddCacheCost(NodePtr, Cost), } + +pub fn tree_hash_cached_costed( + a: &mut Allocator, + node: NodePtr, + cache: &mut TreeCache, + cost_left: Cost, +) -> Response { + cache.visit_tree(a, node); + + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; + let mut cost: Cost = SHA256TREE_BASE_COST; + + while let Some(op) = ops.pop() { + // charge a call cost for processing this op + cost += SHA256TREE_COST_PER_CALL; + check_cost(cost, cost_left)?; + match op { + TreeOp::SExp(node) => match a.node(node) { + NodeVisitor::Buffer(bytes) => { + cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; + check_cost(cost, cost_left)?; + let hash = tree_hash_atom(bytes); + hashes.push(hash); + } + NodeVisitor::U32(val) => { + cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; + check_cost(cost, cost_left)?; + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); + } + } + NodeVisitor::Pair(left, right) => { + // pair cost (65 bytes as before) + cost += SHA256TREE_COST_PER_BYTE * 65_u64; + check_cost(cost, cost_left)?; + if let Some((hash, cached_cost)) = cache.get(node) { + // when reusing a cached subtree, charge its cached cost + cost += cached_cost; + check_cost(cost, cost_left)?; + hashes.push(*hash); + } else { + if cache.should_memoize(node) { + // record the cost_left before traversing this subtree + ops.push(TreeOp::ConsAddCacheCost(node, cost)); + } else { + ops.push(TreeOp::Cons); + } + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + } + }, + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(tree_hash_pair(first, rest)); + } + TreeOp::ConsAddCacheCost(original_node, cost_before) => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + let hash = tree_hash_pair(first, rest); + hashes.push(hash); + // cost_before will be lower + // cost_left is the remaining after computing it + // the cost of this subtree = after - before + let used = cost - cost_before; + cache.insert(original_node, &hash, used); + } + } + } + + assert!(hashes.len() == 1); + Ok(Reduction(cost, a.new_atom(&hashes[0])?)) +} From 3142cd6619abee8b5bf2b40ec3c4baf4ed63926e Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 28 Oct 2025 11:54:22 +0000 Subject: [PATCH 014/110] add malloc_cost_per_byte --- src/treehash.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/treehash.rs b/src/treehash.rs index b50ed4a4a..d71744fe3 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -3,6 +3,7 @@ use crate::allocator::{Allocator, NodePtr}; use crate::cost::check_cost; use crate::cost::Cost; use crate::more_ops::PRECOMPUTED_HASHES; +use crate::op_utils::MALLOC_COST_PER_BYTE; use crate::reduction::Reduction; use crate::reduction::Response; use crate::ObjectType; @@ -211,5 +212,7 @@ pub fn tree_hash_cached_costed( } assert!(hashes.len() == 1); + cost += MALLOC_COST_PER_BYTE * 32; + check_cost(cost, cost_left)?; Ok(Reduction(cost, a.new_atom(&hashes[0])?)) } From 1cfa2045b75cb112451a992626d1075ef51d32ac Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 28 Oct 2025 14:02:24 +0000 Subject: [PATCH 015/110] comment fixes --- src/chia_dialect.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index d1f59bb38..0cce2c68e 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -30,9 +30,12 @@ pub const LIMIT_HEAP: u32 = 0x0004; // This is a hard-fork and should only be enabled when it activates pub const ENABLE_KECCAK_OPS_OUTSIDE_GUARD: u32 = 0x0100; + +// this flag enables the sha256tree op *outside* the softfork guard. +// This is a hard-fork and should only be enabled when it activates. pub const ENABLE_SHA256_TREE: u32 = 0x0200; -// The default mode when running grnerators in mempool-mode (i.e. the stricter +// The default mode when running generators in mempool-mode (i.e. the stricter // mode) pub const MEMPOOL_MODE: u32 = NO_UNKNOWN_OPS | LIMIT_HEAP; From 94aa383db684bcb0b3e2edfab45c9014a497284e Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 09:57:40 +0000 Subject: [PATCH 016/110] generate random tests and fix subtle costing bug --- op-tests/test-sha256tree-hash.txt | 32 +++++++++++ op-tests/test-sha256tree.txt | 10 ++-- src/chia_dialect.rs | 1 - src/test_ops.rs | 1 + src/treehash.rs | 69 ++++++++++++------------ tools/generate-sha256tree-tests.py | 87 ++++++++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 40 deletions(-) create mode 100644 op-tests/test-sha256tree-hash.txt create mode 100644 tools/generate-sha256tree-tests.py diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt new file mode 100644 index 000000000..0e0ae1f68 --- /dev/null +++ b/op-tests/test-sha256tree-hash.txt @@ -0,0 +1,32 @@ +; This file was generated by tools/generate-sha256tree-tests.py + +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 59110 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 11860 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1940 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 11970 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 14880 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 8670 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 2100 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 22030 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 49000 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 2100 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 25410 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 5120 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 12500 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 2100 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 44720 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 8140 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 5200 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 39360 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 8370 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 15070 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 2100 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 8610 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 5110 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 1680 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 18540 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 43070 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 29150 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 5670 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 8190 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 5958074cf..d19c73c24 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,5 +1,5 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1310 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1300 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 5910 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 19570 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 24120 \ No newline at end of file +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1620 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 4930 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 14690 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 17940 \ No newline at end of file diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index 0cce2c68e..7f1ab7414 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -30,7 +30,6 @@ pub const LIMIT_HEAP: u32 = 0x0004; // This is a hard-fork and should only be enabled when it activates pub const ENABLE_KECCAK_OPS_OUTSIDE_GUARD: u32 = 0x0100; - // this flag enables the sha256tree op *outside* the softfork guard. // This is a hard-fork and should only be enabled when it activates. pub const ENABLE_SHA256_TREE: u32 = 0x0200; diff --git a/src/test_ops.rs b/src/test_ops.rs index 7cc5db382..7f83e2337 100644 --- a/src/test_ops.rs +++ b/src/test_ops.rs @@ -267,6 +267,7 @@ mod tests { #[case("test-modpow")] #[case("test-sha256")] #[case("test-sha256tree")] + #[case("test-sha256tree-hash")] #[case("test-keccak256")] #[case("test-keccak256-generated")] fn test_ops(#[case] filename: &str) { diff --git a/src/treehash.rs b/src/treehash.rs index d71744fe3..66a8adf0d 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -151,47 +151,48 @@ pub fn tree_hash_cached_costed( let mut cost: Cost = SHA256TREE_BASE_COST; while let Some(op) = ops.pop() { - // charge a call cost for processing this op - cost += SHA256TREE_COST_PER_CALL; - check_cost(cost, cost_left)?; match op { - TreeOp::SExp(node) => match a.node(node) { - NodeVisitor::Buffer(bytes) => { - cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; - check_cost(cost, cost_left)?; - let hash = tree_hash_atom(bytes); - hashes.push(hash); - } - NodeVisitor::U32(val) => { - cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; - check_cost(cost, cost_left)?; - if (val as usize) < PRECOMPUTED_HASHES.len() { - hashes.push(PRECOMPUTED_HASHES[val as usize]); - } else { - hashes.push(tree_hash_atom(a.atom(node).as_ref())); + TreeOp::SExp(node) => { + // charge a call cost for processing this op + cost += SHA256TREE_COST_PER_CALL; + check_cost(cost, cost_left)?; + match a.node(node) { + NodeVisitor::Buffer(bytes) => { + cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; + check_cost(cost, cost_left)?; + let hash = tree_hash_atom(bytes); + hashes.push(hash); } - } - NodeVisitor::Pair(left, right) => { - // pair cost (65 bytes as before) - cost += SHA256TREE_COST_PER_BYTE * 65_u64; - check_cost(cost, cost_left)?; - if let Some((hash, cached_cost)) = cache.get(node) { - // when reusing a cached subtree, charge its cached cost - cost += cached_cost; + NodeVisitor::U32(val) => { + cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; + check_cost(cost, cost_left)?; + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); + } + } + NodeVisitor::Pair(left, right) => { + cost += SHA256TREE_COST_PER_BYTE * 65_u64; check_cost(cost, cost_left)?; - hashes.push(*hash); - } else { - if cache.should_memoize(node) { - // record the cost_left before traversing this subtree - ops.push(TreeOp::ConsAddCacheCost(node, cost)); + if let Some((hash, cached_cost)) = cache.get(node) { + // when reusing a cached subtree, charge its cached cost + cost += cached_cost; + check_cost(cost, cost_left)?; + hashes.push(*hash); } else { - ops.push(TreeOp::Cons); + if cache.should_memoize(node) { + // record the cost before traversing this subtree + ops.push(TreeOp::ConsAddCacheCost(node, cost)); + } else { + ops.push(TreeOp::Cons); + } + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); } - ops.push(TreeOp::SExp(left)); - ops.push(TreeOp::SExp(right)); } } - }, + } TreeOp::Cons => { let first = hashes.pop().unwrap(); let rest = hashes.pop().unwrap(); diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py new file mode 100644 index 000000000..2404bbce7 --- /dev/null +++ b/tools/generate-sha256tree-tests.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +from hashlib import sha256 +from random import randbytes, choice, randint, seed +import sys + +seed(1337) + +SHA256TREE_BASE_COST = 0 +SHA256TREE_COST_PER_CALL = 1300 +SHA256TREE_COST_PER_BYTE = 10 +MALLOC_COST_PER_BYTE = 10 + +SIZE = 30 + + +def tree_hash_atom(b: bytes) -> bytes: + h = sha256() + h.update(b"\x01") + h.update(b) + return h.digest() + +def tree_hash_pair(left_hash: bytes, right_hash: bytes) -> bytes: + h = sha256() + h.update(b"\x02") + h.update(left_hash) + h.update(right_hash) + return h.digest() + +def random_atom() -> bytes: + return choice([ + b"", + b"\x01", + b"\x02", + b"foobar", + randbytes(24), + randbytes(32), + randbytes(48) + ]) + +def random_tree(depth: int) -> object: + """Recursively generate random nested pairs (tuples) or atoms""" + if depth == 0 or randint(0, 2) == 0: + return random_atom() + else: + return (random_tree(depth - 1), random_tree(depth - 1)) + + +def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: + """Return (hash, cost) similar to tree_hash_cached_costed""" + cost = SHA256TREE_COST_PER_CALL + if isinstance(obj, bytes): + cost += SHA256TREE_COST_PER_BYTE * len(obj) + return tree_hash_atom(obj), cost + else: + left, right = obj + cost += SHA256TREE_COST_PER_BYTE * 65 + left_hash, left_cost = compute_tree_hash_and_cost(left) + right_hash, right_cost = compute_tree_hash_and_cost(right) + return tree_hash_pair(left_hash, right_hash), cost + left_cost + right_cost + + +def sexp_repr(obj) -> str: + """Turn the structure into a Lisp-like representation""" + if isinstance(obj, bytes): + if len(obj) == 0: + return "0" + return f"0x{obj.hex()}" + else: + left, right = obj + return f"({sexp_repr(left)} . {sexp_repr(right)})" + + +test_cases = set() +with open("../op-tests/test-sha256tree-hash.txt", "w") as f: + f.write("; This file was generated by tools/generate-sha256tree-tests.py\n\n") + + for _ in range(SIZE): + depth = choice(range(1, 6)) + t = random_tree(depth) + s = sexp_repr(t) + if s in test_cases: + continue + test_cases.add(s) + h, cost = compute_tree_hash_and_cost(t) + cost += MALLOC_COST_PER_BYTE * 32 + f.write(f"sha256tree {s} => 0x{h.hex()} | {cost}\n") + From 8b569fbd9dd445a4c8001e40f523ca40f1dfe5f6 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 15:31:09 +0000 Subject: [PATCH 017/110] add treecache tests --- src/treehash.rs | 314 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) diff --git a/src/treehash.rs b/src/treehash.rs index 66a8adf0d..5dca1394f 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -217,3 +217,317 @@ pub fn tree_hash_cached_costed( check_cost(cost, cost_left)?; Ok(Reduction(cost, a.new_atom(&hashes[0])?)) } + +#[cfg(test)] +mod tests { + use crate::test_ops::node_eq; + + use super::*; + + // this function neither costs, nor caches + // and it also returns bytes, rather than an Atom + pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; + + while let Some(op) = ops.pop() { + match op { + TreeOp::SExp(node) => match a.node(node) { + NodeVisitor::Buffer(bytes) => { + hashes.push(tree_hash_atom(bytes)); + } + NodeVisitor::U32(val) => { + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); + } + } + NodeVisitor::Pair(left, right) => { + ops.push(TreeOp::Cons); + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + }, + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(tree_hash_pair(first, rest)); + } + TreeOp::ConsAddCacheCost(_, _) => unreachable!(), + } + } + + assert!(hashes.len() == 1); + hashes[0] + } + + // this function costs but does not cache + // we can use it to check that the cache is properly remembering costs + fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Response { + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; + + let mut cost = SHA256TREE_BASE_COST; + + while let Some(op) = ops.pop() { + match op { + TreeOp::SExp(node) => { + cost += SHA256TREE_COST_PER_CALL; + check_cost(cost, cost_left)?; + match a.node(node) { + NodeVisitor::Buffer(bytes) => { + cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; + check_cost(cost, cost_left)?; + + let hash = tree_hash_atom(bytes); + hashes.push(hash); + } + NodeVisitor::U32(val) => { + cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; + check_cost(cost, cost_left)?; + + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); + } + } + NodeVisitor::Pair(left, right) => { + cost += SHA256TREE_COST_PER_BYTE * 65; + check_cost(cost, cost_left)?; + + ops.push(TreeOp::Cons); + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + } + } + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(tree_hash_pair(first, rest)); + } + TreeOp::ConsAddCacheCost(_, _) => unreachable!(), + } + } + + assert!(hashes.len() == 1); + cost += MALLOC_COST_PER_BYTE * 32; + check_cost(cost, cost_left)?; + Ok(Reduction(cost, a.new_atom(&hashes[0])?)) + } + + fn test_sha256_atom(buf: &[u8]) { + let hash = tree_hash_atom(buf); + + let mut hasher = Sha256::new(); + hasher.update([1_u8]); + if !buf.is_empty() { + hasher.update(buf); + } + + assert_eq!(hash.as_ref(), hasher.finalize().as_slice()); + } + + #[test] + fn test_tree_hash_atom() { + test_sha256_atom(&[]); + for val in 0..=255 { + test_sha256_atom(&[val]); + } + + for val in 0..=255 { + test_sha256_atom(&[0, val]); + } + + for val in 0..=255 { + test_sha256_atom(&[0xff, val]); + } + } + + #[test] + fn test_precomputed_atoms() { + assert_eq!(tree_hash_atom(&[]), PRECOMPUTED_HASHES[0]); + for val in 1..(PRECOMPUTED_HASHES.len() as u8) { + assert_eq!(tree_hash_atom(&[val]), PRECOMPUTED_HASHES[val as usize]); + } + } + + #[test] + fn test_tree_cache_get() { + let mut allocator = Allocator::new(); + let mut cache = TreeCache::default(); + + let a = allocator.nil(); + let b = allocator.one(); + let c = allocator.new_pair(a, b).expect("new_pair"); + + assert_eq!(cache.get(a), None); + assert_eq!(cache.get(b), None); + assert_eq!(cache.get(c), None); + + // We don't cache atoms + cache.insert(a, &tree_hash(&mut allocator, a), 0); + assert_eq!(cache.get(a), None); + + cache.insert(b, &tree_hash(&mut allocator, b), 0); + assert_eq!(cache.get(b), None); + + // but pair is OK + cache.insert(c, &tree_hash(&mut allocator, c), 0); + let (h, _c) = cache.get(c).expect("expected cached pair"); + assert_eq!(h, &tree_hash(&mut allocator, c)); + } + + #[test] + fn test_tree_cache_size_limit() { + let mut allocator = Allocator::new(); + let mut cache = TreeCache::default(); + + let mut list = allocator.nil(); + let mut hash = tree_hash(&mut allocator, list); + cache.insert(list, &hash, 0); + + // we only fit 65k items in the cache + for i in 0..65540 { + let b = allocator.one(); + list = allocator.new_pair(b, list).expect("new_pair"); + hash = tree_hash_pair(tree_hash_atom(b"\x01"), hash); + cache.insert(list, &hash, 0); + + println!("{i}"); + if i < 65533 { + let (h, _c) = cache.get(list).expect("expected cached"); + assert_eq!(h, &hash); + } else { + assert_eq!(cache.get(list), None); + } + } + assert_eq!(cache.get(list), None); + } + + #[test] + fn test_tree_cache_should_memoize() { + let mut allocator = Allocator::new(); + let mut cache = TreeCache::default(); + + let a = allocator.nil(); + let b = allocator.one(); + let c = allocator.new_pair(a, b).expect("new_pair"); + + assert!(!cache.should_memoize(a)); + assert!(!cache.should_memoize(b)); + assert!(!cache.should_memoize(c)); + + // we need to visit a node at least twice for it to be considered a + // candidate for memoization + assert!(cache.visit(c)); + assert!(!cache.should_memoize(c)); + assert!(!cache.visit(c)); + + assert!(cache.should_memoize(c)); + } + + #[test] + fn test_tree_hash_costed_equivalence_no_repeats() { + let mut a = Allocator::new(); + + // Build a nested tree: + // ((a . b) . ((x . y) . (z . w))) + let a_atom = a.new_atom(b"a").unwrap(); + let b_atom = a.new_atom(b"b").unwrap(); + let x_atom = a.new_atom(b"x").unwrap(); + let y_atom = a.new_atom(b"y").unwrap(); + let z_atom = a.new_atom(b"z").unwrap(); + let w_atom = a.new_atom(b"w").unwrap(); + + let ab_pair = a.new_pair(a_atom, b_atom).unwrap(); + let xy_pair = a.new_pair(x_atom, y_atom).unwrap(); + let zw_pair = a.new_pair(z_atom, w_atom).unwrap(); + let right_pair = a.new_pair(xy_pair, zw_pair).unwrap(); + let root = a.new_pair(ab_pair, right_pair).unwrap(); + + let cost_left_baseline = 1_000_000; + let cost_left_cached = 1_000_000; + + // baseline: costed but no caching + let baseline = tree_hash_costed(&mut a, root, cost_left_baseline).unwrap(); + + // cached version + let mut cache = TreeCache::default(); + cache.visit_tree(&a, root); + + let cached = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached).unwrap(); + + assert!( + !cache.hashes.is_empty(), + "cache should contain memoized subtrees" + ); + + assert_eq!( + baseline.0, cached.0, + "cost mismatch between costed and cached" + ); + assert!(node_eq(&a, baseline.1, cached.1)); + + // the number of cached hashes and costs must match + assert_eq!(cache.hashes.len(), cache.costs.len()); + + // if we re-run with cache, cost_left should still match the baseline + let cost_left_cached2 = 1_000_000; + let cached2 = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached2).unwrap(); + + assert_eq!( + cached2.0, cached.0, + "cost mismatch between costed and cached" + ); + assert!(node_eq(&a, cached2.1, cached.1)); + } + + #[test] + fn test_tree_hash_cost_equivalence_with_repeats() { + let mut a = Allocator::new(); + let x_atom = a.new_atom(b"x").unwrap(); + let y_atom = a.new_atom(b"y").unwrap(); + let r = a.new_pair(x_atom, y_atom).unwrap(); + + let left = a.new_pair(r, r).unwrap(); + let right = a.new_pair(r, r).unwrap(); + let root = a.new_pair(left, right).unwrap(); + + // Nest it one level deeper: + let root = a.new_pair(root, root).unwrap(); + + let cost_left_baseline = 10_000_000; + let cost_left_cached = 10_000_000; + + let baseline = tree_hash_costed(&mut a, root, cost_left_baseline).unwrap(); + + let mut cache = TreeCache::default(); + cache.visit_tree(&a, root); + + let cached = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached).unwrap(); + + assert!( + !cache.hashes.is_empty(), + "cache should contain memoized subtrees" + ); + + assert_eq!( + baseline.0, cached.0, + "cost mismatch between costed and cached" + ); + assert!(node_eq(&a, baseline.1, cached.1)); + + // run again with same cache — should still match cost and hash + let cost_left_cached2 = 10_000_000; + let cached2 = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached2).unwrap(); + + assert_eq!( + cached2.0, cached.0, + "cost mismatch between costed and cached" + ); + assert!(node_eq(&a, cached2.1, cached.1)); + } +} From 27c57c00097d208d7e9f7fb3da102af0a83c9e45 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 15:50:47 +0000 Subject: [PATCH 018/110] clippy fixes --- src/treehash.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 5dca1394f..2265772f4 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -368,16 +368,16 @@ mod tests { assert_eq!(cache.get(c), None); // We don't cache atoms - cache.insert(a, &tree_hash(&mut allocator, a), 0); + cache.insert(a, &tree_hash(& allocator, a), 0); assert_eq!(cache.get(a), None); - cache.insert(b, &tree_hash(&mut allocator, b), 0); + cache.insert(b, &tree_hash(& allocator, b), 0); assert_eq!(cache.get(b), None); // but pair is OK - cache.insert(c, &tree_hash(&mut allocator, c), 0); + cache.insert(c, &tree_hash(& allocator, c), 0); let (h, _c) = cache.get(c).expect("expected cached pair"); - assert_eq!(h, &tree_hash(&mut allocator, c)); + assert_eq!(h, &tree_hash(& allocator, c)); } #[test] @@ -386,7 +386,7 @@ mod tests { let mut cache = TreeCache::default(); let mut list = allocator.nil(); - let mut hash = tree_hash(&mut allocator, list); + let mut hash = tree_hash(& allocator, list); cache.insert(list, &hash, 0); // we only fit 65k items in the cache From 0976bc345d9f21b65e9a633df067b71079bcd2af Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 15:53:17 +0000 Subject: [PATCH 019/110] fmt again --- src/treehash.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 2265772f4..0e5b86b92 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -368,16 +368,16 @@ mod tests { assert_eq!(cache.get(c), None); // We don't cache atoms - cache.insert(a, &tree_hash(& allocator, a), 0); + cache.insert(a, &tree_hash(&allocator, a), 0); assert_eq!(cache.get(a), None); - cache.insert(b, &tree_hash(& allocator, b), 0); + cache.insert(b, &tree_hash(&allocator, b), 0); assert_eq!(cache.get(b), None); // but pair is OK - cache.insert(c, &tree_hash(& allocator, c), 0); + cache.insert(c, &tree_hash(&allocator, c), 0); let (h, _c) = cache.get(c).expect("expected cached pair"); - assert_eq!(h, &tree_hash(& allocator, c)); + assert_eq!(h, &tree_hash(&allocator, c)); } #[test] @@ -386,7 +386,7 @@ mod tests { let mut cache = TreeCache::default(); let mut list = allocator.nil(); - let mut hash = tree_hash(& allocator, list); + let mut hash = tree_hash(&allocator, list); cache.insert(list, &hash, 0); // we only fit 65k items in the cache From f777a13f6b0538a1a2846c45329cbb8b8834d35a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 16:14:51 +0000 Subject: [PATCH 020/110] add negative tests and more atom tests --- op-tests/test-sha256tree.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index d19c73c24..8a82ae8e6 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,5 +1,9 @@ sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 +sha256tree 0x00cafef00d => 60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1670 sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1620 sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 4930 sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 14690 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 17940 \ No newline at end of file +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 17940 +sha256tree 10 20 => FAIL +sha256tree (10 . 20) 30 => FAIL +sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file From 7efd8d406c5ce75773fc89b150442d884c4adab4 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 16:18:55 +0000 Subject: [PATCH 021/110] test fix --- op-tests/test-sha256tree.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 8a82ae8e6..4758a6bb5 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,5 +1,5 @@ sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 -sha256tree 0x00cafef00d => 60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1670 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1670 sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1620 sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 4930 sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 14690 From c676a34f0d4a3e73f940c1e2c56114a4efcf848b Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 29 Oct 2025 20:35:15 +0000 Subject: [PATCH 022/110] pass ref into pair --- src/treehash.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 0e5b86b92..64de16723 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -21,7 +21,7 @@ pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { sha256.finalize() } -pub fn tree_hash_pair(first: [u8; 32], rest: [u8; 32]) -> [u8; 32] { +pub fn tree_hash_pair(first: &[u8; 32], rest: &[u8; 32]) -> [u8; 32] { let mut sha256 = Sha256::new(); sha256.update([2]); sha256.update(first); @@ -196,12 +196,12 @@ pub fn tree_hash_cached_costed( TreeOp::Cons => { let first = hashes.pop().unwrap(); let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(first, rest)); + hashes.push(tree_hash_pair(&first, &rest)); } TreeOp::ConsAddCacheCost(original_node, cost_before) => { let first = hashes.pop().unwrap(); let rest = hashes.pop().unwrap(); - let hash = tree_hash_pair(first, rest); + let hash = tree_hash_pair(&first, &rest); hashes.push(hash); // cost_before will be lower // cost_left is the remaining after computing it @@ -252,7 +252,7 @@ mod tests { TreeOp::Cons => { let first = hashes.pop().unwrap(); let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(first, rest)); + hashes.push(tree_hash_pair(&first, &rest)); } TreeOp::ConsAddCacheCost(_, _) => unreachable!(), } @@ -306,7 +306,7 @@ mod tests { TreeOp::Cons => { let first = hashes.pop().unwrap(); let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(first, rest)); + hashes.push(tree_hash_pair(&first, &rest)); } TreeOp::ConsAddCacheCost(_, _) => unreachable!(), } @@ -393,7 +393,7 @@ mod tests { for i in 0..65540 { let b = allocator.one(); list = allocator.new_pair(b, list).expect("new_pair"); - hash = tree_hash_pair(tree_hash_atom(b"\x01"), hash); + hash = tree_hash_pair(&tree_hash_atom(b"\x01"), &hash); cache.insert(list, &hash, 0); println!("{i}"); From 52fb0ba0235b178e526257dce823d7592cdb581f Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 30 Oct 2025 16:14:54 +0000 Subject: [PATCH 023/110] add sha256tree to bechmarker --- tools/src/bin/benchmark-clvm-cost.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 435423649..13c3857b5 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -118,7 +118,7 @@ fn substitute(args: Placeholder, s: NodePtr) -> OpArgs { fn time_invocation(a: &mut Allocator, op: u32, arg: OpArgs, flags: u32) -> f64 { let call = build_call(a, op, arg, 1, None); //println!("{:x?}", &Node::new(a, call)); - let dialect = ChiaDialect::new(0); + let dialect = ChiaDialect::new(0x0200); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11000000000); if (flags & ALLOW_FAILURE) == 0 { @@ -378,7 +378,7 @@ pub fn main() { .unwrap(); let number = quote(&mut a, number); - let ops: [Operator; 19] = [ + let ops: [Operator; 20] = [ Operator { opcode: 60, name: "modpow (modulus cost)", @@ -512,6 +512,13 @@ pub fn main() { extra: None, flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, }, + Operator { + opcode: 65, + name: "sha256tree (atom)", + arg: Placeholder::SingleArg(Some(g1)), + extra: None, + flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, + }, ]; // this "magic" scaling depends on the computer you run the tests on. From 90acc78b8fd5e936d76b88c960d49a1c3f7dd898 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 3 Nov 2025 10:36:26 +0000 Subject: [PATCH 024/110] add flag to wheel --- wheel/python/clvm_rs/clvm_rs.pyi | 1 + wheel/src/api.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/wheel/python/clvm_rs/clvm_rs.pyi b/wheel/python/clvm_rs/clvm_rs.pyi index 936c3e860..da6ce8d07 100644 --- a/wheel/python/clvm_rs/clvm_rs.pyi +++ b/wheel/python/clvm_rs/clvm_rs.pyi @@ -14,6 +14,7 @@ NO_NEG_DIV: int NO_UNKNOWN_OPS: int LIMIT_HEAP: int MEMPOOL_MODE: int +ENABLE_SHA256_TREE: int class LazyNode(CLVMStorage): atom: Optional[bytes] diff --git a/wheel/src/api.rs b/wheel/src/api.rs index 1081e0aa4..633778e22 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -10,7 +10,7 @@ use clvmr::error::EvalErr; use clvmr::reduction::Response; use clvmr::run_program::run_program; use clvmr::serde::{node_from_bytes, parse_triples, serialized_length_from_bytes, ParsedTriple}; -use clvmr::{LIMIT_HEAP, MEMPOOL_MODE, NO_UNKNOWN_OPS}; +use clvmr::{LIMIT_HEAP, MEMPOOL_MODE, NO_UNKNOWN_OPS, ENABLE_SHA256_TREE}; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyTuple}; use pyo3::wrap_pyfunction; @@ -91,6 +91,7 @@ fn clvm_rs(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add("NO_UNKNOWN_OPS", NO_UNKNOWN_OPS)?; m.add("LIMIT_HEAP", LIMIT_HEAP)?; m.add("MEMPOOL_MODE", MEMPOOL_MODE)?; + m.add("ENABLE_SHA256_TREE", ENABLE_SHA256_TREE)?; m.add_class::()?; Ok(()) From 51915457334ff2f56d21957b8e67ff0c5942816c Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 3 Nov 2025 10:41:47 +0000 Subject: [PATCH 025/110] use Pathlib for python code --- tools/generate-keccak-tests.py | 4 +++- tools/generate-secp256k1-tests.py | 4 +++- tools/generate-secp256r1-tests.py | 5 +++-- tools/generate-sha256-tests.py | 4 +++- tools/generate-sha256tree-tests.py | 4 +++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/tools/generate-keccak-tests.py b/tools/generate-keccak-tests.py index 1c14fa57d..5e8cb56e1 100644 --- a/tools/generate-keccak-tests.py +++ b/tools/generate-keccak-tests.py @@ -1,3 +1,4 @@ +from pathlib import Path from eth_hash.auto import keccak from random import randbytes, randint, seed, sample from more_itertools import sliced @@ -9,7 +10,8 @@ SIZE = 100 -with open("../op-tests/test-keccak256-generated.txt", "w+") as f: +p = Path(__file__).parent.parent / "op-tests/test-keccak256-generated.txt" +with open(p, "w+") as f: f.write("; This file was generated by tools/generate-keccak-tests.py\n\n") for i in range(SIZE): diff --git a/tools/generate-secp256k1-tests.py b/tools/generate-secp256k1-tests.py index d9b83ef40..a0df23e22 100644 --- a/tools/generate-secp256k1-tests.py +++ b/tools/generate-secp256k1-tests.py @@ -1,3 +1,4 @@ +from pathlib import Path from secp256k1 import PublicKey, PrivateKey from hashlib import sha256 from random import randbytes, randint, seed, sample @@ -39,7 +40,8 @@ def print_validation_test_case(f, num_cases, filter_pk, filter_msg, filter_sig, secret_keys.append(PrivateKey()) -with open("../op-tests/test-secp256k1.txt", "w+") as f: +p = Path(__file__).parent.parent / "op-tests/test-secp256k1.txt" +with open(p, "w+") as f: f.write("; This file was generated by tools/generate-secp256k1-tests.py\n\n") print_validation_test_case(f, SIZE, lambda pk: pk, lambda msg: msg, lambda sig: sig, "0") diff --git a/tools/generate-secp256r1-tests.py b/tools/generate-secp256r1-tests.py index 70c497e7d..76d644a37 100644 --- a/tools/generate-secp256r1-tests.py +++ b/tools/generate-secp256r1-tests.py @@ -1,3 +1,4 @@ +from pathlib import Path from ecdsa import SigningKey, NIST256p from hashlib import sha256 from random import randbytes, randint, seed, sample @@ -38,8 +39,8 @@ def print_validation_test_case(f, num_cases, filter_pk, filter_msg, filter_sig, for i in range(SIZE): secret_keys.append(SigningKey.generate(curve=NIST256p, hashfunc=sha256)) - -with open("../op-tests/test-secp256r1.txt", "w+") as f: +p = Path(__file__).parent.parent / "op-tests/test-secp256r1.txt" +with open(p, "w+") as f: f.write("; This file was generated by tools/generate-secp256r1-tests.py\n\n") print_validation_test_case(f, SIZE, lambda pk: pk, lambda msg: msg, lambda sig: sig, "0") diff --git a/tools/generate-sha256-tests.py b/tools/generate-sha256-tests.py index 017a623a9..06445d830 100644 --- a/tools/generate-sha256-tests.py +++ b/tools/generate-sha256-tests.py @@ -1,3 +1,4 @@ +from pathlib import Path from random import randbytes, randint, seed, choice from hashlib import sha256 @@ -6,7 +7,8 @@ test_cases = set() -with open("../op-tests/test-sha256.txt", "w+") as f: +p = Path(__file__).parent.parent / "op-tests/test-sha256.txt" +with open(p, "w+") as f: f.write("; This file was generated by tools/generate-sha256-tests.py\n\n") for i in range(0, SIZE): diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index 2404bbce7..b0e8e7f42 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 from hashlib import sha256 +from pathlib import Path from random import randbytes, choice, randint, seed import sys @@ -71,7 +72,8 @@ def sexp_repr(obj) -> str: test_cases = set() -with open("../op-tests/test-sha256tree-hash.txt", "w") as f: +p = Path(__file__).parent.parent / "op-tests/test-sha256tree-hash.txt" +with open(p, "w") as f: f.write("; This file was generated by tools/generate-sha256tree-tests.py\n\n") for _ in range(SIZE): From c8d71da928172a1584996b0a3c49f41c6afe4f4a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 3 Nov 2025 10:53:42 +0000 Subject: [PATCH 026/110] export ENABLE_SHA256_TREE in lib --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 11f936781..053a861ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,7 +23,9 @@ pub use allocator::{Allocator, Atom, NodePtr, ObjectType, SExp}; pub use chia_dialect::ChiaDialect; pub use run_program::run_program; -pub use chia_dialect::{ENABLE_KECCAK_OPS_OUTSIDE_GUARD, LIMIT_HEAP, MEMPOOL_MODE, NO_UNKNOWN_OPS}; +pub use chia_dialect::{ + ENABLE_KECCAK_OPS_OUTSIDE_GUARD, ENABLE_SHA256_TREE, LIMIT_HEAP, MEMPOOL_MODE, NO_UNKNOWN_OPS, +}; #[cfg(feature = "counters")] pub use run_program::run_program_with_counters; From 9c70ecfc58b877470097f447f89dc4bbe486dbaa Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 3 Nov 2025 11:01:49 +0000 Subject: [PATCH 027/110] fmt --- wheel/src/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheel/src/api.rs b/wheel/src/api.rs index 633778e22..c68dd22ea 100644 --- a/wheel/src/api.rs +++ b/wheel/src/api.rs @@ -10,7 +10,7 @@ use clvmr::error::EvalErr; use clvmr::reduction::Response; use clvmr::run_program::run_program; use clvmr::serde::{node_from_bytes, parse_triples, serialized_length_from_bytes, ParsedTriple}; -use clvmr::{LIMIT_HEAP, MEMPOOL_MODE, NO_UNKNOWN_OPS, ENABLE_SHA256_TREE}; +use clvmr::{ENABLE_SHA256_TREE, LIMIT_HEAP, MEMPOOL_MODE, NO_UNKNOWN_OPS}; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyTuple}; use pyo3::wrap_pyfunction; From 42a28fe58c4e26266803e5a8df91d8d60be69497 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 6 Nov 2025 17:21:48 +0000 Subject: [PATCH 028/110] add benching for shatree --- tools/src/bin/sha256tree-benching.rs | 88 ++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 tools/src/bin/sha256tree-benching.rs diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs new file mode 100644 index 000000000..942f2a8e2 --- /dev/null +++ b/tools/src/bin/sha256tree-benching.rs @@ -0,0 +1,88 @@ +use std::fs::File; +use std::io::Write; +use std::time::Instant; + +use linreg::linear_regression_of; + +// bring in your existing code +use clvmr::allocator::{Allocator, NodePtr}; +use clvmr::treehash::{tree_hash, tree_hash_atom}; + +fn make_nested_pairs(a: &mut Allocator, depth: usize) -> NodePtr { + let mut node = a.nil(); + for _ in 0..depth { + node = a.new_pair(node, a.nil()).unwrap(); + } + node +} + +fn make_list_of_atoms(a: &mut Allocator, n: usize) -> NodePtr { + let atom = a.new_atom(&[1u8; 32]).unwrap(); + let mut list = a.nil(); + for _ in 0..n { + list = a.new_pair(atom, list).unwrap(); + } + list +} + +fn main() -> std::io::Result<()> { + let mut output = File::create("sha256tree_costs.tsv")?; + + writeln!(output, "# type\tx\ty")?; + + // cost call (with nil) + let mut samples = vec![]; + { + let mut a = Allocator::new(); + for depth in 1..100 { + let node = make_nested_pairs(&mut a, depth); + let start = Instant::now(); + tree_hash(&a, node); // using tree hash as it costs the same as cached + let t = start.elapsed().as_secs_f64(); + writeln!(output, "call\t{}\t{}", depth, t)?; + samples.push((depth as f64, t)); + } + let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + writeln!(output, "\n# call_slope\t{:.9}", slope)?; + writeln!(output, "# call_intercept\t{:.9}\n", intercept)?; + println!("call slope: {:.9}, intercept: {:.9}", slope, intercept); + } + + // cost atom sizes + let mut samples = vec![]; + { + for size in [1, 8, 16, 64, 256, 1024, 4096, 8192] { + let atom = vec![0u8; size]; + let start = Instant::now(); + tree_hash_atom(&atom); + let t = start.elapsed().as_secs_f64(); + writeln!(output, "atom\t{}\t{}", size, t)?; + samples.push((size as f64, t)); + } + let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + writeln!(output, "\n# atom_slope\t{:.9}", slope)?; + writeln!(output, "# atom_intercept\t{:.9}\n", intercept)?; + println!("atom slope: {:.9}, intercept: {:.9}", slope, intercept); + } + + // cost list of atoms + let mut samples = vec![]; + { + let mut a = Allocator::new(); + for n in [1, 2, 4, 8, 16, 32, 64, 128, 256] { + let node = make_list_of_atoms(&mut a, n); + let start = Instant::now(); + tree_hash(&a, node); + let t = start.elapsed().as_secs_f64(); + writeln!(output, "pair\t{}\t{}", n, t)?; + samples.push((n as f64, t)); + } + let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + writeln!(output, "\n# pair_slope\t{:.9}", slope)?; + writeln!(output, "# pair_intercept\t{:.9}\n", intercept)?; + println!("pair slope: {:.9}, intercept: {:.9}", slope, intercept); + } + + println!("Results written to sha256tree_costs.tsv"); + Ok(()) +} \ No newline at end of file From ca2fea53af3d6574b962f73190689baed7261a12 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 6 Nov 2025 17:22:01 +0000 Subject: [PATCH 029/110] make treehash pub --- src/treehash.rs | 74 ++++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 64de16723..d602ad421 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -218,50 +218,50 @@ pub fn tree_hash_cached_costed( Ok(Reduction(cost, a.new_atom(&hashes[0])?)) } -#[cfg(test)] -mod tests { - use crate::test_ops::node_eq; - - use super::*; - - // this function neither costs, nor caches - // and it also returns bytes, rather than an Atom - pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { - let mut hashes = Vec::new(); - let mut ops = vec![TreeOp::SExp(node)]; +// this function neither costs, nor caches +// and it also returns bytes, rather than an Atom +pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; - while let Some(op) = ops.pop() { - match op { - TreeOp::SExp(node) => match a.node(node) { - NodeVisitor::Buffer(bytes) => { - hashes.push(tree_hash_atom(bytes)); - } - NodeVisitor::U32(val) => { - if (val as usize) < PRECOMPUTED_HASHES.len() { - hashes.push(PRECOMPUTED_HASHES[val as usize]); - } else { - hashes.push(tree_hash_atom(a.atom(node).as_ref())); - } - } - NodeVisitor::Pair(left, right) => { - ops.push(TreeOp::Cons); - ops.push(TreeOp::SExp(left)); - ops.push(TreeOp::SExp(right)); + while let Some(op) = ops.pop() { + match op { + TreeOp::SExp(node) => match a.node(node) { + NodeVisitor::Buffer(bytes) => { + hashes.push(tree_hash_atom(bytes)); + } + NodeVisitor::U32(val) => { + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); } - }, - TreeOp::Cons => { - let first = hashes.pop().unwrap(); - let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(&first, &rest)); } - TreeOp::ConsAddCacheCost(_, _) => unreachable!(), + NodeVisitor::Pair(left, right) => { + ops.push(TreeOp::Cons); + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + }, + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(tree_hash_pair(&first, &rest)); } + TreeOp::ConsAddCacheCost(_, _) => unreachable!(), } - - assert!(hashes.len() == 1); - hashes[0] } + assert!(hashes.len() == 1); + hashes[0] +} + +#[cfg(test)] +mod tests { + use crate::test_ops::node_eq; + + use super::*; + // this function costs but does not cache // we can use it to check that the cache is properly remembering costs fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Response { From 27642b23e52f3cb99c157e53cc0dbaf3b597df9c Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 6 Nov 2025 17:22:18 +0000 Subject: [PATCH 030/110] fmt --- tools/src/bin/sha256tree-benching.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 942f2a8e2..503ddc15d 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -37,7 +37,7 @@ fn main() -> std::io::Result<()> { for depth in 1..100 { let node = make_nested_pairs(&mut a, depth); let start = Instant::now(); - tree_hash(&a, node); // using tree hash as it costs the same as cached + tree_hash(&a, node); // using tree hash as it costs the same as cached let t = start.elapsed().as_secs_f64(); writeln!(output, "call\t{}\t{}", depth, t)?; samples.push((depth as f64, t)); @@ -85,4 +85,4 @@ fn main() -> std::io::Result<()> { println!("Results written to sha256tree_costs.tsv"); Ok(()) -} \ No newline at end of file +} From f726ca8575c4e9556988dd038d6dd25c7a638024 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 7 Nov 2025 16:01:17 +0000 Subject: [PATCH 031/110] larger range of samples --- tools/src/bin/sha256tree-benching.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 503ddc15d..892c9e19f 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -34,7 +34,7 @@ fn main() -> std::io::Result<()> { let mut samples = vec![]; { let mut a = Allocator::new(); - for depth in 1..100 { + for depth in 1..1000 { let node = make_nested_pairs(&mut a, depth); let start = Instant::now(); tree_hash(&a, node); // using tree hash as it costs the same as cached @@ -51,8 +51,8 @@ fn main() -> std::io::Result<()> { // cost atom sizes let mut samples = vec![]; { - for size in [1, 8, 16, 64, 256, 1024, 4096, 8192] { - let atom = vec![0u8; size]; + for size in 1..8192 { + let atom: Vec = vec![11_u8; size]; let start = Instant::now(); tree_hash_atom(&atom); let t = start.elapsed().as_secs_f64(); @@ -69,7 +69,7 @@ fn main() -> std::io::Result<()> { let mut samples = vec![]; { let mut a = Allocator::new(); - for n in [1, 2, 4, 8, 16, 32, 64, 128, 256] { + for n in 1..256 { let node = make_list_of_atoms(&mut a, n); let start = Instant::now(); tree_hash(&a, node); From af06b6d71624f134844c9433fb69daae70870ee9 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 7 Nov 2025 16:32:55 +0000 Subject: [PATCH 032/110] add cost scaling in line with benchmark-clvm-costs --- tools/src/bin/sha256tree-benching.rs | 32 ++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 892c9e19f..280722198 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,3 +1,4 @@ +use core::time; use std::fs::File; use std::io::Write; use std::time::Instant; @@ -29,6 +30,9 @@ fn main() -> std::io::Result<()> { let mut output = File::create("sha256tree_costs.tsv")?; writeln!(output, "# type\tx\ty")?; + // this "magic" scaling depends on the computer you run the tests on. + // It's calibrated against the timing of point_add, which has a cost + let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; // cost call (with nil) let mut samples = vec![]; @@ -38,14 +42,19 @@ fn main() -> std::io::Result<()> { let node = make_nested_pairs(&mut a, depth); let start = Instant::now(); tree_hash(&a, node); // using tree hash as it costs the same as cached - let t = start.elapsed().as_secs_f64(); + let t = start.elapsed().as_nanos() as f64; writeln!(output, "call\t{}\t{}", depth, t)?; samples.push((depth as f64, t)); } let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + let cost = slope * cost_scale; writeln!(output, "\n# call_slope\t{:.9}", slope)?; + writeln!(output, "\n# call_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# call_intercept\t{:.9}\n", intercept)?; - println!("call slope: {:.9}, intercept: {:.9}", slope, intercept); + println!( + "call slope: {:.9}, intercept: {:.9}, cost: {:.9}", + slope, intercept, cost + ); } // cost atom sizes @@ -55,14 +64,20 @@ fn main() -> std::io::Result<()> { let atom: Vec = vec![11_u8; size]; let start = Instant::now(); tree_hash_atom(&atom); - let t = start.elapsed().as_secs_f64(); + let t = start.elapsed().as_nanos() as f64; + writeln!(output, "atom\t{}\t{}", size, t)?; samples.push((size as f64, t)); } let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + let cost = slope * cost_scale; writeln!(output, "\n# atom_slope\t{:.9}", slope)?; + writeln!(output, "\n# atom_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# atom_intercept\t{:.9}\n", intercept)?; - println!("atom slope: {:.9}, intercept: {:.9}", slope, intercept); + println!( + "atom slope: {:.9}, intercept: {:.9}, cost: {:.9}", + slope, intercept, cost + ); } // cost list of atoms @@ -73,14 +88,19 @@ fn main() -> std::io::Result<()> { let node = make_list_of_atoms(&mut a, n); let start = Instant::now(); tree_hash(&a, node); - let t = start.elapsed().as_secs_f64(); + let t = start.elapsed().as_nanos() as f64; writeln!(output, "pair\t{}\t{}", n, t)?; samples.push((n as f64, t)); } let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + let cost = slope * cost_scale; writeln!(output, "\n# pair_slope\t{:.9}", slope)?; + writeln!(output, "\n# pair_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# pair_intercept\t{:.9}\n", intercept)?; - println!("pair slope: {:.9}, intercept: {:.9}", slope, intercept); + println!( + "pair slope: {:.9}, intercept: {:.9}, cost: {:.9}", + slope, intercept, cost + ); } println!("Results written to sha256tree_costs.tsv"); From 3473df74fb724a5b5e88c52e5275280f445e06a4 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 7 Nov 2025 16:34:24 +0000 Subject: [PATCH 033/110] clippy fix --- tools/src/bin/sha256tree-benching.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 280722198..0a65d6ede 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,4 +1,3 @@ -use core::time; use std::fs::File; use std::io::Write; use std::time::Instant; From ba6ce50451b814aa3d7f11b0020f1323b6f6919f Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Mon, 10 Nov 2025 15:13:35 +0000 Subject: [PATCH 034/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 13c3857b5..2721577d2 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -515,7 +515,7 @@ pub fn main() { Operator { opcode: 65, name: "sha256tree (atom)", - arg: Placeholder::SingleArg(Some(g1)), + arg: Placeholder::SingleArg(None), extra: None, flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, }, From 68fdb8f6532ffc9f661b05c913f59c522e4c4796 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 11 Nov 2025 11:57:50 +0000 Subject: [PATCH 035/110] call max() on values to stop negatives --- tools/src/bin/benchmark-clvm-cost.rs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 2721577d2..34222cb5e 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -205,12 +205,7 @@ fn time_per_arg(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 // measure run-time of many *nested* calls, to establish how much longer it // takes, approximately, for each additional nesting. The per_arg_time is // subtracted to get the base cost -fn base_call_time( - a: &mut Allocator, - op: &Operator, - per_arg_time: f64, - output: &mut dyn Write, -) -> f64 { +fn base_call_time(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); @@ -229,20 +224,21 @@ fn base_call_time( a.restore_checkpoint(&checkpoint); let call = build_nested_call(a, op.opcode, arg, i, op.extra); let start = Instant::now(); - let r = run_program(a, &dialect, call, a.nil(), 11000000000); + let r = run_program(a, &dialect, call, a.nil(), 11_000_000_000); if (op.flags & ALLOW_FAILURE) == 0 { r.unwrap(); } - let duration = start.elapsed(); - let duration = (duration.as_nanos() as f64) - (per_arg_time * i as f64); - let sample = (i as f64, duration); - writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); - samples.push(sample); + let duration_ns = start.elapsed().as_nanos() as f64; + writeln!(output, "{}\t{}", i, duration_ns).expect("failed to write"); + samples.push((i as f64, duration_ns)); } } - let (slope, _): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); - slope + // duration = base_cost (intercept) + slope (nested_cost) * i + let (_slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + + // 'intercept' is the estimated base cost per call (in ns) + intercept.max(100.0) } fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) -> f64 { @@ -265,7 +261,7 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - num_samples += 1; } - (total_time - per_arg_time * num_samples as f64) / num_samples as f64 + ((total_time - per_arg_time * num_samples as f64) / num_samples as f64).max(100.0) } const PER_BYTE_COST: u32 = 1; @@ -557,7 +553,7 @@ pub fn main() { let base_call_time = if (op.flags & NESTING_BASE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "base.log"); write_gnuplot_header(&mut *gnuplot, op, "base", "num nested calls"); - let base_call_time = base_call_time(&mut a, op, time_per_arg, &mut *output); + let base_call_time = base_call_time(&mut a, op, &mut *output); println!(" time: base: {base_call_time:.2}ns"); println!(" cost: base: {:.0}", base_call_time * base_cost_scale); From 4907172856d9ab32c698422b0e7bfccf965acccc Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 11 Nov 2025 12:09:54 +0000 Subject: [PATCH 036/110] remove per-arg-time comment --- tools/src/bin/benchmark-clvm-cost.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 34222cb5e..1094bc61d 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -203,8 +203,7 @@ fn time_per_arg(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 } // measure run-time of many *nested* calls, to establish how much longer it -// takes, approximately, for each additional nesting. The per_arg_time is -// subtracted to get the base cost +// takes, approximately, for each additional nesting. fn base_call_time(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); From 2552dc4a5da853caa979e60411556815ed85d518 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 11 Nov 2025 12:41:34 +0000 Subject: [PATCH 037/110] revert to original base_call_time with max(100.0) added --- tools/src/bin/benchmark-clvm-cost.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 1094bc61d..43d510015 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -227,17 +227,16 @@ fn base_call_time(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f if (op.flags & ALLOW_FAILURE) == 0 { r.unwrap(); } - let duration_ns = start.elapsed().as_nanos() as f64; - writeln!(output, "{}\t{}", i, duration_ns).expect("failed to write"); - samples.push((i as f64, duration_ns)); + let duration = start.elapsed(); + let duration = (duration.as_nanos() as f64) - (per_arg_time * i as f64); + let sample = (i as f64, duration); + writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); + samples.push(sample); } } - // duration = base_cost (intercept) + slope (nested_cost) * i - let (_slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); - - // 'intercept' is the estimated base cost per call (in ns) - intercept.max(100.0) + let (slope, _): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + slope.max(100.0) } fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) -> f64 { From 4b529e8d956e7474965d0213017ce115c5b2c3f4 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 11 Nov 2025 12:43:20 +0000 Subject: [PATCH 038/110] re-add time_per_arg --- tools/src/bin/benchmark-clvm-cost.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 43d510015..0853462db 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -204,7 +204,12 @@ fn time_per_arg(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 // measure run-time of many *nested* calls, to establish how much longer it // takes, approximately, for each additional nesting. -fn base_call_time(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { +fn base_call_time( + a: &mut Allocator, + op: &Operator, + per_arg_time: f64, + output: &mut dyn Write, +) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); @@ -551,7 +556,7 @@ pub fn main() { let base_call_time = if (op.flags & NESTING_BASE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "base.log"); write_gnuplot_header(&mut *gnuplot, op, "base", "num nested calls"); - let base_call_time = base_call_time(&mut a, op, &mut *output); + let base_call_time = base_call_time(&mut a, op, time_per_arg, &mut *output); println!(" time: base: {base_call_time:.2}ns"); println!(" cost: base: {:.0}", base_call_time * base_cost_scale); From 9fa2495ed2a6ee80732795bd1553af3b0d0d770b Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 11 Nov 2025 16:36:45 +0000 Subject: [PATCH 039/110] new per-byte cost measuring --- tools/src/bin/sha256tree-benching.rs | 81 +++++++++++++--------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 0a65d6ede..eb9ed22fa 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,3 +1,5 @@ +use clvmr::chia_dialect::ChiaDialect; +use clvmr::run_program::run_program; use std::fs::File; use std::io::Write; use std::time::Instant; @@ -6,15 +8,7 @@ use linreg::linear_regression_of; // bring in your existing code use clvmr::allocator::{Allocator, NodePtr}; -use clvmr::treehash::{tree_hash, tree_hash_atom}; - -fn make_nested_pairs(a: &mut Allocator, depth: usize) -> NodePtr { - let mut node = a.nil(); - for _ in 0..depth { - node = a.new_pair(node, a.nil()).unwrap(); - } - node -} +use clvmr::treehash::tree_hash; fn make_list_of_atoms(a: &mut Allocator, n: usize) -> NodePtr { let atom = a.new_atom(&[1u8; 32]).unwrap(); @@ -25,6 +19,36 @@ fn make_list_of_atoms(a: &mut Allocator, n: usize) -> NodePtr { list } +fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { + let mut samples = Vec::<(f64, f64)>::new(); + let dialect = ChiaDialect::new(0x0200); // enable shatree + + let op_code = a.new_number(65.into()).unwrap(); + let quote = a.new_number(1.into()).unwrap(); + let mut atom_str = String::from(""); + let checkpoint = a.checkpoint(); + + for i in (0..1000000).step_by(5) { + // make the atom longer as a function of i + atom_str.push_str(&((i % 89) + 10).to_string()); // just to mix it up + let atom = a.new_atom(&hex::decode(&atom_str).unwrap()).unwrap(); + // let args = a.new_pair(atom, a.nil()).unwrap(); + let args = a.new_pair(quote, atom).unwrap(); + let call = a.new_pair(args, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + let start = Instant::now(); + run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let duration = start.elapsed(); + let sample = (i as f64, duration.as_nanos() as f64); + writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); + samples.push(sample); + + a.restore_checkpoint(&checkpoint); + } + + linear_regression_of(&samples).expect("linreg failed") +} + fn main() -> std::io::Result<()> { let mut output = File::create("sha256tree_costs.tsv")?; @@ -33,48 +57,17 @@ fn main() -> std::io::Result<()> { // It's calibrated against the timing of point_add, which has a cost let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; - // cost call (with nil) - let mut samples = vec![]; - { - let mut a = Allocator::new(); - for depth in 1..1000 { - let node = make_nested_pairs(&mut a, depth); - let start = Instant::now(); - tree_hash(&a, node); // using tree hash as it costs the same as cached - let t = start.elapsed().as_nanos() as f64; - writeln!(output, "call\t{}\t{}", depth, t)?; - samples.push((depth as f64, t)); - } - let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); - let cost = slope * cost_scale; - writeln!(output, "\n# call_slope\t{:.9}", slope)?; - writeln!(output, "\n# call_slope * cost_scale\t{:?}", cost)?; - writeln!(output, "# call_intercept\t{:.9}\n", intercept)?; - println!( - "call slope: {:.9}, intercept: {:.9}, cost: {:.9}", - slope, intercept, cost - ); - } - + // base call cost is covered in benchmark-clvm-cost so not included here // cost atom sizes - let mut samples = vec![]; { - for size in 1..8192 { - let atom: Vec = vec![11_u8; size]; - let start = Instant::now(); - tree_hash_atom(&atom); - let t = start.elapsed().as_nanos() as f64; - - writeln!(output, "atom\t{}\t{}", size, t)?; - samples.push((size as f64, t)); - } - let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + let mut a = Allocator::new(); + let (slope, intercept) = time_per_byte_for_atom(&mut a, &mut output); let cost = slope * cost_scale; writeln!(output, "\n# atom_slope\t{:.9}", slope)?; writeln!(output, "\n# atom_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# atom_intercept\t{:.9}\n", intercept)?; println!( - "atom slope: {:.9}, intercept: {:.9}, cost: {:.9}", + "atom slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", slope, intercept, cost ); } From 4cbd4778d73e5c4b458bcbaec02264d7c8333e54 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 12 Nov 2025 10:21:08 +0000 Subject: [PATCH 040/110] reformat list timings --- tools/src/bin/sha256tree-benching.rs | 56 ++++++++++++++++------------ 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index eb9ed22fa..442c6df06 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,5 +1,6 @@ use clvmr::chia_dialect::ChiaDialect; use clvmr::run_program::run_program; +use clvmr::serde::node_to_bytes; use std::fs::File; use std::io::Write; use std::time::Instant; @@ -7,17 +8,7 @@ use std::time::Instant; use linreg::linear_regression_of; // bring in your existing code -use clvmr::allocator::{Allocator, NodePtr}; -use clvmr::treehash::tree_hash; - -fn make_list_of_atoms(a: &mut Allocator, n: usize) -> NodePtr { - let atom = a.new_atom(&[1u8; 32]).unwrap(); - let mut list = a.nil(); - for _ in 0..n { - list = a.new_pair(atom, list).unwrap(); - } - list -} +use clvmr::allocator::Allocator; fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); @@ -28,7 +19,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let mut atom_str = String::from(""); let checkpoint = a.checkpoint(); - for i in (0..1000000).step_by(5) { + for i in 0..10000 { // make the atom longer as a function of i atom_str.push_str(&((i % 89) + 10).to_string()); // just to mix it up let atom = a.new_atom(&hex::decode(&atom_str).unwrap()).unwrap(); @@ -49,6 +40,33 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 linear_regression_of(&samples).expect("linreg failed") } +fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { + let mut samples = Vec::<(f64, f64)>::new(); + let dialect = ChiaDialect::new(0x0200); // enable shatree + + let op_code = a.new_number(65.into()).unwrap(); + let quote = a.new_number(1.into()).unwrap(); + let list = a.nil(); + + for i in 0..10000 { + // make the atom longer as a function of i + let list = a.new_pair(a.nil(), list).unwrap(); + let quotation = a.new_pair(quote, list).unwrap(); + let call = a.new_pair(quotation, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + let debug = node_to_bytes(a, call); + println!("DEBUG: {:?}", debug); + let start = Instant::now(); + run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let duration = start.elapsed(); + let sample = (i as f64, duration.as_nanos() as f64); + writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); + samples.push(sample); + } + + linear_regression_of(&samples).expect("linreg failed") +} + fn main() -> std::io::Result<()> { let mut output = File::create("sha256tree_costs.tsv")?; @@ -58,6 +76,7 @@ fn main() -> std::io::Result<()> { let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; // base call cost is covered in benchmark-clvm-cost so not included here + // cost atom sizes { let mut a = Allocator::new(); @@ -72,19 +91,10 @@ fn main() -> std::io::Result<()> { ); } - // cost list of atoms - let mut samples = vec![]; + // cost list / cons boxes { let mut a = Allocator::new(); - for n in 1..256 { - let node = make_list_of_atoms(&mut a, n); - let start = Instant::now(); - tree_hash(&a, node); - let t = start.elapsed().as_nanos() as f64; - writeln!(output, "pair\t{}\t{}", n, t)?; - samples.push((n as f64, t)); - } - let (slope, intercept): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); + let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); let cost = slope * cost_scale; writeln!(output, "\n# pair_slope\t{:.9}", slope)?; writeln!(output, "\n# pair_slope * cost_scale\t{:?}", cost)?; From 8e58fa8c88eef2a3a2e5eda6810c8eb08bdac069 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 12 Nov 2025 15:33:15 +0000 Subject: [PATCH 041/110] dialect expansions --- op-tests/test-sha256tree.txt | 1 + src/chia_dialect.rs | 8 +++++++- src/dialect.rs | 3 +++ src/run_program.rs | 3 ++- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 4758a6bb5..e393fd6b0 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,6 +1,7 @@ sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1670 sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1620 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 4870 sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 4930 sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 14690 sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 17940 diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index 7f1ab7414..70bbd3350 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -81,6 +81,9 @@ impl Dialect for ChiaDialect { // Keccak is allowed as if it were a default operator, inside of the softfork guard. OperatorSet::Keccak => ENABLE_KECCAK_OPS_OUTSIDE_GUARD, + + // Sha256tree is enabled as if it were a default operator + OperatorSet::Sha256tree => ENABLE_SHA256_TREE | ENABLE_KECCAK_OPS_OUTSIDE_GUARD, }; let op_len = allocator.atom_len(o); @@ -199,7 +202,10 @@ impl Dialect for ChiaDialect { // Extension 1 is for the keccak256 operator. 1 => OperatorSet::Keccak, - // Extensions 2 and beyond are considered invalid by the mempool. + // Extension 2 is for the sha256tree operator. + 2 => OperatorSet::Sha256tree, + + // Extensions 3 and beyond are considered invalid by the mempool. // However, all future extensions are valid in consensus mode and reserved for future softforks. _ => OperatorSet::Default, } diff --git a/src/dialect.rs b/src/dialect.rs index cefaadd9d..722d906e7 100644 --- a/src/dialect.rs +++ b/src/dialect.rs @@ -16,6 +16,9 @@ pub enum OperatorSet { /// The keccak256 operator, which is only available inside the softfork guard. /// This uses softfork extension 1, which does not conflict with the BLS fork. Keccak, + + // The sha256tree operator, which is only activated after a hardfork + Sha256tree, } pub trait Dialect { diff --git a/src/run_program.rs b/src/run_program.rs index 06afbf29a..139c754bc 100644 --- a/src/run_program.rs +++ b/src/run_program.rs @@ -1432,7 +1432,7 @@ mod tests { #[case] err: &'static str, #[values(0)] flags: u32, #[values(false, true)] mempool: bool, - #[values(0, 1, 2)] test_ext: u8, + #[values(0, 1, 2, 3)] test_ext: u8, ) { let (cost, enabled, hard_fork_flag) = fields; let softfork_prg = @@ -1445,6 +1445,7 @@ mod tests { let ext_enabled = match test_ext { 0 => true, // BLS 1 => true, // KECCAK + 2 => true, // SHA256TREE _ => false, }; From 6f316d136765700f0c72c44df8c55a1ea9ce6394 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 12 Nov 2025 15:43:12 +0000 Subject: [PATCH 042/110] increment extension on unknown extension test now extension is known --- src/run_program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run_program.rs b/src/run_program.rs index 139c754bc..e23a6a9d4 100644 --- a/src/run_program.rs +++ b/src/run_program.rs @@ -1161,7 +1161,7 @@ mod tests { // without the flag to enable the keccak extensions, it's an unknown extension RunProgramTest { - prg: "(softfork (q . 161) (q . 2) (q . (q . 42)) (q . ()))", + prg: "(softfork (q . 161) (q . 3) (q . (q . 42)) (q . ()))", args: "()", flags: NO_UNKNOWN_OPS, result: None, From d597e26f34eb0345436cad040c2a6291e67da2a3 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 13 Nov 2025 10:49:54 +0000 Subject: [PATCH 043/110] fix bug --- tools/src/bin/sha256tree-benching.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 442c6df06..5826f585d 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -14,7 +14,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0x0200); // enable shatree - let op_code = a.new_number(65.into()).unwrap(); + let op_code = a.new_number(63.into()).unwrap(); let quote = a.new_number(1.into()).unwrap(); let mut atom_str = String::from(""); let checkpoint = a.checkpoint(); @@ -44,7 +44,7 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0x0200); // enable shatree - let op_code = a.new_number(65.into()).unwrap(); + let op_code = a.new_number(63.into()).unwrap(); let quote = a.new_number(1.into()).unwrap(); let list = a.nil(); @@ -55,7 +55,6 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let call = a.new_pair(quotation, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); let debug = node_to_bytes(a, call); - println!("DEBUG: {:?}", debug); let start = Instant::now(); run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); let duration = start.elapsed(); From 59afd68bf37805582ee0f5cc8307b53109f0b8eb Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 13 Nov 2025 11:09:33 +0000 Subject: [PATCH 044/110] remove debugging --- tools/src/bin/sha256tree-benching.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 5826f585d..263e8996d 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,6 +1,5 @@ use clvmr::chia_dialect::ChiaDialect; use clvmr::run_program::run_program; -use clvmr::serde::node_to_bytes; use std::fs::File; use std::io::Write; use std::time::Instant; @@ -16,7 +15,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let op_code = a.new_number(63.into()).unwrap(); let quote = a.new_number(1.into()).unwrap(); - let mut atom_str = String::from(""); + let mut atom_str = String::from("ff".repeat(10_000)); let checkpoint = a.checkpoint(); for i in 0..10000 { @@ -48,13 +47,12 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let quote = a.new_number(1.into()).unwrap(); let list = a.nil(); - for i in 0..10000 { + for i in 0..100000 { // make the atom longer as a function of i let list = a.new_pair(a.nil(), list).unwrap(); let quotation = a.new_pair(quote, list).unwrap(); let call = a.new_pair(quotation, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); - let debug = node_to_bytes(a, call); let start = Instant::now(); run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); let duration = start.elapsed(); @@ -85,7 +83,7 @@ fn main() -> std::io::Result<()> { writeln!(output, "\n# atom_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# atom_intercept\t{:.9}\n", intercept)?; println!( - "atom slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", + "atom byte slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", slope, intercept, cost ); } @@ -99,7 +97,7 @@ fn main() -> std::io::Result<()> { writeln!(output, "\n# pair_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# pair_intercept\t{:.9}\n", intercept)?; println!( - "pair slope: {:.9}, intercept: {:.9}, cost: {:.9}", + "list length slope: {:.9}, intercept: {:.9}, cost: {:.9}", slope, intercept, cost ); } From 3a96d38ff95fe0e1d5bb0e4a79c27c6ea8e346f6 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 13 Nov 2025 16:25:17 +0000 Subject: [PATCH 045/110] new costs and new costings based on benchmarks --- op-tests/test-sha256tree-hash.txt | 60 ++++++++++++++-------------- op-tests/test-sha256tree.txt | 14 +++---- src/treehash.rs | 34 ++++++++++------ tools/generate-sha256tree-tests.py | 59 ++++++++++++++++++--------- tools/src/bin/sha256tree-benching.rs | 14 ++++--- 5 files changed, 108 insertions(+), 73 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index 0e0ae1f68..0a3d5f62c 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 59110 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 11860 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1940 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 11970 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 14880 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 8670 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 2100 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 22030 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 49000 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 2100 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 25410 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 5120 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 12500 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 2100 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 44720 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 8140 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 5200 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 39360 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 8370 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 15070 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 2100 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 8610 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 5110 -sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 1680 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 18540 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 43070 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 29150 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 5670 -sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 8190 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 134120 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 26250 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 4050 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 26860 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 33650 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 18850 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 4050 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 49670 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 110700 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 4050 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 57070 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 10840 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 27470 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 4050 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 101470 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 18240 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 11450 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 88500 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 18240 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 33650 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 4050 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 18850 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 10840 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 3440 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 41660 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 97120 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 65690 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 12060 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3440 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 18240 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index e393fd6b0..287ca1eae 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1630 -sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1670 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1620 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 4870 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 4930 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 14690 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 17940 +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3440 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 3440 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 3440 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 10840 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 10840 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 33040 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 40440 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/src/treehash.rs b/src/treehash.rs index d602ad421..c21058ae3 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -10,9 +10,9 @@ use crate::ObjectType; use crate::SExp; use chia_sha2::Sha256; -const SHA256TREE_BASE_COST: Cost = 0; -const SHA256TREE_COST_PER_CALL: Cost = 1300; -const SHA256TREE_COST_PER_BYTE: Cost = 10; +const SHA256TREE_BASE_COST: Cost = 30; +const SHA256TREE_COST_PER_NODE: Cost = 3090; +const SHA256TREE_COST_PER_32_BYTES: Cost = 610; pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); @@ -138,6 +138,12 @@ enum TreeOp { ConsAddCacheCost(NodePtr, Cost), } +fn increment_bytes(bytes: &mut usize, amount: usize, cost: &mut Cost) { + *bytes += amount; + *cost += (*bytes / 32) as u64 * SHA256TREE_COST_PER_32_BYTES; + *bytes %= 32; +} + pub fn tree_hash_cached_costed( a: &mut Allocator, node: NodePtr, @@ -149,22 +155,25 @@ pub fn tree_hash_cached_costed( let mut hashes = Vec::new(); let mut ops = vec![TreeOp::SExp(node)]; let mut cost: Cost = SHA256TREE_BASE_COST; + let mut total_bytes: usize = 0; while let Some(op) = ops.pop() { match op { TreeOp::SExp(node) => { // charge a call cost for processing this op - cost += SHA256TREE_COST_PER_CALL; + cost += SHA256TREE_COST_PER_NODE; + // this represents the 1 or 2 for atom or pair + increment_bytes(&mut total_bytes, 1, &mut cost); check_cost(cost, cost_left)?; match a.node(node) { NodeVisitor::Buffer(bytes) => { - cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; + increment_bytes(&mut total_bytes, bytes.len(), &mut cost); check_cost(cost, cost_left)?; let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { - cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; + increment_bytes(&mut total_bytes, a.atom_len(node), &mut cost); check_cost(cost, cost_left)?; if (val as usize) < PRECOMPUTED_HASHES.len() { hashes.push(PRECOMPUTED_HASHES[val as usize]); @@ -173,7 +182,7 @@ pub fn tree_hash_cached_costed( } } NodeVisitor::Pair(left, right) => { - cost += SHA256TREE_COST_PER_BYTE * 65_u64; + increment_bytes(&mut total_bytes, 64, &mut cost); check_cost(cost, cost_left)?; if let Some((hash, cached_cost)) = cache.get(node) { // when reusing a cached subtree, charge its cached cost @@ -269,24 +278,23 @@ mod tests { let mut ops = vec![TreeOp::SExp(node)]; let mut cost = SHA256TREE_BASE_COST; + let mut total_bytes: usize = 0; while let Some(op) = ops.pop() { match op { TreeOp::SExp(node) => { - cost += SHA256TREE_COST_PER_CALL; + cost += SHA256TREE_COST_PER_NODE; check_cost(cost, cost_left)?; match a.node(node) { NodeVisitor::Buffer(bytes) => { - cost += SHA256TREE_COST_PER_BYTE * bytes.len() as u64; + increment_bytes(&mut total_bytes, bytes.len(), &mut cost); check_cost(cost, cost_left)?; - let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { - cost += SHA256TREE_COST_PER_BYTE * a.atom_len(node) as u64; + increment_bytes(&mut total_bytes, a.atom_len(node), &mut cost); check_cost(cost, cost_left)?; - if (val as usize) < PRECOMPUTED_HASHES.len() { hashes.push(PRECOMPUTED_HASHES[val as usize]); } else { @@ -294,7 +302,7 @@ mod tests { } } NodeVisitor::Pair(left, right) => { - cost += SHA256TREE_COST_PER_BYTE * 65; + increment_bytes(&mut total_bytes, 64, &mut cost); check_cost(cost, cost_left)?; ops.push(TreeOp::Cons); diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index b0e8e7f42..dc9633924 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -6,10 +6,11 @@ seed(1337) -SHA256TREE_BASE_COST = 0 -SHA256TREE_COST_PER_CALL = 1300 -SHA256TREE_COST_PER_BYTE = 10 -MALLOC_COST_PER_BYTE = 10 +# Match Rust cost constants exactly +SHA256TREE_BASE_COST = 30 +SHA256TREE_COST_PER_NODE = 3090 +SHA256TREE_COST_PER_32_BYTES = 610 +MALLOC_COST_PER_BYTE = 10 SIZE = 30 @@ -20,6 +21,7 @@ def tree_hash_atom(b: bytes) -> bytes: h.update(b) return h.digest() + def tree_hash_pair(left_hash: bytes, right_hash: bytes) -> bytes: h = sha256() h.update(b"\x02") @@ -27,6 +29,7 @@ def tree_hash_pair(left_hash: bytes, right_hash: bytes) -> bytes: h.update(right_hash) return h.digest() + def random_atom() -> bytes: return choice([ b"", @@ -38,7 +41,8 @@ def random_atom() -> bytes: randbytes(48) ]) -def random_tree(depth: int) -> object: + +def random_tree(depth: int): """Recursively generate random nested pairs (tuples) or atoms""" if depth == 0 or randint(0, 2) == 0: return random_atom() @@ -47,17 +51,38 @@ def random_tree(depth: int) -> object: def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: - """Return (hash, cost) similar to tree_hash_cached_costed""" - cost = SHA256TREE_COST_PER_CALL - if isinstance(obj, bytes): - cost += SHA256TREE_COST_PER_BYTE * len(obj) - return tree_hash_atom(obj), cost - else: - left, right = obj - cost += SHA256TREE_COST_PER_BYTE * 65 - left_hash, left_cost = compute_tree_hash_and_cost(left) - right_hash, right_cost = compute_tree_hash_and_cost(right) - return tree_hash_pair(left_hash, right_hash), cost + left_cost + right_cost + """ + Return (hash, cost) consistent with Rust's tree_hash_cached_costed() + """ + total_cost = SHA256TREE_BASE_COST + total_bytes = 0 + + def helper(node): + nonlocal total_cost, total_bytes + # Every node adds a fixed per-node cost + total_cost += SHA256TREE_COST_PER_NODE + + if isinstance(node, bytes): + # Add 1 for the atom marker (b"\x01") + len(bytes) + added_bytes = 1 + len(node) + total_bytes += added_bytes + total_cost += (total_bytes // 32) * SHA256TREE_COST_PER_32_BYTES + total_bytes %= 32 + return tree_hash_atom(node) + else: + left, right = node + # Add 1 for the pair marker (b"\x02") + 64 bytes of child hashes + added_bytes = 1 + 64 + total_bytes += added_bytes + total_cost += (total_bytes // 32) * SHA256TREE_COST_PER_32_BYTES + total_bytes %= 32 + left_hash = helper(left) + right_hash = helper(right) + return tree_hash_pair(left_hash, right_hash) + + h = helper(obj) + total_cost += MALLOC_COST_PER_BYTE * 32 + return h, total_cost def sexp_repr(obj) -> str: @@ -84,6 +109,4 @@ def sexp_repr(obj) -> str: continue test_cases.add(s) h, cost = compute_tree_hash_and_cost(t) - cost += MALLOC_COST_PER_BYTE * 32 f.write(f"sha256tree {s} => 0x{h.hex()} | {cost}\n") - diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 263e8996d..521683a9a 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -15,12 +15,12 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let op_code = a.new_number(63.into()).unwrap(); let quote = a.new_number(1.into()).unwrap(); - let mut atom_str = String::from("ff".repeat(10_000)); + let mut atom_str = "ff".repeat(10_000); let checkpoint = a.checkpoint(); - for i in 0..10000 { + for i in 0..100000 { // make the atom longer as a function of i - atom_str.push_str(&((i % 89) + 10).to_string()); // just to mix it up + atom_str.push_str(&((i % 89) + 10).to_string().repeat(32)); // just to mix it up let atom = a.new_atom(&hex::decode(&atom_str).unwrap()).unwrap(); // let args = a.new_pair(atom, a.nil()).unwrap(); let args = a.new_pair(quote, atom).unwrap(); @@ -45,11 +45,15 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let op_code = a.new_number(63.into()).unwrap(); let quote = a.new_number(1.into()).unwrap(); - let list = a.nil(); + let mut list = a.nil(); + + for _ in 0..500 { + list = a.new_pair(a.nil(), list).unwrap(); + } for i in 0..100000 { // make the atom longer as a function of i - let list = a.new_pair(a.nil(), list).unwrap(); + list = a.new_pair(a.nil(), list).unwrap(); let quotation = a.new_pair(quote, list).unwrap(); let call = a.new_pair(quotation, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); From 5d1b6299b7b628f8919e38f82abae1e8ddf2c4ed Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 13 Nov 2025 19:35:05 +0000 Subject: [PATCH 046/110] 32 byte blocks calculated per hash instead of in total --- op-tests/test-sha256tree-hash.txt | 60 +++++++++++++++--------------- op-tests/test-sha256tree.txt | 14 +++---- src/treehash.rs | 22 +++++------ tools/generate-sha256tree-tests.py | 54 +++++++++++---------------- 4 files changed, 67 insertions(+), 83 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index 0a3d5f62c..cafdfa50e 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 134120 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 26250 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 4050 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 26860 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 33650 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 18850 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 4050 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 49670 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 110700 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 4050 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 57070 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 10840 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 27470 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 4050 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 101470 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 18240 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 11450 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 88500 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 18240 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 33650 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 4050 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 18850 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 10840 -sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 3440 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 41660 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 97120 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 65690 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 12060 -sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3440 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 18240 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 153030 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 29910 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 4660 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 30520 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 38530 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 21900 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 4660 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 56990 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 126560 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 4660 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 64390 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 12670 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 31740 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 4660 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 117330 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 21290 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 13280 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 101920 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 21290 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 39140 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 4660 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 21900 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 12670 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 4050 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 48370 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 109930 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 75450 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 13890 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 4050 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 21290 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 287ca1eae..31496e34f 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3440 -sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 3440 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 3440 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 10840 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 10840 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 33040 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 40440 +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 4050 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 4050 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 4050 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 12670 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 12670 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 38530 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 47150 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/src/treehash.rs b/src/treehash.rs index c21058ae3..1dbe21c91 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -138,10 +138,9 @@ enum TreeOp { ConsAddCacheCost(NodePtr, Cost), } -fn increment_bytes(bytes: &mut usize, amount: usize, cost: &mut Cost) { - *bytes += amount; - *cost += (*bytes / 32) as u64 * SHA256TREE_COST_PER_32_BYTES; - *bytes %= 32; +#[inline] +fn increment_bytes(amount: usize, cost: &mut Cost) { + *cost += (amount.div_ceil(32)) as u64 * SHA256TREE_COST_PER_32_BYTES; } pub fn tree_hash_cached_costed( @@ -155,7 +154,6 @@ pub fn tree_hash_cached_costed( let mut hashes = Vec::new(); let mut ops = vec![TreeOp::SExp(node)]; let mut cost: Cost = SHA256TREE_BASE_COST; - let mut total_bytes: usize = 0; while let Some(op) = ops.pop() { match op { @@ -163,17 +161,16 @@ pub fn tree_hash_cached_costed( // charge a call cost for processing this op cost += SHA256TREE_COST_PER_NODE; // this represents the 1 or 2 for atom or pair - increment_bytes(&mut total_bytes, 1, &mut cost); check_cost(cost, cost_left)?; match a.node(node) { NodeVisitor::Buffer(bytes) => { - increment_bytes(&mut total_bytes, bytes.len(), &mut cost); + increment_bytes(bytes.len() + 1, &mut cost); check_cost(cost, cost_left)?; let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { - increment_bytes(&mut total_bytes, a.atom_len(node), &mut cost); + increment_bytes(a.atom_len(node) + 1, &mut cost); check_cost(cost, cost_left)?; if (val as usize) < PRECOMPUTED_HASHES.len() { hashes.push(PRECOMPUTED_HASHES[val as usize]); @@ -182,7 +179,7 @@ pub fn tree_hash_cached_costed( } } NodeVisitor::Pair(left, right) => { - increment_bytes(&mut total_bytes, 64, &mut cost); + increment_bytes(65, &mut cost); check_cost(cost, cost_left)?; if let Some((hash, cached_cost)) = cache.get(node) { // when reusing a cached subtree, charge its cached cost @@ -278,7 +275,6 @@ mod tests { let mut ops = vec![TreeOp::SExp(node)]; let mut cost = SHA256TREE_BASE_COST; - let mut total_bytes: usize = 0; while let Some(op) = ops.pop() { match op { @@ -287,13 +283,13 @@ mod tests { check_cost(cost, cost_left)?; match a.node(node) { NodeVisitor::Buffer(bytes) => { - increment_bytes(&mut total_bytes, bytes.len(), &mut cost); + increment_bytes(bytes.len() + 1, &mut cost); check_cost(cost, cost_left)?; let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { - increment_bytes(&mut total_bytes, a.atom_len(node), &mut cost); + increment_bytes(a.atom_len(node) + 1, &mut cost); check_cost(cost, cost_left)?; if (val as usize) < PRECOMPUTED_HASHES.len() { hashes.push(PRECOMPUTED_HASHES[val as usize]); @@ -302,7 +298,7 @@ mod tests { } } NodeVisitor::Pair(left, right) => { - increment_bytes(&mut total_bytes, 64, &mut cost); + increment_bytes(65, &mut cost); check_cost(cost, cost_left)?; ops.push(TreeOp::Cons); diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index dc9633924..c3312c5b4 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -3,10 +3,11 @@ from pathlib import Path from random import randbytes, choice, randint, seed import sys +import math seed(1337) -# Match Rust cost constants exactly +# --- updated costing constants to match current Rust --- SHA256TREE_BASE_COST = 30 SHA256TREE_COST_PER_NODE = 3090 SHA256TREE_COST_PER_32_BYTES = 610 @@ -49,40 +50,25 @@ def random_tree(depth: int): else: return (random_tree(depth - 1), random_tree(depth - 1)) +def increment_bytes(amount: int) -> int: + return math.ceil(amount / 32) * SHA256TREE_COST_PER_32_BYTES + def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: - """ - Return (hash, cost) consistent with Rust's tree_hash_cached_costed() - """ - total_cost = SHA256TREE_BASE_COST - total_bytes = 0 - - def helper(node): - nonlocal total_cost, total_bytes - # Every node adds a fixed per-node cost - total_cost += SHA256TREE_COST_PER_NODE - - if isinstance(node, bytes): - # Add 1 for the atom marker (b"\x01") + len(bytes) - added_bytes = 1 + len(node) - total_bytes += added_bytes - total_cost += (total_bytes // 32) * SHA256TREE_COST_PER_32_BYTES - total_bytes %= 32 - return tree_hash_atom(node) - else: - left, right = node - # Add 1 for the pair marker (b"\x02") + 64 bytes of child hashes - added_bytes = 1 + 64 - total_bytes += added_bytes - total_cost += (total_bytes // 32) * SHA256TREE_COST_PER_32_BYTES - total_bytes %= 32 - left_hash = helper(left) - right_hash = helper(right) - return tree_hash_pair(left_hash, right_hash) - - h = helper(obj) - total_cost += MALLOC_COST_PER_BYTE * 32 - return h, total_cost + """Return (hash, cost) similar to tree_hash_cached_costed""" + cost = SHA256TREE_COST_PER_NODE + + if isinstance(obj, bytes): + cost += increment_bytes(len(obj) + 1) + return tree_hash_atom(obj), cost + + # Pair + left, right = obj + cost += increment_bytes(65) + left_hash, left_cost = compute_tree_hash_and_cost(left) + right_hash, right_cost = compute_tree_hash_and_cost(right) + total_cost = cost + left_cost + right_cost + return tree_hash_pair(left_hash, right_hash), total_cost def sexp_repr(obj) -> str: @@ -109,4 +95,6 @@ def sexp_repr(obj) -> str: continue test_cases.add(s) h, cost = compute_tree_hash_and_cost(t) + cost += SHA256TREE_BASE_COST + cost += MALLOC_COST_PER_BYTE * 32 f.write(f"sha256tree {s} => 0x{h.hex()} | {cost}\n") From b8cb31eef3079e42aaa1904f9e424d933247c141 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 14 Nov 2025 10:51:15 +0000 Subject: [PATCH 047/110] max cost not slope --- tools/src/bin/benchmark-clvm-cost.rs | 18 ++++++++++++------ tools/src/bin/sha256tree-benching.rs | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 0853462db..33db4678d 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -1,6 +1,6 @@ use clap::Parser; use clvmr::allocator::{Allocator, NodePtr}; -use clvmr::chia_dialect::ChiaDialect; +use clvmr::chia_dialect::{ChiaDialect, ENABLE_SHA256_TREE}; use clvmr::run_program::run_program; use linreg::linear_regression_of; use std::fs::{create_dir_all, File}; @@ -118,7 +118,7 @@ fn substitute(args: Placeholder, s: NodePtr) -> OpArgs { fn time_invocation(a: &mut Allocator, op: u32, arg: OpArgs, flags: u32) -> f64 { let call = build_call(a, op, arg, 1, None); //println!("{:x?}", &Node::new(a, call)); - let dialect = ChiaDialect::new(0x0200); + let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11000000000); if (flags & ALLOW_FAILURE) == 0 { @@ -241,7 +241,7 @@ fn base_call_time( } let (slope, _): (f64, f64) = linear_regression_of(&samples).expect("linreg failed"); - slope.max(100.0) + slope } fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) -> f64 { @@ -264,7 +264,7 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - num_samples += 1; } - ((total_time - per_arg_time * num_samples as f64) / num_samples as f64).max(100.0) + ((total_time - per_arg_time * num_samples as f64) / num_samples as f64) } const PER_BYTE_COST: u32 = 1; @@ -558,14 +558,20 @@ pub fn main() { write_gnuplot_header(&mut *gnuplot, op, "base", "num nested calls"); let base_call_time = base_call_time(&mut a, op, time_per_arg, &mut *output); println!(" time: base: {base_call_time:.2}ns"); - println!(" cost: base: {:.0}", base_call_time * base_cost_scale); + println!( + " cost: base: {:.0}", + (base_call_time * base_cost_scale).max(100.0) + ); print_plot(&mut *gnuplot, &base_call_time, &0.0, op.name, "base"); base_call_time } else { let base_call_time = base_call_time_no_nest(&mut a, op, time_per_arg); println!(" time: base: {base_call_time:.2}ns"); - println!(" cost: base: {:.0}", base_call_time * base_cost_scale); + println!( + " cost: base: {:.0}", + (base_call_time * base_cost_scale).max(100) + ); base_call_time }; diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 521683a9a..182e1e8dd 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -101,7 +101,7 @@ fn main() -> std::io::Result<()> { writeln!(output, "\n# pair_slope * cost_scale\t{:?}", cost)?; writeln!(output, "# pair_intercept\t{:.9}\n", intercept)?; println!( - "list length slope: {:.9}, intercept: {:.9}, cost: {:.9}", + "list length slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", slope, intercept, cost ); } From 972bb927e442ff7d6b54a057b38a4939e48060c0 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 14 Nov 2025 14:50:28 +0000 Subject: [PATCH 048/110] integrate into benchmark-clvm-cost --- tools/src/bin/benchmark-clvm-cost.rs | 99 ++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 33db4678d..ab0222845 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -264,15 +264,80 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - num_samples += 1; } - ((total_time - per_arg_time * num_samples as f64) / num_samples as f64) + (total_time - per_arg_time * num_samples as f64) / num_samples as f64 } +// this adds 32 bytes at a time compared to per_byte which adds 5 at a time +// at the moment this func is only for sha256tree and will need adapting in the future for other uses +fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { + let mut samples = Vec::<(f64, f64)>::new(); + let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree + + let op_code = a.new_number(63.into()).unwrap(); + let quote = a.new_number(1.into()).unwrap(); + let mut atom_str = "ff".repeat(10_000); + let checkpoint = a.checkpoint(); + + for i in 0..10000 { + // make the atom longer as a function of i + atom_str.push_str(&((i % 89) + 10).to_string().repeat(32)); // just to mix it up + let atom = a.new_atom(&hex::decode(&atom_str).unwrap()).unwrap(); + // let args = a.new_pair(atom, a.nil()).unwrap(); + let args = a.new_pair(quote, atom).unwrap(); + let call = a.new_pair(args, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + let start = Instant::now(); + run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let duration = start.elapsed(); + let sample = (i as f64, duration.as_nanos() as f64); + writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); + samples.push(sample); + + a.restore_checkpoint(&checkpoint); + } + + linear_regression_of(&samples).expect("linreg failed") +} + +// at the moment this func is only for sha256tree and will need adapting in the future for other uses +fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { + let mut samples = Vec::<(f64, f64)>::new(); + let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree + + let op_code = a.new_number(63.into()).unwrap(); + let quote = a.new_number(1.into()).unwrap(); + let mut list = a.nil(); + + for _ in 0..500 { + list = a.new_pair(a.nil(), list).unwrap(); + } + + for i in 0..10000 { + // make the atom longer as a function of i + list = a.new_pair(a.nil(), list).unwrap(); + let quotation = a.new_pair(quote, list).unwrap(); + let call = a.new_pair(quotation, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + let start = Instant::now(); + run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let duration = start.elapsed(); + let sample = (i as f64, duration.as_nanos() as f64); + writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); + samples.push(sample); + } + + linear_regression_of(&samples).expect("linreg failed") +} + + const PER_BYTE_COST: u32 = 1; const PER_ARG_COST: u32 = 2; const NESTING_BASE_COST: u32 = 4; const EXPONENTIAL_COST: u32 = 8; const LARGE_BUFFERS: u32 = 16; const ALLOW_FAILURE: u32 = 32; +const LIST_LENGTH_COST: u32 = 64; +const ALT_PER_BYTE_COST: u32 = 128; struct Operator { opcode: u32, @@ -513,10 +578,10 @@ pub fn main() { }, Operator { opcode: 65, - name: "sha256tree (atom)", + name: "sha256tree", arg: Placeholder::SingleArg(None), extra: None, - flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, + flags: NESTING_BASE_COST | ALT_PER_BYTE_COST | LARGE_BUFFERS | LIST_LENGTH_COST, }, ]; @@ -541,6 +606,13 @@ pub fn main() { println!(" time: per-byte: {time_per_byte:.2}ns"); println!(" cost: per-byte: {:.0}", time_per_byte * cost_scale); time_per_byte + } else if (op.flags & ALT_PER_BYTE_COST) != 0 { + let mut output = maybe_open(options.plot, op.name, "per-byte.log"); + let (slope, _intercept) = time_per_byte_for_atom(&mut a, &mut output); + let cost = slope * cost_scale; + println!(" time: per-byte: {slope:.2}ns"); + println!(" cost: per-byte: {:.0}", cost); + slope } else { 0.0 }; @@ -570,7 +642,7 @@ pub fn main() { println!(" time: base: {base_call_time:.2}ns"); println!( " cost: base: {:.0}", - (base_call_time * base_cost_scale).max(100) + (base_call_time * base_cost_scale).max(100.0) ); base_call_time }; @@ -590,7 +662,7 @@ pub fn main() { op.name, "per-arg", ); - } else if (op.flags & PER_BYTE_COST) != 0 { + } else if (op.flags & PER_BYTE_COST) != 0 || (op.flags & ALT_PER_BYTE_COST) != 0 { write_gnuplot_header(&mut *gnuplot, op, "per-byte", "num bytes"); print_plot( &mut *gnuplot, @@ -600,6 +672,23 @@ pub fn main() { "per-byte", ); } + if (op.flags & LIST_LENGTH_COST) != 0 { + write_gnuplot_header(&mut *gnuplot, op, "per-pair", "num pairs"); + let mut output = maybe_open(options.plot, op.name, "per-list.log"); + let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); + let cost = slope * cost_scale; + println!( + "list length slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", + slope, intercept, cost + ); + print_plot( + &mut *gnuplot, + &slope, + &intercept, + op.name, + "per-pair", + ); + } } if options.plot { println!("To generate plots, run:\n (cd measurements; gnuplot gen-graphs.gnuplot)"); From 928afac5b82327dfc5a7edd51f17259d971f6b50 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 14 Nov 2025 15:36:49 +0000 Subject: [PATCH 049/110] finish integration --- tools/src/bin/benchmark-clvm-cost.rs | 11 +-- tools/src/bin/sha256tree-benching.rs | 111 --------------------------- 2 files changed, 2 insertions(+), 120 deletions(-) delete mode 100644 tools/src/bin/sha256tree-benching.rs diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index ab0222845..6a8c83690 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -329,7 +329,6 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 linear_regression_of(&samples).expect("linreg failed") } - const PER_BYTE_COST: u32 = 1; const PER_ARG_COST: u32 = 2; const NESTING_BASE_COST: u32 = 4; @@ -674,20 +673,14 @@ pub fn main() { } if (op.flags & LIST_LENGTH_COST) != 0 { write_gnuplot_header(&mut *gnuplot, op, "per-pair", "num pairs"); - let mut output = maybe_open(options.plot, op.name, "per-list.log"); + let mut output = maybe_open(options.plot, op.name, "per-pair.log"); let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); let cost = slope * cost_scale; println!( "list length slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", slope, intercept, cost ); - print_plot( - &mut *gnuplot, - &slope, - &intercept, - op.name, - "per-pair", - ); + print_plot(&mut *gnuplot, &slope, &intercept, op.name, "per-pair"); } } if options.plot { diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs deleted file mode 100644 index 182e1e8dd..000000000 --- a/tools/src/bin/sha256tree-benching.rs +++ /dev/null @@ -1,111 +0,0 @@ -use clvmr::chia_dialect::ChiaDialect; -use clvmr::run_program::run_program; -use std::fs::File; -use std::io::Write; -use std::time::Instant; - -use linreg::linear_regression_of; - -// bring in your existing code -use clvmr::allocator::Allocator; - -fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { - let mut samples = Vec::<(f64, f64)>::new(); - let dialect = ChiaDialect::new(0x0200); // enable shatree - - let op_code = a.new_number(63.into()).unwrap(); - let quote = a.new_number(1.into()).unwrap(); - let mut atom_str = "ff".repeat(10_000); - let checkpoint = a.checkpoint(); - - for i in 0..100000 { - // make the atom longer as a function of i - atom_str.push_str(&((i % 89) + 10).to_string().repeat(32)); // just to mix it up - let atom = a.new_atom(&hex::decode(&atom_str).unwrap()).unwrap(); - // let args = a.new_pair(atom, a.nil()).unwrap(); - let args = a.new_pair(quote, atom).unwrap(); - let call = a.new_pair(args, a.nil()).unwrap(); - let call = a.new_pair(op_code, call).unwrap(); - let start = Instant::now(); - run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); - let duration = start.elapsed(); - let sample = (i as f64, duration.as_nanos() as f64); - writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); - samples.push(sample); - - a.restore_checkpoint(&checkpoint); - } - - linear_regression_of(&samples).expect("linreg failed") -} - -fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { - let mut samples = Vec::<(f64, f64)>::new(); - let dialect = ChiaDialect::new(0x0200); // enable shatree - - let op_code = a.new_number(63.into()).unwrap(); - let quote = a.new_number(1.into()).unwrap(); - let mut list = a.nil(); - - for _ in 0..500 { - list = a.new_pair(a.nil(), list).unwrap(); - } - - for i in 0..100000 { - // make the atom longer as a function of i - list = a.new_pair(a.nil(), list).unwrap(); - let quotation = a.new_pair(quote, list).unwrap(); - let call = a.new_pair(quotation, a.nil()).unwrap(); - let call = a.new_pair(op_code, call).unwrap(); - let start = Instant::now(); - run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); - let duration = start.elapsed(); - let sample = (i as f64, duration.as_nanos() as f64); - writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); - samples.push(sample); - } - - linear_regression_of(&samples).expect("linreg failed") -} - -fn main() -> std::io::Result<()> { - let mut output = File::create("sha256tree_costs.tsv")?; - - writeln!(output, "# type\tx\ty")?; - // this "magic" scaling depends on the computer you run the tests on. - // It's calibrated against the timing of point_add, which has a cost - let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; - - // base call cost is covered in benchmark-clvm-cost so not included here - - // cost atom sizes - { - let mut a = Allocator::new(); - let (slope, intercept) = time_per_byte_for_atom(&mut a, &mut output); - let cost = slope * cost_scale; - writeln!(output, "\n# atom_slope\t{:.9}", slope)?; - writeln!(output, "\n# atom_slope * cost_scale\t{:?}", cost)?; - writeln!(output, "# atom_intercept\t{:.9}\n", intercept)?; - println!( - "atom byte slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", - slope, intercept, cost - ); - } - - // cost list / cons boxes - { - let mut a = Allocator::new(); - let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); - let cost = slope * cost_scale; - writeln!(output, "\n# pair_slope\t{:.9}", slope)?; - writeln!(output, "\n# pair_slope * cost_scale\t{:?}", cost)?; - writeln!(output, "# pair_intercept\t{:.9}\n", intercept)?; - println!( - "list length slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", - slope, intercept, cost - ); - } - - println!("Results written to sha256tree_costs.tsv"); - Ok(()) -} From 8cab362d0039fb7f55aa80606b334f8fbcbce615 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 10:56:28 +0000 Subject: [PATCH 050/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 6a8c83690..36aa2b67d 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -274,7 +274,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree let op_code = a.new_number(63.into()).unwrap(); - let quote = a.new_number(1.into()).unwrap(); + let quote = a.one(); let mut atom_str = "ff".repeat(10_000); let checkpoint = a.checkpoint(); From 1696f7d2fcf818ab2ae151a837fcf805fb6e61fc Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 10:56:40 +0000 Subject: [PATCH 051/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 36aa2b67d..43adec9cc 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -273,7 +273,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree - let op_code = a.new_number(63.into()).unwrap(); + let op_code = a.new_small_number(63).unwrap(); let quote = a.one(); let mut atom_str = "ff".repeat(10_000); let checkpoint = a.checkpoint(); From d6806f5cf611167f545176032548430b7b85cdc6 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 10:58:22 +0000 Subject: [PATCH 052/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 43adec9cc..d49f20386 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -304,7 +304,7 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree - let op_code = a.new_number(63.into()).unwrap(); + let op_code = a.new_small_number(63).unwrap(); let quote = a.new_number(1.into()).unwrap(); let mut list = a.nil(); From 17abaf46393475f2416615d300492ea76ec2913b Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 10:59:23 +0000 Subject: [PATCH 053/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index d49f20386..88b2485e8 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -609,8 +609,8 @@ pub fn main() { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); let (slope, _intercept) = time_per_byte_for_atom(&mut a, &mut output); let cost = slope * cost_scale; - println!(" time: per-byte: {slope:.2}ns"); - println!(" cost: per-byte: {:.0}", cost); + println!(" time: per-32bytes: {slope:.2}ns"); + println!(" cost: per-32bytes: {:.0}", cost); slope } else { 0.0 From 628fe37278c8117a3c26c550c9cbad50894d58ce Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Sat, 15 Nov 2025 11:07:50 +0000 Subject: [PATCH 054/110] a.one() --- tools/src/bin/benchmark-clvm-cost.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 88b2485e8..d0178d819 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -305,7 +305,7 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree let op_code = a.new_small_number(63).unwrap(); - let quote = a.new_number(1.into()).unwrap(); + let quote = a.one(); let mut list = a.nil(); for _ in 0..500 { @@ -609,8 +609,8 @@ pub fn main() { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); let (slope, _intercept) = time_per_byte_for_atom(&mut a, &mut output); let cost = slope * cost_scale; - println!(" time: per-32bytes: {slope:.2}ns"); - println!(" cost: per-32bytes: {:.0}", cost); + println!(" time: per-byte: {slope:.2}ns"); + println!(" cost: per-byte: {:.0}", cost); slope } else { 0.0 From 1e3ba494302bed5620bb87439d8e6624a951b8d2 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:08:23 +0000 Subject: [PATCH 055/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 88b2485e8..f723d8927 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -313,7 +313,7 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 } for i in 0..10000 { - // make the atom longer as a function of i + // make the list longer list = a.new_pair(a.nil(), list).unwrap(); let quotation = a.new_pair(quote, list).unwrap(); let call = a.new_pair(quotation, a.nil()).unwrap(); From 94c1b34718a8710a1205d21c8eeb85562855ca45 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:09:12 +0000 Subject: [PATCH 056/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index f723d8927..6f5bcbd57 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -275,7 +275,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 let op_code = a.new_small_number(63).unwrap(); let quote = a.one(); - let mut atom_str = "ff".repeat(10_000); + let mut atom = [0xff].repeat(10_000); let checkpoint = a.checkpoint(); for i in 0..10000 { From 77dab85a27ef3f6274026e19ce06c73038f26600 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 11:09:22 +0000 Subject: [PATCH 057/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 6f5bcbd57..6729a2b2b 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -280,9 +280,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 for i in 0..10000 { // make the atom longer as a function of i - atom_str.push_str(&((i % 89) + 10).to_string().repeat(32)); // just to mix it up - let atom = a.new_atom(&hex::decode(&atom_str).unwrap()).unwrap(); - // let args = a.new_pair(atom, a.nil()).unwrap(); + atom.append([(i % 89 + 10) as u8].repeat(32)) // just to mix it up let args = a.new_pair(quote, atom).unwrap(); let call = a.new_pair(args, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); From d2aad70000279df3fc1898b542cd41f9d44ea6fb Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Sat, 15 Nov 2025 11:15:33 +0000 Subject: [PATCH 058/110] add comments --- tools/src/bin/benchmark-clvm-cost.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 712d90126..c465beaac 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -280,7 +280,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 for i in 0..10000 { // make the atom longer as a function of i - atom.append([(i % 89 + 10) as u8].repeat(32)) // just to mix it up + atom.append([(i % 89 + 10) as u8].repeat(32)); // just to mix it up let args = a.new_pair(quote, atom).unwrap(); let call = a.new_pair(args, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); @@ -327,13 +327,21 @@ fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 linear_regression_of(&samples).expect("linreg failed") } +// cost one argument with increasing amount of bytes const PER_BYTE_COST: u32 = 1; +// cost multiple arguments with increasing amount of arguments const PER_ARG_COST: u32 = 2; +// cost the base cost by doing f(f(f(x))) instead of arg amounts const NESTING_BASE_COST: u32 = 4; +// EXPONENTIAL_COST is for operators where the cost grows exponentially with the size of the arguments. const EXPONENTIAL_COST: u32 = 8; +// make the buffers extra large, 1000x the size const LARGE_BUFFERS: u32 = 16; +// permit the operator to fail in tests const ALLOW_FAILURE: u32 = 32; +// increase the length of a list for the argument for the operator const LIST_LENGTH_COST: u32 = 64; +// increase byte size by 32 per step when running the linear regression const ALT_PER_BYTE_COST: u32 = 128; struct Operator { From 579371735ae53e1bef86bde622c425f71d44c4c8 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Sat, 15 Nov 2025 12:06:23 +0000 Subject: [PATCH 059/110] fix formatting and errors --- tools/src/bin/benchmark-clvm-cost.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index c465beaac..d0d52134c 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -280,8 +280,9 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 for i in 0..10000 { // make the atom longer as a function of i - atom.append([(i % 89 + 10) as u8].repeat(32)); // just to mix it up - let args = a.new_pair(quote, atom).unwrap(); + atom.append(&mut [(i % 89 + 10) as u8].repeat(32)); // just to mix it up + let atom_node = a.new_atom(&atom).unwrap(); + let args = a.new_pair(quote, atom_node).unwrap(); let call = a.new_pair(args, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); let start = Instant::now(); @@ -682,10 +683,11 @@ pub fn main() { let mut output = maybe_open(options.plot, op.name, "per-pair.log"); let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); let cost = slope * cost_scale; - println!( - "list length slope: {:.9}, intercept: {:.9}, cost (slope * cost_scale): {:.9}", - slope, intercept, cost - ); + + println!(" time: per-pair: {:.2}ns", slope); + println!(" cost: per-pair: {:.0}", cost); + println!(" intercept: {:.2}", intercept); + print_plot(&mut *gnuplot, &slope, &intercept, op.name, "per-pair"); } } From 5b2ee2d5b1eeef1be868814dcbd38bff48b0b4dc Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Sat, 15 Nov 2025 16:03:18 +0000 Subject: [PATCH 060/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index d0d52134c..68c2ac268 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -684,8 +684,8 @@ pub fn main() { let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); let cost = slope * cost_scale; - println!(" time: per-pair: {:.2}ns", slope); - println!(" cost: per-pair: {:.0}", cost); + println!(" time: per-node: {:.2}ns", slope); + println!(" cost: per-node: {:.0}", cost); println!(" intercept: {:.2}", intercept); print_plot(&mut *gnuplot, &slope, &intercept, op.name, "per-pair"); From a613348cdcdd284101ac15af9974317243ceca7a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Sun, 16 Nov 2025 09:05:20 +0000 Subject: [PATCH 061/110] re-add 32byte tag to output --- tools/src/bin/benchmark-clvm-cost.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 68c2ac268..c9b651d89 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -616,8 +616,8 @@ pub fn main() { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); let (slope, _intercept) = time_per_byte_for_atom(&mut a, &mut output); let cost = slope * cost_scale; - println!(" time: per-byte: {slope:.2}ns"); - println!(" cost: per-byte: {:.0}", cost); + println!(" time: per-32byte: {slope:.2}ns"); + println!(" cost: per-32byte: {:.0}", cost); slope } else { 0.0 From 29943838ae8cd477b30b3e458791c539b8516283 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Sun, 16 Nov 2025 09:07:18 +0000 Subject: [PATCH 062/110] Revert "dialect expansions" This reverts commit 8e58fa8c88eef2a3a2e5eda6810c8eb08bdac069. --- src/chia_dialect.rs | 8 +------- src/dialect.rs | 3 --- src/run_program.rs | 3 +-- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/chia_dialect.rs b/src/chia_dialect.rs index 70bbd3350..7f1ab7414 100644 --- a/src/chia_dialect.rs +++ b/src/chia_dialect.rs @@ -81,9 +81,6 @@ impl Dialect for ChiaDialect { // Keccak is allowed as if it were a default operator, inside of the softfork guard. OperatorSet::Keccak => ENABLE_KECCAK_OPS_OUTSIDE_GUARD, - - // Sha256tree is enabled as if it were a default operator - OperatorSet::Sha256tree => ENABLE_SHA256_TREE | ENABLE_KECCAK_OPS_OUTSIDE_GUARD, }; let op_len = allocator.atom_len(o); @@ -202,10 +199,7 @@ impl Dialect for ChiaDialect { // Extension 1 is for the keccak256 operator. 1 => OperatorSet::Keccak, - // Extension 2 is for the sha256tree operator. - 2 => OperatorSet::Sha256tree, - - // Extensions 3 and beyond are considered invalid by the mempool. + // Extensions 2 and beyond are considered invalid by the mempool. // However, all future extensions are valid in consensus mode and reserved for future softforks. _ => OperatorSet::Default, } diff --git a/src/dialect.rs b/src/dialect.rs index 722d906e7..cefaadd9d 100644 --- a/src/dialect.rs +++ b/src/dialect.rs @@ -16,9 +16,6 @@ pub enum OperatorSet { /// The keccak256 operator, which is only available inside the softfork guard. /// This uses softfork extension 1, which does not conflict with the BLS fork. Keccak, - - // The sha256tree operator, which is only activated after a hardfork - Sha256tree, } pub trait Dialect { diff --git a/src/run_program.rs b/src/run_program.rs index e23a6a9d4..1dac559c7 100644 --- a/src/run_program.rs +++ b/src/run_program.rs @@ -1432,7 +1432,7 @@ mod tests { #[case] err: &'static str, #[values(0)] flags: u32, #[values(false, true)] mempool: bool, - #[values(0, 1, 2, 3)] test_ext: u8, + #[values(0, 1, 2)] test_ext: u8, ) { let (cost, enabled, hard_fork_flag) = fields; let softfork_prg = @@ -1445,7 +1445,6 @@ mod tests { let ext_enabled = match test_ext { 0 => true, // BLS 1 => true, // KECCAK - 2 => true, // SHA256TREE _ => false, }; From c8c54b4a2907b16d1369c19dfe9d1c45e5be3fd3 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Sun, 16 Nov 2025 09:08:32 +0000 Subject: [PATCH 063/110] revert test change in softfork --- src/run_program.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/run_program.rs b/src/run_program.rs index 1dac559c7..06afbf29a 100644 --- a/src/run_program.rs +++ b/src/run_program.rs @@ -1161,7 +1161,7 @@ mod tests { // without the flag to enable the keccak extensions, it's an unknown extension RunProgramTest { - prg: "(softfork (q . 161) (q . 3) (q . (q . 42)) (q . ()))", + prg: "(softfork (q . 161) (q . 2) (q . (q . 42)) (q . ()))", args: "()", flags: NO_UNKNOWN_OPS, result: None, From 498915a2cdabbeb3c7c78da16e473b74de0be89a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 18 Nov 2025 15:42:32 +0000 Subject: [PATCH 064/110] checkpoint --- tools/src/bin/sha256tree-benching.rs | 152 +++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 tools/src/bin/sha256tree-benching.rs diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs new file mode 100644 index 000000000..6942c25b0 --- /dev/null +++ b/tools/src/bin/sha256tree-benching.rs @@ -0,0 +1,152 @@ +use clvmr::allocator::{Allocator, NodePtr}; +use clvmr::chia_dialect::{ChiaDialect, ENABLE_SHA256_TREE}; +use clvmr::run_program::run_program; +use linreg::linear_regression_of; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::time::Instant; + +fn time_per_byte_for_atom( + a: &mut Allocator, + sha_prog: NodePtr, + mut output_native: impl Write, + mut output_clvm: impl Write, +) -> ((f64, f64), (f64, f64)) { + let mut samples = Vec::<(f64, f64)>::new(); + let mut samples_clvm = Vec::<(f64, f64)>::new(); + let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); + + let op_code = a.new_small_number(63).unwrap(); + let quote = a.one(); + let mut atom = vec![0xff; 10_000]; + let checkpoint = a.checkpoint(); + + for i in 0..10_000 { + atom.extend(std::iter::repeat(((i % 89) + 10) as u8).take(32)); + + let atom_node = a.new_atom(&atom).unwrap(); + let args = a.new_pair(quote, atom_node).unwrap(); + let call = a.new_pair(args, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + + let start = Instant::now(); + run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let duration = start.elapsed().as_nanos() as f64; + writeln!(output_native, "{}\t{}", i, duration).unwrap(); + samples.push((i as f64, duration)); + + let start = Instant::now(); + run_program(a, &dialect, sha_prog, atom_node, 11000000000).unwrap(); + let duration = start.elapsed().as_nanos() as f64; + writeln!(output_clvm, "{}\t{}", i, duration).unwrap(); + samples_clvm.push((i as f64, duration)); + + a.restore_checkpoint(&checkpoint); + } + + ( + linear_regression_of(&samples).unwrap(), + linear_regression_of(&samples_clvm).unwrap(), + ) +} + +fn time_per_cons_for_list( + a: &mut Allocator, + sha_prog: NodePtr, + mut output_native: impl Write, + mut output_clvm: impl Write, +) -> ((f64, f64), (f64, f64)) { + let mut samples_native = Vec::<(f64, f64)>::new(); + let mut samples_clvm = Vec::<(f64, f64)>::new(); + let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); + + let op_code = a.new_small_number(63).unwrap(); + let quote = a.one(); + let mut list = a.nil(); + let checkpoint = a.checkpoint(); + + for _ in 0..500 { + list = a.new_pair(a.nil(), list).unwrap(); + } + + for i in 0..10_000 { + list = a.new_pair(a.nil(), list).unwrap(); + + let quoted = a.new_pair(quote, list).unwrap(); + let call = a.new_pair(quoted, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + + // native + let start = Instant::now(); + run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let t = start.elapsed().as_nanos() as f64; + writeln!(output_native, "{}\t{}", i, t).unwrap(); + samples_native.push((i as f64, t)); + + // clvm + let start = Instant::now(); + run_program(a, &dialect, sha_prog, list, 11000000000).unwrap(); + let t = start.elapsed().as_nanos() as f64; + writeln!(output_clvm, "{}\t{}", i, t).unwrap(); + samples_clvm.push((i as f64, t)); + + a.restore_checkpoint(&checkpoint); + } + + ( + linear_regression_of(&samples_native).unwrap(), + linear_regression_of(&samples_clvm).unwrap(), + ) +} + +fn main() { + let shaprogbytes = hex::decode( + "ff02ffff01ff02ff02ffff04ff02ffff04ff03ff80808080ffff04ffff01ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff02ffff04ff02ffff04ff09ff80808080ffff02ff02ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080" + ).unwrap(); + + let mut a = Allocator::new(); + let shaprog = a.new_atom(&shaprogbytes).unwrap(); + + let atom_native = BufWriter::new(File::create("atom_native.dat").unwrap()); + let atom_clvm = BufWriter::new(File::create("atom_clvm.dat").unwrap()); + let cons_native = BufWriter::new(File::create("cons_native.dat").unwrap()); + let cons_clvm = BufWriter::new(File::create("cons_clvm.dat").unwrap()); + + let (atom_nat_lin, atom_clvm_lin) = + time_per_byte_for_atom(&mut a, shaprog, atom_native, atom_clvm); + + let (cons_nat_lin, cons_clvm_lin) = + time_per_cons_for_list(&mut a, shaprog, cons_native, cons_clvm); + + println!("Atom native slope: {:.4}", atom_nat_lin.0); + println!("Atom C L V M slope: {:.4}", atom_clvm_lin.0); + println!("Cons native slope: {:.4}", cons_nat_lin.0); + println!("Cons C L V M slope: {:.4}", cons_clvm_lin.0); + + let mut gp = File::create("plots.gnuplot").unwrap(); + writeln!( + gp, + r#" +set terminal png size 1200,900 + +set output "atom_bench.png" +set title "Time per Byte (Atom SHA-tree)" +set xlabel "Iteration" +set ylabel "Time (ns)" +plot \ + "atom_native.dat" using 1:2 with lines title "native", \ + "atom_clvm.dat" using 1:2 with lines title "clvm" + +set output "cons_bench.png" +set title "Time per Cons Cell (List SHA-tree)" +set xlabel "Iteration" +set ylabel "Time (ns)" +plot \ + "cons_native.dat" using 1:2 with lines title "native", \ + "cons_clvm.dat" using 1:2 with lines title "clvm" +"# + ) + .unwrap(); + + println!("Run with: gnuplot plots.gnuplot"); +} From e119aab9a187a4d08761fc71157d5e64ddd669c5 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 18 Nov 2025 17:41:53 +0000 Subject: [PATCH 065/110] fix error --- tools/src/bin/sha256tree-benching.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 6942c25b0..1f40a5614 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,6 +1,7 @@ use clvmr::allocator::{Allocator, NodePtr}; use clvmr::chia_dialect::{ChiaDialect, ENABLE_SHA256_TREE}; use clvmr::run_program::run_program; +use clvmr::serde::node_from_bytes; use linreg::linear_regression_of; use std::fs::File; use std::io::{BufWriter, Write}; @@ -19,7 +20,6 @@ fn time_per_byte_for_atom( let op_code = a.new_small_number(63).unwrap(); let quote = a.one(); let mut atom = vec![0xff; 10_000]; - let checkpoint = a.checkpoint(); for i in 0..10_000 { atom.extend(std::iter::repeat(((i % 89) + 10) as u8).take(32)); @@ -29,19 +29,19 @@ fn time_per_byte_for_atom( let call = a.new_pair(args, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); + let checkpoint = a.checkpoint(); let start = Instant::now(); run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); let duration = start.elapsed().as_nanos() as f64; writeln!(output_native, "{}\t{}", i, duration).unwrap(); samples.push((i as f64, duration)); + a.restore_checkpoint(&checkpoint); let start = Instant::now(); run_program(a, &dialect, sha_prog, atom_node, 11000000000).unwrap(); let duration = start.elapsed().as_nanos() as f64; writeln!(output_clvm, "{}\t{}", i, duration).unwrap(); samples_clvm.push((i as f64, duration)); - - a.restore_checkpoint(&checkpoint); } ( @@ -63,13 +63,12 @@ fn time_per_cons_for_list( let op_code = a.new_small_number(63).unwrap(); let quote = a.one(); let mut list = a.nil(); - let checkpoint = a.checkpoint(); for _ in 0..500 { list = a.new_pair(a.nil(), list).unwrap(); } - for i in 0..10_000 { + for i in 0..10_00 { list = a.new_pair(a.nil(), list).unwrap(); let quoted = a.new_pair(quote, list).unwrap(); @@ -77,6 +76,7 @@ fn time_per_cons_for_list( let call = a.new_pair(op_code, call).unwrap(); // native + let checkpoint = a.checkpoint(); let start = Instant::now(); run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); let t = start.elapsed().as_nanos() as f64; @@ -84,13 +84,12 @@ fn time_per_cons_for_list( samples_native.push((i as f64, t)); // clvm + a.restore_checkpoint(&checkpoint); let start = Instant::now(); run_program(a, &dialect, sha_prog, list, 11000000000).unwrap(); let t = start.elapsed().as_nanos() as f64; writeln!(output_clvm, "{}\t{}", i, t).unwrap(); samples_clvm.push((i as f64, t)); - - a.restore_checkpoint(&checkpoint); } ( @@ -105,7 +104,7 @@ fn main() { ).unwrap(); let mut a = Allocator::new(); - let shaprog = a.new_atom(&shaprogbytes).unwrap(); + let shaprog = node_from_bytes(&mut a, shaprogbytes.as_ref()).unwrap(); let atom_native = BufWriter::new(File::create("atom_native.dat").unwrap()); let atom_clvm = BufWriter::new(File::create("atom_clvm.dat").unwrap()); @@ -119,9 +118,9 @@ fn main() { time_per_cons_for_list(&mut a, shaprog, cons_native, cons_clvm); println!("Atom native slope: {:.4}", atom_nat_lin.0); - println!("Atom C L V M slope: {:.4}", atom_clvm_lin.0); + println!("Atom CLVM slope: {:.4}", atom_clvm_lin.0); println!("Cons native slope: {:.4}", cons_nat_lin.0); - println!("Cons C L V M slope: {:.4}", cons_clvm_lin.0); + println!("Cons CLVM slope: {:.4}", cons_clvm_lin.0); let mut gp = File::create("plots.gnuplot").unwrap(); writeln!( From dc568c361199c373a113f1fa92119dd573162adb Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 18 Nov 2025 17:48:01 +0000 Subject: [PATCH 066/110] rename to list --- tools/src/bin/sha256tree-benching.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 1f40a5614..7997199b1 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -119,8 +119,8 @@ fn main() { println!("Atom native slope: {:.4}", atom_nat_lin.0); println!("Atom CLVM slope: {:.4}", atom_clvm_lin.0); - println!("Cons native slope: {:.4}", cons_nat_lin.0); - println!("Cons CLVM slope: {:.4}", cons_clvm_lin.0); + println!("List native slope: {:.4}", cons_nat_lin.0); + println!("List CLVM slope: {:.4}", cons_clvm_lin.0); let mut gp = File::create("plots.gnuplot").unwrap(); writeln!( From 481047849eab70b24762c0b60901ee17709889dd Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 19 Nov 2025 14:07:54 +0000 Subject: [PATCH 067/110] print cost --- tools/src/bin/sha256tree-benching.rs | 184 +++++++++++++++++++-------- 1 file changed, 134 insertions(+), 50 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 7997199b1..faf2a8ed7 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -10,11 +10,20 @@ use std::time::Instant; fn time_per_byte_for_atom( a: &mut Allocator, sha_prog: NodePtr, - mut output_native: impl Write, - mut output_clvm: impl Write, -) -> ((f64, f64), (f64, f64)) { - let mut samples = Vec::<(f64, f64)>::new(); - let mut samples_clvm = Vec::<(f64, f64)>::new(); + mut output_native_time: impl Write, + mut output_clvm_time: impl Write, + mut output_native_cost: impl Write, + mut output_clvm_cost: impl Write, +) -> ( + (f64, f64), + (f64, f64), // time slopes + (f64, f64), + (f64, f64), // cost slopes +) { + let mut samples_time_native = Vec::<(f64, f64)>::new(); + let mut samples_time_clvm = Vec::<(f64, f64)>::new(); + let mut samples_cost_native = Vec::<(f64, f64)>::new(); + let mut samples_cost_clvm = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); let op_code = a.new_small_number(63).unwrap(); @@ -30,34 +39,56 @@ fn time_per_byte_for_atom( let call = a.new_pair(op_code, call).unwrap(); let checkpoint = a.checkpoint(); + + // native let start = Instant::now(); - run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); + let cost = run_program(a, &dialect, call, a.nil(), 11_000_000_000) + .unwrap() + .0; let duration = start.elapsed().as_nanos() as f64; - writeln!(output_native, "{}\t{}", i, duration).unwrap(); - samples.push((i as f64, duration)); + writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); + writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); + samples_time_native.push((i as f64, duration)); + samples_cost_native.push((i as f64, cost as f64)); + // clvm a.restore_checkpoint(&checkpoint); let start = Instant::now(); - run_program(a, &dialect, sha_prog, atom_node, 11000000000).unwrap(); + let cost = run_program(a, &dialect, sha_prog, atom_node, 11_000_000_000) + .unwrap() + .0; let duration = start.elapsed().as_nanos() as f64; - writeln!(output_clvm, "{}\t{}", i, duration).unwrap(); - samples_clvm.push((i as f64, duration)); + writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); + writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); + samples_time_clvm.push((i as f64, duration)); + samples_cost_clvm.push((i as f64, cost as f64)); } ( - linear_regression_of(&samples).unwrap(), - linear_regression_of(&samples_clvm).unwrap(), + linear_regression_of(&samples_time_native).unwrap(), + linear_regression_of(&samples_time_clvm).unwrap(), + linear_regression_of(&samples_cost_native).unwrap(), + linear_regression_of(&samples_cost_clvm).unwrap(), ) } fn time_per_cons_for_list( a: &mut Allocator, sha_prog: NodePtr, - mut output_native: impl Write, - mut output_clvm: impl Write, -) -> ((f64, f64), (f64, f64)) { - let mut samples_native = Vec::<(f64, f64)>::new(); - let mut samples_clvm = Vec::<(f64, f64)>::new(); + mut output_native_time: impl Write, + mut output_clvm_time: impl Write, + mut output_native_cost: impl Write, + mut output_clvm_cost: impl Write, +) -> ( + (f64, f64), + (f64, f64), // time slopes + (f64, f64), + (f64, f64), // cost slopes +) { + let mut samples_time_native = Vec::<(f64, f64)>::new(); + let mut samples_time_clvm = Vec::<(f64, f64)>::new(); + let mut samples_cost_native = Vec::<(f64, f64)>::new(); + let mut samples_cost_clvm = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); let op_code = a.new_small_number(63).unwrap(); @@ -68,33 +99,43 @@ fn time_per_cons_for_list( list = a.new_pair(a.nil(), list).unwrap(); } - for i in 0..10_00 { + for i in 0..1000 { list = a.new_pair(a.nil(), list).unwrap(); - - let quoted = a.new_pair(quote, list).unwrap(); - let call = a.new_pair(quoted, a.nil()).unwrap(); + let q = a.new_pair(quote, list).unwrap(); + let call = a.new_pair(q, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); - // native let checkpoint = a.checkpoint(); + + // native let start = Instant::now(); - run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); - let t = start.elapsed().as_nanos() as f64; - writeln!(output_native, "{}\t{}", i, t).unwrap(); - samples_native.push((i as f64, t)); + let cost = run_program(a, &dialect, call, a.nil(), 11_000_000_000) + .unwrap() + .0; + let duration = start.elapsed().as_nanos() as f64; + writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); + writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); + samples_time_native.push((i as f64, duration)); + samples_cost_native.push((i as f64, cost as f64)); // clvm a.restore_checkpoint(&checkpoint); let start = Instant::now(); - run_program(a, &dialect, sha_prog, list, 11000000000).unwrap(); - let t = start.elapsed().as_nanos() as f64; - writeln!(output_clvm, "{}\t{}", i, t).unwrap(); - samples_clvm.push((i as f64, t)); + let cost = run_program(a, &dialect, sha_prog, list, 11_000_000_000) + .unwrap() + .0; + let duration = start.elapsed().as_nanos() as f64; + writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); + writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); + samples_time_clvm.push((i as f64, duration)); + samples_cost_clvm.push((i as f64, cost as f64)); } ( - linear_regression_of(&samples_native).unwrap(), - linear_regression_of(&samples_clvm).unwrap(), + linear_regression_of(&samples_time_native).unwrap(), + linear_regression_of(&samples_time_clvm).unwrap(), + linear_regression_of(&samples_cost_native).unwrap(), + linear_regression_of(&samples_cost_clvm).unwrap(), ) } @@ -106,22 +147,48 @@ fn main() { let mut a = Allocator::new(); let shaprog = node_from_bytes(&mut a, shaprogbytes.as_ref()).unwrap(); - let atom_native = BufWriter::new(File::create("atom_native.dat").unwrap()); - let atom_clvm = BufWriter::new(File::create("atom_clvm.dat").unwrap()); - let cons_native = BufWriter::new(File::create("cons_native.dat").unwrap()); - let cons_clvm = BufWriter::new(File::create("cons_clvm.dat").unwrap()); - - let (atom_nat_lin, atom_clvm_lin) = - time_per_byte_for_atom(&mut a, shaprog, atom_native, atom_clvm); - - let (cons_nat_lin, cons_clvm_lin) = - time_per_cons_for_list(&mut a, shaprog, cons_native, cons_clvm); - - println!("Atom native slope: {:.4}", atom_nat_lin.0); - println!("Atom CLVM slope: {:.4}", atom_clvm_lin.0); - println!("List native slope: {:.4}", cons_nat_lin.0); - println!("List CLVM slope: {:.4}", cons_clvm_lin.0); - + // Output files + let atom_native_time = BufWriter::new(File::create("atom_native.dat").unwrap()); + let atom_clvm_time = BufWriter::new(File::create("atom_clvm.dat").unwrap()); + let cons_native_time = BufWriter::new(File::create("cons_native.dat").unwrap()); + let cons_clvm_time = BufWriter::new(File::create("cons_clvm.dat").unwrap()); + + let atom_native_cost = BufWriter::new(File::create("atom_native_cost.dat").unwrap()); + let atom_clvm_cost = BufWriter::new(File::create("atom_clvm_cost.dat").unwrap()); + let cons_native_cost = BufWriter::new(File::create("cons_native_cost.dat").unwrap()); + let cons_clvm_cost = BufWriter::new(File::create("cons_clvm_cost.dat").unwrap()); + + let (atom_nat_t, atom_clvm_t, atom_nat_c, atom_clvm_c) = time_per_byte_for_atom( + &mut a, + shaprog, + atom_native_time, + atom_clvm_time, + atom_native_cost, + atom_clvm_cost, + ); + + let (cons_nat_t, cons_clvm_t, cons_nat_c, cons_clvm_c) = time_per_cons_for_list( + &mut a, + shaprog, + cons_native_time, + cons_clvm_time, + cons_native_cost, + cons_clvm_cost, + ); + + println!("atom results: "); + println!("Native time slope (ns): {:.4}", atom_nat_t.0); + println!("CLVM time slope (ns): {:.4}", atom_clvm_t.0); + println!("Native cost slope : {:.4}", atom_nat_c.0); + println!("CLVM cost slope : {:.4}", atom_clvm_c.0); + + println!("list results: "); + println!("Native time slope (ns): {:.4}", cons_nat_t.0); + println!("CLVM time slope (ns): {:.4}", cons_clvm_t.0); + println!("Native cost slope : {:.4}", cons_nat_c.0); + println!("CLVM cost slope : {:.4}", cons_clvm_c.0); + + // gnuplot script let mut gp = File::create("plots.gnuplot").unwrap(); writeln!( gp, @@ -136,6 +203,14 @@ plot \ "atom_native.dat" using 1:2 with lines title "native", \ "atom_clvm.dat" using 1:2 with lines title "clvm" +set output "atom_cost.png" +set title "Cost per Byte (Atom SHA-tree)" +set xlabel "Iteration" +set ylabel "Cost" +plot \ + "atom_native_cost.dat" using 1:2 with lines title "native", \ + "atom_clvm_cost.dat" using 1:2 with lines title "clvm" + set output "cons_bench.png" set title "Time per Cons Cell (List SHA-tree)" set xlabel "Iteration" @@ -143,9 +218,18 @@ set ylabel "Time (ns)" plot \ "cons_native.dat" using 1:2 with lines title "native", \ "cons_clvm.dat" using 1:2 with lines title "clvm" + +set output "cons_cost.png" +set title "Cost per Cons Cell (List SHA-tree)" +set xlabel "Iteration" +set ylabel "Cost" +plot \ + "cons_native_cost.dat" using 1:2 with lines title "native", \ + "cons_clvm_cost.dat" using 1:2 with lines title "clvm" "# ) .unwrap(); - println!("Run with: gnuplot plots.gnuplot"); + println!("\nData + plots complete. Generate graphs with:"); + println!(" gnuplot plots.gnuplot\n"); } From 52df50890ceac6403e1104b5f559baf85c58163f Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 19 Nov 2025 14:46:38 +0000 Subject: [PATCH 068/110] assert equal outputs --- tools/src/bin/sha256tree-benching.rs | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index faf2a8ed7..d4dfc4f6b 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -1,7 +1,7 @@ use clvmr::allocator::{Allocator, NodePtr}; use clvmr::chia_dialect::{ChiaDialect, ENABLE_SHA256_TREE}; use clvmr::run_program::run_program; -use clvmr::serde::node_from_bytes; +use clvmr::serde::{node_from_bytes, node_to_bytes}; use linreg::linear_regression_of; use std::fs::File; use std::io::{BufWriter, Write}; @@ -42,9 +42,9 @@ fn time_per_byte_for_atom( // native let start = Instant::now(); - let cost = run_program(a, &dialect, call, a.nil(), 11_000_000_000) - .unwrap() - .0; + let red = run_program(a, &dialect, call, a.nil(), 11_000_000_000).unwrap(); + let cost = red.0; + let result_1 = node_to_bytes(a, red.1).expect("should work"); let duration = start.elapsed().as_nanos() as f64; writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); @@ -54,9 +54,10 @@ fn time_per_byte_for_atom( // clvm a.restore_checkpoint(&checkpoint); let start = Instant::now(); - let cost = run_program(a, &dialect, sha_prog, atom_node, 11_000_000_000) - .unwrap() - .0; + let red = run_program(a, &dialect, sha_prog, atom_node, 11_000_000_000).unwrap(); + let cost = red.0; + let result_2 = node_to_bytes(a, red.1).expect("should work"); + assert_eq!(result_1, result_2); let duration = start.elapsed().as_nanos() as f64; writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); @@ -109,9 +110,9 @@ fn time_per_cons_for_list( // native let start = Instant::now(); - let cost = run_program(a, &dialect, call, a.nil(), 11_000_000_000) - .unwrap() - .0; + let red = run_program(a, &dialect, call, a.nil(), 11_000_000_000).unwrap(); + let cost = red.0; + let result_1 = node_to_bytes(a, red.1).expect("should work"); let duration = start.elapsed().as_nanos() as f64; writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); @@ -121,9 +122,11 @@ fn time_per_cons_for_list( // clvm a.restore_checkpoint(&checkpoint); let start = Instant::now(); - let cost = run_program(a, &dialect, sha_prog, list, 11_000_000_000) - .unwrap() - .0; + let red = run_program(a, &dialect, sha_prog, list, 11_000_000_000).unwrap(); + let cost = red.0; + let result_2 = node_to_bytes(a, red.1).expect("should work"); + assert_eq!(result_1, result_2); + let duration = start.elapsed().as_nanos() as f64; writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); From 3744787d21dcbacd898bd58e3a797d40bb943c86 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 20 Nov 2025 09:18:31 +0000 Subject: [PATCH 069/110] use non-cached --- src/sha_tree_op.rs | 4 +- src/treehash.rs | 108 +++++++++++++-------------- tools/src/bin/sha256tree-benching.rs | 6 +- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/src/sha_tree_op.rs b/src/sha_tree_op.rs index 69da0ed51..7706350fe 100644 --- a/src/sha_tree_op.rs +++ b/src/sha_tree_op.rs @@ -6,6 +6,6 @@ use crate::treehash::*; pub fn op_sha256_tree(a: &mut Allocator, input: NodePtr, max_cost: Cost) -> Response { let [n] = get_args::<1>(a, input, "sha256tree")?; - let mut cache = TreeCache::default(); - tree_hash_cached_costed(a, n, &mut cache, max_cost) + // let mut cache = TreeCache::default(); + tree_hash_costed(a, n, max_cost) } diff --git a/src/treehash.rs b/src/treehash.rs index 1dbe21c91..67d1b0a46 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -224,6 +224,60 @@ pub fn tree_hash_cached_costed( Ok(Reduction(cost, a.new_atom(&hashes[0])?)) } +// this function costs but does not cache +// we can use it to check that the cache is properly remembering costs +pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Response { + let mut hashes = Vec::new(); + let mut ops = vec![TreeOp::SExp(node)]; + + let mut cost = SHA256TREE_BASE_COST; + + while let Some(op) = ops.pop() { + match op { + TreeOp::SExp(node) => { + cost += SHA256TREE_COST_PER_NODE; + check_cost(cost, cost_left)?; + match a.node(node) { + NodeVisitor::Buffer(bytes) => { + increment_bytes(bytes.len() + 1, &mut cost); + check_cost(cost, cost_left)?; + let hash = tree_hash_atom(bytes); + hashes.push(hash); + } + NodeVisitor::U32(val) => { + increment_bytes(a.atom_len(node) + 1, &mut cost); + check_cost(cost, cost_left)?; + if (val as usize) < PRECOMPUTED_HASHES.len() { + hashes.push(PRECOMPUTED_HASHES[val as usize]); + } else { + hashes.push(tree_hash_atom(a.atom(node).as_ref())); + } + } + NodeVisitor::Pair(left, right) => { + increment_bytes(65, &mut cost); + check_cost(cost, cost_left)?; + + ops.push(TreeOp::Cons); + ops.push(TreeOp::SExp(left)); + ops.push(TreeOp::SExp(right)); + } + } + } + TreeOp::Cons => { + let first = hashes.pop().unwrap(); + let rest = hashes.pop().unwrap(); + hashes.push(tree_hash_pair(&first, &rest)); + } + TreeOp::ConsAddCacheCost(_, _) => unreachable!(), + } + } + + assert!(hashes.len() == 1); + cost += MALLOC_COST_PER_BYTE * 32; + check_cost(cost, cost_left)?; + Ok(Reduction(cost, a.new_atom(&hashes[0])?)) +} + // this function neither costs, nor caches // and it also returns bytes, rather than an Atom pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { @@ -268,60 +322,6 @@ mod tests { use super::*; - // this function costs but does not cache - // we can use it to check that the cache is properly remembering costs - fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Response { - let mut hashes = Vec::new(); - let mut ops = vec![TreeOp::SExp(node)]; - - let mut cost = SHA256TREE_BASE_COST; - - while let Some(op) = ops.pop() { - match op { - TreeOp::SExp(node) => { - cost += SHA256TREE_COST_PER_NODE; - check_cost(cost, cost_left)?; - match a.node(node) { - NodeVisitor::Buffer(bytes) => { - increment_bytes(bytes.len() + 1, &mut cost); - check_cost(cost, cost_left)?; - let hash = tree_hash_atom(bytes); - hashes.push(hash); - } - NodeVisitor::U32(val) => { - increment_bytes(a.atom_len(node) + 1, &mut cost); - check_cost(cost, cost_left)?; - if (val as usize) < PRECOMPUTED_HASHES.len() { - hashes.push(PRECOMPUTED_HASHES[val as usize]); - } else { - hashes.push(tree_hash_atom(a.atom(node).as_ref())); - } - } - NodeVisitor::Pair(left, right) => { - increment_bytes(65, &mut cost); - check_cost(cost, cost_left)?; - - ops.push(TreeOp::Cons); - ops.push(TreeOp::SExp(left)); - ops.push(TreeOp::SExp(right)); - } - } - } - TreeOp::Cons => { - let first = hashes.pop().unwrap(); - let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(&first, &rest)); - } - TreeOp::ConsAddCacheCost(_, _) => unreachable!(), - } - } - - assert!(hashes.len() == 1); - cost += MALLOC_COST_PER_BYTE * 32; - check_cost(cost, cost_left)?; - Ok(Reduction(cost, a.new_atom(&hashes[0])?)) - } - fn test_sha256_atom(buf: &[u8]) { let hash = tree_hash_atom(buf); diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index d4dfc4f6b..44f8ab4e4 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -96,12 +96,14 @@ fn time_per_cons_for_list( let quote = a.one(); let mut list = a.nil(); + let atom = a.new_atom(&[0xff, 0xff]).unwrap(); + for _ in 0..500 { - list = a.new_pair(a.nil(), list).unwrap(); + list = a.new_pair(atom, list).unwrap(); } for i in 0..1000 { - list = a.new_pair(a.nil(), list).unwrap(); + list = a.new_pair(atom, list).unwrap(); let q = a.new_pair(quote, list).unwrap(); let call = a.new_pair(q, a.nil()).unwrap(); let call = a.new_pair(op_code, call).unwrap(); From 745927e6afbf9fa17de300e5254eb3456a9fa08a Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 20 Nov 2025 11:50:12 +0000 Subject: [PATCH 070/110] update values --- src/treehash.rs | 4 ++-- tools/src/bin/sha256tree-benching.rs | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 67d1b0a46..4f4ce6814 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -11,8 +11,8 @@ use crate::SExp; use chia_sha2::Sha256; const SHA256TREE_BASE_COST: Cost = 30; -const SHA256TREE_COST_PER_NODE: Cost = 3090; -const SHA256TREE_COST_PER_32_BYTES: Cost = 610; +const SHA256TREE_COST_PER_NODE: Cost = 3000; +const SHA256TREE_COST_PER_32_BYTES: Cost = 700; pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 44f8ab4e4..0f2f3826e 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -7,6 +7,7 @@ use std::fs::File; use std::io::{BufWriter, Write}; use std::time::Instant; +#[allow(clippy::type_complexity)] fn time_per_byte_for_atom( a: &mut Allocator, sha_prog: NodePtr, @@ -31,7 +32,7 @@ fn time_per_byte_for_atom( let mut atom = vec![0xff; 10_000]; for i in 0..10_000 { - atom.extend(std::iter::repeat(((i % 89) + 10) as u8).take(32)); + atom.extend(std::iter::repeat_n(((i % 89) + 10) as u8, 32)); let atom_node = a.new_atom(&atom).unwrap(); let args = a.new_pair(quote, atom_node).unwrap(); @@ -73,6 +74,7 @@ fn time_per_byte_for_atom( ) } +#[allow(clippy::type_complexity)] fn time_per_cons_for_list( a: &mut Allocator, sha_prog: NodePtr, From a4e2e36973aba222f3b78ddb7af4b6c26de1b36c Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 20 Nov 2025 14:26:13 +0000 Subject: [PATCH 071/110] update costing and tests for shatree --- op-tests/test-sha256tree-hash.txt | 56 ++++++++++++++-------------- op-tests/test-sha256tree.txt | 8 ++-- tools/generate-sha256tree-tests.py | 8 +--- tools/src/bin/benchmark-clvm-cost.rs | 18 ++++----- 4 files changed, 42 insertions(+), 48 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index cafdfa50e..8f16e2d10 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 153030 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 29910 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 4660 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 30520 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 38530 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 21900 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 4660 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 56990 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 126560 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 4660 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 64390 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 12670 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 31740 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 4660 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 117330 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 21290 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 13280 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 101920 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 21290 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 39140 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 4660 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 21900 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 12670 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 156450 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 30450 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 4750 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 31150 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 39250 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 22350 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 4750 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 58250 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 129350 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 4750 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 65650 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 12850 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 32550 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 4750 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 119850 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 21650 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 13550 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 104350 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 21650 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 39950 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 4750 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 22350 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 12850 sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 4050 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 48370 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 109930 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 75450 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 13890 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 49450 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 112450 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 77250 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 14250 sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 4050 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 21290 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 21650 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 31496e34f..72fa6b237 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 4050 sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 4050 sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 4050 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 12670 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 12670 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 38530 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 47150 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 12850 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 12850 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 39250 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 48050 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index c3312c5b4..668863486 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -7,10 +7,9 @@ seed(1337) -# --- updated costing constants to match current Rust --- SHA256TREE_BASE_COST = 30 -SHA256TREE_COST_PER_NODE = 3090 -SHA256TREE_COST_PER_32_BYTES = 610 +SHA256TREE_COST_PER_NODE = 3000 +SHA256TREE_COST_PER_32_BYTES = 700 MALLOC_COST_PER_BYTE = 10 SIZE = 30 @@ -44,7 +43,6 @@ def random_atom() -> bytes: def random_tree(depth: int): - """Recursively generate random nested pairs (tuples) or atoms""" if depth == 0 or randint(0, 2) == 0: return random_atom() else: @@ -55,7 +53,6 @@ def increment_bytes(amount: int) -> int: def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: - """Return (hash, cost) similar to tree_hash_cached_costed""" cost = SHA256TREE_COST_PER_NODE if isinstance(obj, bytes): @@ -72,7 +69,6 @@ def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: def sexp_repr(obj) -> str: - """Turn the structure into a Lisp-like representation""" if isinstance(obj, bytes): if len(obj) == 0: return "0" diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index c9b651d89..8572494c7 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -268,12 +268,11 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - } // this adds 32 bytes at a time compared to per_byte which adds 5 at a time -// at the moment this func is only for sha256tree and will need adapting in the future for other uses -fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { +fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree - let op_code = a.new_small_number(63).unwrap(); + let op_code = a.new_small_number(op.opcode).unwrap(); let quote = a.one(); let mut atom = [0xff].repeat(10_000); let checkpoint = a.checkpoint(); @@ -298,12 +297,11 @@ fn time_per_byte_for_atom(a: &mut Allocator, output: &mut dyn Write) -> (f64, f6 linear_regression_of(&samples).expect("linreg failed") } -// at the moment this func is only for sha256tree and will need adapting in the future for other uses -fn time_per_cons_for_list(a: &mut Allocator, output: &mut dyn Write) -> (f64, f64) { +fn time_per_cons_for_list(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree - let op_code = a.new_small_number(63).unwrap(); + let op_code = a.new_small_number(op.opcode).unwrap(); let quote = a.one(); let mut list = a.nil(); @@ -573,7 +571,7 @@ pub fn main() { name: "sha256", arg: Placeholder::SingleArg(Some(g1)), extra: None, - flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, + flags: NESTING_BASE_COST | PER_ARG_COST | ALT_PER_BYTE_COST | LARGE_BUFFERS, }, Operator { opcode: 62, @@ -583,7 +581,7 @@ pub fn main() { flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, }, Operator { - opcode: 65, + opcode: 63, name: "sha256tree", arg: Placeholder::SingleArg(None), extra: None, @@ -614,7 +612,7 @@ pub fn main() { time_per_byte } else if (op.flags & ALT_PER_BYTE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); - let (slope, _intercept) = time_per_byte_for_atom(&mut a, &mut output); + let (slope, _intercept) = time_per_byte_for_atom(&mut a, op, &mut output); let cost = slope * cost_scale; println!(" time: per-32byte: {slope:.2}ns"); println!(" cost: per-32byte: {:.0}", cost); @@ -681,7 +679,7 @@ pub fn main() { if (op.flags & LIST_LENGTH_COST) != 0 { write_gnuplot_header(&mut *gnuplot, op, "per-pair", "num pairs"); let mut output = maybe_open(options.plot, op.name, "per-pair.log"); - let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, &mut output); + let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, op, &mut output); let cost = slope * cost_scale; println!(" time: per-node: {:.2}ns", slope); From c3ffba1a6406baf650836baa9b398f52301c6249 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Wed, 26 Nov 2025 15:43:20 +0000 Subject: [PATCH 072/110] remove cached treehash --- src/treehash.rs | 368 ------------------------------------------------ 1 file changed, 368 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 4f4ce6814..05bd5fdab 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -6,8 +6,6 @@ use crate::more_ops::PRECOMPUTED_HASHES; use crate::op_utils::MALLOC_COST_PER_BYTE; use crate::reduction::Reduction; use crate::reduction::Response; -use crate::ObjectType; -use crate::SExp; use chia_sha2::Sha256; const SHA256TREE_BASE_COST: Cost = 30; @@ -29,113 +27,9 @@ pub fn tree_hash_pair(first: &[u8; 32], rest: &[u8; 32]) -> [u8; 32] { sha256.finalize() } -#[derive(Default)] -pub struct TreeCache { - hashes: Vec<[u8; 32]>, - // parallel vector holding the cost used to compute the corresponding hash - costs: Vec, - // each entry is an index into hashes and costs, or one of 3 special values: - // u16::MAX if the pair has not been visited - // u16::MAX - 1 if the pair has been seen once - // u16::MAX - 2 if the pair has been seen at least twice (this makes it a - // candidate for memoization) - pairs: Vec, -} - -const NOT_VISITED: u16 = u16::MAX; -const SEEN_ONCE: u16 = u16::MAX - 1; -const SEEN_MULTIPLE: u16 = u16::MAX - 2; - -impl TreeCache { - /// Get cached hash and its associated cost (if present). - pub fn get(&self, n: NodePtr) -> Option<(&[u8; 32], Cost)> { - // We only cache pairs (for now) - if !matches!(n.object_type(), ObjectType::Pair) { - return None; - } - - let idx = n.index() as usize; - let slot = *self.pairs.get(idx)?; - if slot >= SEEN_MULTIPLE { - return None; - } - Some((&self.hashes[slot as usize], self.costs[slot as usize])) - } - - /// Insert a cached hash with its associated cost. If the cache is full we - /// ignore the insertion. - pub fn insert(&mut self, n: NodePtr, hash: &[u8; 32], cost: Cost) { - // If we've reached the max size, just ignore new cache items - if self.hashes.len() == SEEN_MULTIPLE as usize { - return; - } - - if !matches!(n.object_type(), ObjectType::Pair) { - return; - } - - let idx = n.index() as usize; - if idx >= self.pairs.len() { - self.pairs.resize(idx + 1, NOT_VISITED); - } - - let slot = self.hashes.len(); - self.hashes.push(*hash); - self.costs.push(cost); - self.pairs[idx] = slot as u16; - } - - /// mark the node as being visited. Returns true if we need to - /// traverse visitation down this node. - fn visit(&mut self, n: NodePtr) -> bool { - if !matches!(n.object_type(), ObjectType::Pair) { - return false; - } - let idx = n.index() as usize; - if idx >= self.pairs.len() { - self.pairs.resize(idx + 1, NOT_VISITED); - } - if self.pairs[idx] > SEEN_MULTIPLE { - self.pairs[idx] -= 1; - } - self.pairs[idx] == SEEN_ONCE - } - - pub fn should_memoize(&mut self, n: NodePtr) -> bool { - if !matches!(n.object_type(), ObjectType::Pair) { - return false; - } - let idx = n.index() as usize; - if idx >= self.pairs.len() { - false - } else { - self.pairs[idx] <= SEEN_MULTIPLE - } - } - - pub fn visit_tree(&mut self, a: &Allocator, node: NodePtr) { - if !self.visit(node) { - return; - } - let mut nodes = vec![node]; - while let Some(n) = nodes.pop() { - let SExp::Pair(left, right) = a.sexp(n) else { - continue; - }; - if self.visit(left) { - nodes.push(left); - } - if self.visit(right) { - nodes.push(right); - } - } - } -} - enum TreeOp { SExp(NodePtr), Cons, - ConsAddCacheCost(NodePtr, Cost), } #[inline] @@ -143,87 +37,6 @@ fn increment_bytes(amount: usize, cost: &mut Cost) { *cost += (amount.div_ceil(32)) as u64 * SHA256TREE_COST_PER_32_BYTES; } -pub fn tree_hash_cached_costed( - a: &mut Allocator, - node: NodePtr, - cache: &mut TreeCache, - cost_left: Cost, -) -> Response { - cache.visit_tree(a, node); - - let mut hashes = Vec::new(); - let mut ops = vec![TreeOp::SExp(node)]; - let mut cost: Cost = SHA256TREE_BASE_COST; - - while let Some(op) = ops.pop() { - match op { - TreeOp::SExp(node) => { - // charge a call cost for processing this op - cost += SHA256TREE_COST_PER_NODE; - // this represents the 1 or 2 for atom or pair - check_cost(cost, cost_left)?; - match a.node(node) { - NodeVisitor::Buffer(bytes) => { - increment_bytes(bytes.len() + 1, &mut cost); - check_cost(cost, cost_left)?; - let hash = tree_hash_atom(bytes); - hashes.push(hash); - } - NodeVisitor::U32(val) => { - increment_bytes(a.atom_len(node) + 1, &mut cost); - check_cost(cost, cost_left)?; - if (val as usize) < PRECOMPUTED_HASHES.len() { - hashes.push(PRECOMPUTED_HASHES[val as usize]); - } else { - hashes.push(tree_hash_atom(a.atom(node).as_ref())); - } - } - NodeVisitor::Pair(left, right) => { - increment_bytes(65, &mut cost); - check_cost(cost, cost_left)?; - if let Some((hash, cached_cost)) = cache.get(node) { - // when reusing a cached subtree, charge its cached cost - cost += cached_cost; - check_cost(cost, cost_left)?; - hashes.push(*hash); - } else { - if cache.should_memoize(node) { - // record the cost before traversing this subtree - ops.push(TreeOp::ConsAddCacheCost(node, cost)); - } else { - ops.push(TreeOp::Cons); - } - ops.push(TreeOp::SExp(left)); - ops.push(TreeOp::SExp(right)); - } - } - } - } - TreeOp::Cons => { - let first = hashes.pop().unwrap(); - let rest = hashes.pop().unwrap(); - hashes.push(tree_hash_pair(&first, &rest)); - } - TreeOp::ConsAddCacheCost(original_node, cost_before) => { - let first = hashes.pop().unwrap(); - let rest = hashes.pop().unwrap(); - let hash = tree_hash_pair(&first, &rest); - hashes.push(hash); - // cost_before will be lower - // cost_left is the remaining after computing it - // the cost of this subtree = after - before - let used = cost - cost_before; - cache.insert(original_node, &hash, used); - } - } - } - - assert!(hashes.len() == 1); - cost += MALLOC_COST_PER_BYTE * 32; - check_cost(cost, cost_left)?; - Ok(Reduction(cost, a.new_atom(&hashes[0])?)) -} - // this function costs but does not cache // we can use it to check that the cache is properly remembering costs pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Response { @@ -268,7 +81,6 @@ pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Re let rest = hashes.pop().unwrap(); hashes.push(tree_hash_pair(&first, &rest)); } - TreeOp::ConsAddCacheCost(_, _) => unreachable!(), } } @@ -308,7 +120,6 @@ pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { let rest = hashes.pop().unwrap(); hashes.push(tree_hash_pair(&first, &rest)); } - TreeOp::ConsAddCacheCost(_, _) => unreachable!(), } } @@ -318,8 +129,6 @@ pub fn tree_hash(a: &Allocator, node: NodePtr) -> [u8; 32] { #[cfg(test)] mod tests { - use crate::test_ops::node_eq; - use super::*; fn test_sha256_atom(buf: &[u8]) { @@ -357,181 +166,4 @@ mod tests { assert_eq!(tree_hash_atom(&[val]), PRECOMPUTED_HASHES[val as usize]); } } - - #[test] - fn test_tree_cache_get() { - let mut allocator = Allocator::new(); - let mut cache = TreeCache::default(); - - let a = allocator.nil(); - let b = allocator.one(); - let c = allocator.new_pair(a, b).expect("new_pair"); - - assert_eq!(cache.get(a), None); - assert_eq!(cache.get(b), None); - assert_eq!(cache.get(c), None); - - // We don't cache atoms - cache.insert(a, &tree_hash(&allocator, a), 0); - assert_eq!(cache.get(a), None); - - cache.insert(b, &tree_hash(&allocator, b), 0); - assert_eq!(cache.get(b), None); - - // but pair is OK - cache.insert(c, &tree_hash(&allocator, c), 0); - let (h, _c) = cache.get(c).expect("expected cached pair"); - assert_eq!(h, &tree_hash(&allocator, c)); - } - - #[test] - fn test_tree_cache_size_limit() { - let mut allocator = Allocator::new(); - let mut cache = TreeCache::default(); - - let mut list = allocator.nil(); - let mut hash = tree_hash(&allocator, list); - cache.insert(list, &hash, 0); - - // we only fit 65k items in the cache - for i in 0..65540 { - let b = allocator.one(); - list = allocator.new_pair(b, list).expect("new_pair"); - hash = tree_hash_pair(&tree_hash_atom(b"\x01"), &hash); - cache.insert(list, &hash, 0); - - println!("{i}"); - if i < 65533 { - let (h, _c) = cache.get(list).expect("expected cached"); - assert_eq!(h, &hash); - } else { - assert_eq!(cache.get(list), None); - } - } - assert_eq!(cache.get(list), None); - } - - #[test] - fn test_tree_cache_should_memoize() { - let mut allocator = Allocator::new(); - let mut cache = TreeCache::default(); - - let a = allocator.nil(); - let b = allocator.one(); - let c = allocator.new_pair(a, b).expect("new_pair"); - - assert!(!cache.should_memoize(a)); - assert!(!cache.should_memoize(b)); - assert!(!cache.should_memoize(c)); - - // we need to visit a node at least twice for it to be considered a - // candidate for memoization - assert!(cache.visit(c)); - assert!(!cache.should_memoize(c)); - assert!(!cache.visit(c)); - - assert!(cache.should_memoize(c)); - } - - #[test] - fn test_tree_hash_costed_equivalence_no_repeats() { - let mut a = Allocator::new(); - - // Build a nested tree: - // ((a . b) . ((x . y) . (z . w))) - let a_atom = a.new_atom(b"a").unwrap(); - let b_atom = a.new_atom(b"b").unwrap(); - let x_atom = a.new_atom(b"x").unwrap(); - let y_atom = a.new_atom(b"y").unwrap(); - let z_atom = a.new_atom(b"z").unwrap(); - let w_atom = a.new_atom(b"w").unwrap(); - - let ab_pair = a.new_pair(a_atom, b_atom).unwrap(); - let xy_pair = a.new_pair(x_atom, y_atom).unwrap(); - let zw_pair = a.new_pair(z_atom, w_atom).unwrap(); - let right_pair = a.new_pair(xy_pair, zw_pair).unwrap(); - let root = a.new_pair(ab_pair, right_pair).unwrap(); - - let cost_left_baseline = 1_000_000; - let cost_left_cached = 1_000_000; - - // baseline: costed but no caching - let baseline = tree_hash_costed(&mut a, root, cost_left_baseline).unwrap(); - - // cached version - let mut cache = TreeCache::default(); - cache.visit_tree(&a, root); - - let cached = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached).unwrap(); - - assert!( - !cache.hashes.is_empty(), - "cache should contain memoized subtrees" - ); - - assert_eq!( - baseline.0, cached.0, - "cost mismatch between costed and cached" - ); - assert!(node_eq(&a, baseline.1, cached.1)); - - // the number of cached hashes and costs must match - assert_eq!(cache.hashes.len(), cache.costs.len()); - - // if we re-run with cache, cost_left should still match the baseline - let cost_left_cached2 = 1_000_000; - let cached2 = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached2).unwrap(); - - assert_eq!( - cached2.0, cached.0, - "cost mismatch between costed and cached" - ); - assert!(node_eq(&a, cached2.1, cached.1)); - } - - #[test] - fn test_tree_hash_cost_equivalence_with_repeats() { - let mut a = Allocator::new(); - let x_atom = a.new_atom(b"x").unwrap(); - let y_atom = a.new_atom(b"y").unwrap(); - let r = a.new_pair(x_atom, y_atom).unwrap(); - - let left = a.new_pair(r, r).unwrap(); - let right = a.new_pair(r, r).unwrap(); - let root = a.new_pair(left, right).unwrap(); - - // Nest it one level deeper: - let root = a.new_pair(root, root).unwrap(); - - let cost_left_baseline = 10_000_000; - let cost_left_cached = 10_000_000; - - let baseline = tree_hash_costed(&mut a, root, cost_left_baseline).unwrap(); - - let mut cache = TreeCache::default(); - cache.visit_tree(&a, root); - - let cached = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached).unwrap(); - - assert!( - !cache.hashes.is_empty(), - "cache should contain memoized subtrees" - ); - - assert_eq!( - baseline.0, cached.0, - "cost mismatch between costed and cached" - ); - assert!(node_eq(&a, baseline.1, cached.1)); - - // run again with same cache — should still match cost and hash - let cost_left_cached2 = 10_000_000; - let cached2 = tree_hash_cached_costed(&mut a, root, &mut cache, cost_left_cached2).unwrap(); - - assert_eq!( - cached2.0, cached.0, - "cost mismatch between costed and cached" - ); - assert!(node_eq(&a, cached2.1, cached.1)); - } } From bdbf557b8f3c7f0e58135d6faeeb9950e7813aea Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 1 Dec 2025 14:27:35 +0000 Subject: [PATCH 073/110] cleanups and better benching --- tools/src/bin/sha256tree-benching.rs | 87 +++++++++++++++++++--------- 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 0f2f3826e..a55628576 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -16,10 +16,10 @@ fn time_per_byte_for_atom( mut output_native_cost: impl Write, mut output_clvm_cost: impl Write, ) -> ( - (f64, f64), - (f64, f64), // time slopes - (f64, f64), - (f64, f64), // cost slopes + f64, + f64, // time slopes + f64, + f64, // cost slopes ) { let mut samples_time_native = Vec::<(f64, f64)>::new(); let mut samples_time_clvm = Vec::<(f64, f64)>::new(); @@ -67,10 +67,10 @@ fn time_per_byte_for_atom( } ( - linear_regression_of(&samples_time_native).unwrap(), - linear_regression_of(&samples_time_clvm).unwrap(), - linear_regression_of(&samples_cost_native).unwrap(), - linear_regression_of(&samples_cost_clvm).unwrap(), + linear_regression_of(&samples_time_native).unwrap().0, + linear_regression_of(&samples_time_clvm).unwrap().0, + linear_regression_of(&samples_cost_native).unwrap().0, + linear_regression_of(&samples_cost_clvm).unwrap().0, ) } @@ -78,15 +78,19 @@ fn time_per_byte_for_atom( fn time_per_cons_for_list( a: &mut Allocator, sha_prog: NodePtr, + bytes32_native_cost: f64, + bytes32_clvm_cost: f64, + bytes32_native_time: f64, + bytes32_clvm_time: f64, mut output_native_time: impl Write, mut output_clvm_time: impl Write, mut output_native_cost: impl Write, mut output_clvm_cost: impl Write, ) -> ( - (f64, f64), - (f64, f64), // time slopes - (f64, f64), - (f64, f64), // cost slopes + f64, + f64, // time slopes + f64, + f64, // cost slopes ) { let mut samples_time_native = Vec::<(f64, f64)>::new(); let mut samples_time_clvm = Vec::<(f64, f64)>::new(); @@ -118,6 +122,8 @@ fn time_per_cons_for_list( let cost = red.0; let result_1 = node_to_bytes(a, red.1).expect("should work"); let duration = start.elapsed().as_nanos() as f64; + let duration = (duration - (3.0 * bytes32_native_time)) / 2.0; + let cost = (cost as f64 - (3.0 * bytes32_native_cost)) / 2.0; writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); samples_time_native.push((i as f64, duration)); @@ -130,8 +136,9 @@ fn time_per_cons_for_list( let cost = red.0; let result_2 = node_to_bytes(a, red.1).expect("should work"); assert_eq!(result_1, result_2); - let duration = start.elapsed().as_nanos() as f64; + let duration = (duration - (3.0 * bytes32_clvm_time)) / 2.0; + let cost = (cost as f64 - (3.0 * bytes32_clvm_cost)) / 2.0; writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); samples_time_clvm.push((i as f64, duration)); @@ -139,10 +146,10 @@ fn time_per_cons_for_list( } ( - linear_regression_of(&samples_time_native).unwrap(), - linear_regression_of(&samples_time_clvm).unwrap(), - linear_regression_of(&samples_cost_native).unwrap(), - linear_regression_of(&samples_cost_clvm).unwrap(), + linear_regression_of(&samples_time_native).unwrap().0, + linear_regression_of(&samples_time_clvm).unwrap().0, + linear_regression_of(&samples_cost_native).unwrap().0, + linear_regression_of(&samples_cost_clvm).unwrap().0, ) } @@ -177,23 +184,47 @@ fn main() { let (cons_nat_t, cons_clvm_t, cons_nat_c, cons_clvm_c) = time_per_cons_for_list( &mut a, shaprog, + atom_nat_c, + atom_clvm_c, + atom_nat_t, + atom_clvm_t, cons_native_time, cons_clvm_time, cons_native_cost, cons_clvm_cost, ); - println!("atom results: "); - println!("Native time slope (ns): {:.4}", atom_nat_t.0); - println!("CLVM time slope (ns): {:.4}", atom_clvm_t.0); - println!("Native cost slope : {:.4}", atom_nat_c.0); - println!("CLVM cost slope : {:.4}", atom_clvm_c.0); - - println!("list results: "); - println!("Native time slope (ns): {:.4}", cons_nat_t.0); - println!("CLVM time slope (ns): {:.4}", cons_clvm_t.0); - println!("Native cost slope : {:.4}", cons_nat_c.0); - println!("CLVM cost slope : {:.4}", cons_clvm_c.0); + // taken from benchmark-clvm-cost.rs + let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; + + println!("bytes32 results: "); + println!("Native time per bytes32 (ns): {:.4}", atom_nat_t); + println!("CLVM time per bytes32 (ns): {:.4}", atom_clvm_t); + println!( + "Native (time_per_bytes32 * cost_ratio): {:.4}", + atom_nat_t * cost_scale + ); + println!( + "CLVM (time_per_bytes * cost_ratio : {:.4}", + atom_clvm_t * cost_scale + ); + + println!("Native cost per bytes32 : {:.4}", atom_nat_c); + println!("CLVM cost per bytes32 : {:.4}", atom_clvm_c); + + println!("per node results: "); + println!("Native time per node (ns): {:.4}", cons_nat_t); + println!("CLVM time per node (ns): {:.4}", cons_clvm_t); + println!( + "Native (time_per_node * cost_ratio): {:.4}", + cons_nat_t * cost_scale + ); + println!( + "CLVM (time_per_node * cost_ratio): {:.4}", + cons_clvm_t * cost_scale + ); + println!("Native cost slope : {:.4}", cons_nat_c); + println!("CLVM cost slope : {:.4}", cons_clvm_c); // gnuplot script let mut gp = File::create("plots.gnuplot").unwrap(); From ad5daaf79a656913cd73f6314d9a07fc568dcef2 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 1 Dec 2025 14:36:54 +0000 Subject: [PATCH 074/110] ignore many arguments --- tools/src/bin/sha256tree-benching.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index a55628576..0b6ed84cf 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -75,6 +75,7 @@ fn time_per_byte_for_atom( } #[allow(clippy::type_complexity)] +#[allow(clippy::too_many_arguments)] fn time_per_cons_for_list( a: &mut Allocator, sha_prog: NodePtr, From fe62f3a129eddf4048e3d65ab63853e945bf234d Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 1 Dec 2025 14:44:18 +0000 Subject: [PATCH 075/110] gitignore DS_Store --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9bb6c7b8a..33433b5ed 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,6 @@ target/ # Node.js /target /node_modules + +# Mac generates these with the benchmarking tools +.DS_Store \ No newline at end of file From 55bfbed76ca0362c2535182228b5a8a20dd54dd8 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 1 Dec 2025 15:17:24 +0000 Subject: [PATCH 076/110] update comments and text output for clarity --- src/treehash.rs | 16 ++++++++++------ tools/src/bin/sha256tree-benching.rs | 4 ++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/treehash.rs b/src/treehash.rs index 05bd5fdab..7bea1b01a 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -8,8 +8,11 @@ use crate::reduction::Reduction; use crate::reduction::Response; use chia_sha2::Sha256; +// the base cost is the cost of calling it to begin with const SHA256TREE_BASE_COST: Cost = 30; -const SHA256TREE_COST_PER_NODE: Cost = 3000; +// this is the cost per node, whether it is a cons box or an atom +const SHA256TREE_COST_PER_NODE: Cost = 2000; +// this is the cost for every 32 bytes in a sha256 call const SHA256TREE_COST_PER_32_BYTES: Cost = 700; pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { @@ -32,9 +35,10 @@ enum TreeOp { Cons, } +// costing is done for every 32 byte chunk that is hashed #[inline] -fn increment_bytes(amount: usize, cost: &mut Cost) { - *cost += (amount.div_ceil(32)) as u64 * SHA256TREE_COST_PER_32_BYTES; +fn increment_cost_for_hash_of_bytes(size: usize, cost: &mut Cost) { + *cost += (size.div_ceil(32)) as u64 * SHA256TREE_COST_PER_32_BYTES; } // this function costs but does not cache @@ -52,13 +56,13 @@ pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Re check_cost(cost, cost_left)?; match a.node(node) { NodeVisitor::Buffer(bytes) => { - increment_bytes(bytes.len() + 1, &mut cost); + increment_cost_for_hash_of_bytes(bytes.len() + 1, &mut cost); check_cost(cost, cost_left)?; let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { - increment_bytes(a.atom_len(node) + 1, &mut cost); + increment_cost_for_hash_of_bytes(a.atom_len(node) + 1, &mut cost); check_cost(cost, cost_left)?; if (val as usize) < PRECOMPUTED_HASHES.len() { hashes.push(PRECOMPUTED_HASHES[val as usize]); @@ -67,7 +71,7 @@ pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Re } } NodeVisitor::Pair(left, right) => { - increment_bytes(65, &mut cost); + increment_cost_for_hash_of_bytes(65, &mut cost); check_cost(cost, cost_left)?; ops.push(TreeOp::Cons); diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 0b6ed84cf..e9517ad23 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -224,8 +224,8 @@ fn main() { "CLVM (time_per_node * cost_ratio): {:.4}", cons_clvm_t * cost_scale ); - println!("Native cost slope : {:.4}", cons_nat_c); - println!("CLVM cost slope : {:.4}", cons_clvm_c); + println!("Native cost per node : {:.4}", cons_nat_c); + println!("CLVM cost per node : {:.4}", cons_clvm_c); // gnuplot script let mut gp = File::create("plots.gnuplot").unwrap(); From 0eed06b896324c59f22334f13a0822153cc5f0e9 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 1 Dec 2025 15:28:22 +0000 Subject: [PATCH 077/110] update tests for new costs --- op-tests/test-sha256tree-hash.txt | 60 +++++++++++++++--------------- op-tests/test-sha256tree.txt | 14 +++---- tools/generate-sha256tree-tests.py | 2 +- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index 8f16e2d10..5c72b44f6 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 156450 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 30450 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 4750 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 31150 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 39250 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 22350 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 4750 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 58250 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 129350 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 4750 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 65650 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 12850 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 32550 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 4750 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 119850 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 21650 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 13550 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 104350 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 21650 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 39950 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 4750 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 22350 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 12850 -sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 4050 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 49450 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 112450 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 77250 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 14250 -sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 4050 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 21650 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 121450 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 23450 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 3750 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 24150 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 30250 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 17350 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 3750 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 45250 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 100350 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 3750 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 50650 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 9850 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 25550 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 3750 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 92850 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 16650 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 10550 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 81350 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 16650 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 30950 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 3750 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 17350 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 9850 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 3050 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 38450 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 87450 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 60250 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 11250 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3050 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 16650 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 72fa6b237..9eded00e3 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 4050 -sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 4050 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 4050 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 12850 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 12850 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 39250 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 48050 +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3050 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 3050 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 3050 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 9850 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 9850 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 30250 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 37050 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index 668863486..db4a728a1 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -8,7 +8,7 @@ seed(1337) SHA256TREE_BASE_COST = 30 -SHA256TREE_COST_PER_NODE = 3000 +SHA256TREE_COST_PER_NODE = 2000 SHA256TREE_COST_PER_32_BYTES = 700 MALLOC_COST_PER_BYTE = 10 From b27f794a82faa759da801fb62451af05e3b4d73d Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 2 Dec 2025 12:10:00 +0000 Subject: [PATCH 078/110] no longer add high cost per node, only calculate cost based on hash operations --- op-tests/test-sha256tree-hash.txt | 60 ++++++++++++++-------------- op-tests/test-sha256tree.txt | 14 +++---- src/treehash.rs | 4 +- tools/generate-sha256tree-tests.py | 4 +- tools/src/bin/benchmark-clvm-cost.rs | 35 +++++++++------- 5 files changed, 62 insertions(+), 55 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index 5c72b44f6..4f8903315 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 121450 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 23450 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 3750 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 24150 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 30250 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 17350 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 3750 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 45250 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 100350 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 3750 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 50650 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 9850 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 25550 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 3750 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 92850 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 16650 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 10550 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 81350 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 16650 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 30950 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 3750 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 17350 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 9850 -sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 3050 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 38450 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 87450 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 60250 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 11250 -sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3050 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 16650 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 51420 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 9420 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1720 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 10120 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 12220 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 7320 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 1720 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 19220 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 42320 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 1720 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 20620 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 3820 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 11520 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 1720 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 38820 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 6620 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 4520 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 35320 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 6620 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 12920 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 1720 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 7320 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 3820 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 1020 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 16420 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 37420 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 26220 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 5220 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1020 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 6620 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 9eded00e3..6eac567e8 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 3050 -sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 3050 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 3050 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 9850 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 9850 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 30250 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 37050 +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1020 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1020 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1020 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 3820 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 3820 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 12220 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 15020 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/src/treehash.rs b/src/treehash.rs index 7bea1b01a..01ea6add6 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -9,9 +9,9 @@ use crate::reduction::Response; use chia_sha2::Sha256; // the base cost is the cost of calling it to begin with -const SHA256TREE_BASE_COST: Cost = 30; +const SHA256TREE_BASE_COST: Cost = 0; // this is the cost per node, whether it is a cons box or an atom -const SHA256TREE_COST_PER_NODE: Cost = 2000; +const SHA256TREE_COST_PER_NODE: Cost = 0; // this is the cost for every 32 bytes in a sha256 call const SHA256TREE_COST_PER_32_BYTES: Cost = 700; diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index db4a728a1..721186280 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -7,8 +7,8 @@ seed(1337) -SHA256TREE_BASE_COST = 30 -SHA256TREE_COST_PER_NODE = 2000 +SHA256TREE_BASE_COST = 0 +SHA256TREE_COST_PER_NODE = 0 SHA256TREE_COST_PER_32_BYTES = 700 MALLOC_COST_PER_BYTE = 10 diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 8572494c7..f3fd09fff 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -297,7 +297,12 @@ fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Wri linear_regression_of(&samples).expect("linreg failed") } -fn time_per_cons_for_list(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { +fn time_per_cons_for_list( + a: &mut Allocator, + time_per_byte32: f64, + op: &Operator, + output: &mut dyn Write, +) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree @@ -318,7 +323,8 @@ fn time_per_cons_for_list(a: &mut Allocator, op: &Operator, output: &mut dyn Wri let start = Instant::now(); run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); let duration = start.elapsed(); - let sample = (i as f64, duration.as_nanos() as f64); + let duration_f64 = (duration.as_nanos() as f64 - (4.0 * time_per_byte32)) / 2.0; + let sample = (i as f64, duration_f64); writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); } @@ -616,6 +622,19 @@ pub fn main() { let cost = slope * cost_scale; println!(" time: per-32byte: {slope:.2}ns"); println!(" cost: per-32byte: {:.0}", cost); + if (op.flags & LIST_LENGTH_COST) != 0 { + write_gnuplot_header(&mut *gnuplot, op, "per-pair", "num pairs"); + let mut output = maybe_open(options.plot, op.name, "per-pair.log"); + let (slope, intercept): (f64, f64) = + time_per_cons_for_list(&mut a, slope, op, &mut output); + let cost = slope * cost_scale; + + println!(" time: per-node: {:.2}ns", slope); + println!(" cost: per-node: {:.0}", cost); + println!(" intercept: {:.2}", intercept); + + print_plot(&mut *gnuplot, &slope, &intercept, op.name, "per-pair"); + } slope } else { 0.0 @@ -676,18 +695,6 @@ pub fn main() { "per-byte", ); } - if (op.flags & LIST_LENGTH_COST) != 0 { - write_gnuplot_header(&mut *gnuplot, op, "per-pair", "num pairs"); - let mut output = maybe_open(options.plot, op.name, "per-pair.log"); - let (slope, intercept): (f64, f64) = time_per_cons_for_list(&mut a, op, &mut output); - let cost = slope * cost_scale; - - println!(" time: per-node: {:.2}ns", slope); - println!(" cost: per-node: {:.0}", cost); - println!(" intercept: {:.2}", intercept); - - print_plot(&mut *gnuplot, &slope, &intercept, op.name, "per-pair"); - } } if options.plot { println!("To generate plots, run:\n (cd measurements; gnuplot gen-graphs.gnuplot)"); From a28ce5464b207a8f98e66a4ec35b4d4ebe120977 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 2 Dec 2025 14:41:44 +0000 Subject: [PATCH 079/110] add comments --- tools/src/bin/benchmark-clvm-cost.rs | 4 ++++ tools/src/bin/sha256tree-benching.rs | 20 ++++++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index f3fd09fff..448e53830 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -297,6 +297,10 @@ fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Wri linear_regression_of(&samples).expect("linreg failed") } +// this function is used for calculating a theoretical cost per node +// we pass in the time it takes for a byte32 chunk and subtract 4*chunk_time +// we then divide by two to account for the fact that we are adding a nil atom each time the list grows too +// this is because atoms are nodes too fn time_per_cons_for_list( a: &mut Allocator, time_per_byte32: f64, diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index e9517ad23..19f02044b 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -7,6 +7,12 @@ use std::fs::File; use std::io::{BufWriter, Write}; use std::time::Instant; +/* +This file is for comparing the native sha256tree with the clvm implementation which previously existed. +The costs for the native implementation should be lower as it is not required to make allocations. +*/ + +// this function is for comparing the cost per 32byte chunk of hashing between the native and clvm implementation #[allow(clippy::type_complexity)] fn time_per_byte_for_atom( a: &mut Allocator, @@ -74,6 +80,8 @@ fn time_per_byte_for_atom( ) } +// this function calculates the cost per node theoretically +// in reality we are only charging per hash operation on a 32 byte chunk #[allow(clippy::type_complexity)] #[allow(clippy::too_many_arguments)] fn time_per_cons_for_list( @@ -123,8 +131,10 @@ fn time_per_cons_for_list( let cost = red.0; let result_1 = node_to_bytes(a, red.1).expect("should work"); let duration = start.elapsed().as_nanos() as f64; - let duration = (duration - (3.0 * bytes32_native_time)) / 2.0; - let cost = (cost as f64 - (3.0 * bytes32_native_cost)) / 2.0; + // a new list entry is 2 nodes (a cons and a nil) and a 3 chunk hash operation and a 1 chunk hash operation + // this equation lets us figure out a theoretical cost just for a node + let duration = (duration - (4.0 * bytes32_native_time)) / 2.0; + let cost = (cost as f64 - (4.0 * bytes32_native_cost)) / 2.0; writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); samples_time_native.push((i as f64, duration)); @@ -138,8 +148,10 @@ fn time_per_cons_for_list( let result_2 = node_to_bytes(a, red.1).expect("should work"); assert_eq!(result_1, result_2); let duration = start.elapsed().as_nanos() as f64; - let duration = (duration - (3.0 * bytes32_clvm_time)) / 2.0; - let cost = (cost as f64 - (3.0 * bytes32_clvm_cost)) / 2.0; + // a new list entry is 2 nodes (a cons and a nil) and a 3 chunk hash operation and a 1 chunk hash operation + // this equation lets us figure out a theoretical cost just for a node + let duration = (duration - (4.0 * bytes32_clvm_time)) / 2.0; + let cost = (cost as f64 - (4.0 * bytes32_clvm_cost)) / 2.0; writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); samples_time_clvm.push((i as f64, duration)); From 7fa2819b81236790f86bcdc7389ec69ffcd5cdfd Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 4 Dec 2025 13:06:21 +0000 Subject: [PATCH 080/110] add more comments --- tools/src/bin/benchmark-clvm-cost.rs | 2 ++ tools/src/bin/sha256tree-benching.rs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 448e53830..afbdbbfd5 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -268,6 +268,7 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - } // this adds 32 bytes at a time compared to per_byte which adds 5 at a time +// at the moment this function is specialised for sha256tree but may be relevant to other hash operations in the future fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree @@ -301,6 +302,7 @@ fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Wri // we pass in the time it takes for a byte32 chunk and subtract 4*chunk_time // we then divide by two to account for the fact that we are adding a nil atom each time the list grows too // this is because atoms are nodes too +// at the moment this function is extremely specialised for sha256tree fn time_per_cons_for_list( a: &mut Allocator, time_per_byte32: f64, diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 19f02044b..452fa9aa4 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -210,7 +210,7 @@ fn main() { // taken from benchmark-clvm-cost.rs let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; - println!("bytes32 results: "); + println!("Costs per bytes32 chunk: "); println!("Native time per bytes32 (ns): {:.4}", atom_nat_t); println!("CLVM time per bytes32 (ns): {:.4}", atom_clvm_t); println!( @@ -225,7 +225,9 @@ fn main() { println!("Native cost per bytes32 : {:.4}", atom_nat_c); println!("CLVM cost per bytes32 : {:.4}", atom_clvm_c); - println!("per node results: "); + // this is described as estimated as we're adding a cons and a nil atom each time + // and then we're subtracting the costs to calculate what a single node might theoretically cost + println!("Estimated costs per node results: "); println!("Native time per node (ns): {:.4}", cons_nat_t); println!("CLVM time per node (ns): {:.4}", cons_clvm_t); println!( From a9fcdcdc82e847e07e149f867030853198286bb5 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 4 Dec 2025 13:12:03 +0000 Subject: [PATCH 081/110] remove cost_per_node and add base cost equal to sha256 --- op-tests/test-sha256tree-hash.txt | 60 +++++++++++++++--------------- op-tests/test-sha256tree.txt | 14 +++---- src/treehash.rs | 12 +++--- tools/generate-sha256tree-tests.py | 5 +-- 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index 4f8903315..d9b363081 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 51420 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 9420 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1720 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 10120 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 12220 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 7320 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 1720 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 19220 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 42320 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 1720 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 20620 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 3820 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 11520 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 1720 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 38820 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 6620 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 4520 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 35320 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 6620 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 12920 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 1720 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 7320 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 3820 -sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 1020 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 16420 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 37420 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 26220 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 5220 -sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1020 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 6620 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 51507 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 9507 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1807 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 10207 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 12307 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 7407 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 1807 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 19307 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 42407 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 1807 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 20707 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 3907 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 11607 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 1807 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 38907 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 6707 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 4607 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 35407 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 6707 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 13007 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 1807 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 7407 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 3907 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 1107 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 16507 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 37507 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 26307 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 5307 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1107 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 6707 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 6eac567e8..1026b5ec9 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1020 -sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1020 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1020 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 3820 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 3820 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 12220 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 15020 +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1107 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1107 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1107 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 3907 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 3907 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 12307 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 15107 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/src/treehash.rs b/src/treehash.rs index 01ea6add6..d734fb21d 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -9,9 +9,8 @@ use crate::reduction::Response; use chia_sha2::Sha256; // the base cost is the cost of calling it to begin with -const SHA256TREE_BASE_COST: Cost = 0; -// this is the cost per node, whether it is a cons box or an atom -const SHA256TREE_COST_PER_NODE: Cost = 0; +// this is set to the same as sha256 +const SHA256TREE_BASE_COST: Cost = 87; // this is the cost for every 32 bytes in a sha256 call const SHA256TREE_COST_PER_32_BYTES: Cost = 700; @@ -52,16 +51,17 @@ pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Re while let Some(op) = ops.pop() { match op { TreeOp::SExp(node) => { - cost += SHA256TREE_COST_PER_NODE; - check_cost(cost, cost_left)?; + // we could theoretically add a COST_PER_NODE on this line in the future match a.node(node) { NodeVisitor::Buffer(bytes) => { + // +1 byte to length because of prefix before atoms increment_cost_for_hash_of_bytes(bytes.len() + 1, &mut cost); check_cost(cost, cost_left)?; let hash = tree_hash_atom(bytes); hashes.push(hash); } NodeVisitor::U32(val) => { + // +1 byte to length because of prefix before atoms increment_cost_for_hash_of_bytes(a.atom_len(node) + 1, &mut cost); check_cost(cost, cost_left)?; if (val as usize) < PRECOMPUTED_HASHES.len() { @@ -71,6 +71,8 @@ pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Re } } NodeVisitor::Pair(left, right) => { + // 2 * 32byte hashes from a pair + // + 1 byte to length because of prefix before atoms increment_cost_for_hash_of_bytes(65, &mut cost); check_cost(cost, cost_left)?; diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index 721186280..7714c5086 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -7,8 +7,7 @@ seed(1337) -SHA256TREE_BASE_COST = 0 -SHA256TREE_COST_PER_NODE = 0 +SHA256TREE_BASE_COST = 87 SHA256TREE_COST_PER_32_BYTES = 700 MALLOC_COST_PER_BYTE = 10 @@ -53,7 +52,7 @@ def increment_bytes(amount: int) -> int: def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: - cost = SHA256TREE_COST_PER_NODE + cost = 0 if isinstance(obj, bytes): cost += increment_bytes(len(obj) + 1) From 73deeee6b55d19ed7b306c777b1f1555237e8f93 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 5 Dec 2025 13:56:39 +0000 Subject: [PATCH 082/110] add further comments and remove per_node calculations from benchmark-clvm-cost --- tools/src/bin/benchmark-clvm-cost.rs | 57 +--------------------------- tools/src/bin/sha256tree-benching.rs | 3 ++ 2 files changed, 4 insertions(+), 56 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index afbdbbfd5..10b4c80f5 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -298,46 +298,6 @@ fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Wri linear_regression_of(&samples).expect("linreg failed") } -// this function is used for calculating a theoretical cost per node -// we pass in the time it takes for a byte32 chunk and subtract 4*chunk_time -// we then divide by two to account for the fact that we are adding a nil atom each time the list grows too -// this is because atoms are nodes too -// at the moment this function is extremely specialised for sha256tree -fn time_per_cons_for_list( - a: &mut Allocator, - time_per_byte32: f64, - op: &Operator, - output: &mut dyn Write, -) -> (f64, f64) { - let mut samples = Vec::<(f64, f64)>::new(); - let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree - - let op_code = a.new_small_number(op.opcode).unwrap(); - let quote = a.one(); - let mut list = a.nil(); - - for _ in 0..500 { - list = a.new_pair(a.nil(), list).unwrap(); - } - - for i in 0..10000 { - // make the list longer - list = a.new_pair(a.nil(), list).unwrap(); - let quotation = a.new_pair(quote, list).unwrap(); - let call = a.new_pair(quotation, a.nil()).unwrap(); - let call = a.new_pair(op_code, call).unwrap(); - let start = Instant::now(); - run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); - let duration = start.elapsed(); - let duration_f64 = (duration.as_nanos() as f64 - (4.0 * time_per_byte32)) / 2.0; - let sample = (i as f64, duration_f64); - writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); - samples.push(sample); - } - - linear_regression_of(&samples).expect("linreg failed") -} - // cost one argument with increasing amount of bytes const PER_BYTE_COST: u32 = 1; // cost multiple arguments with increasing amount of arguments @@ -350,8 +310,6 @@ const EXPONENTIAL_COST: u32 = 8; const LARGE_BUFFERS: u32 = 16; // permit the operator to fail in tests const ALLOW_FAILURE: u32 = 32; -// increase the length of a list for the argument for the operator -const LIST_LENGTH_COST: u32 = 64; // increase byte size by 32 per step when running the linear regression const ALT_PER_BYTE_COST: u32 = 128; @@ -597,7 +555,7 @@ pub fn main() { name: "sha256tree", arg: Placeholder::SingleArg(None), extra: None, - flags: NESTING_BASE_COST | ALT_PER_BYTE_COST | LARGE_BUFFERS | LIST_LENGTH_COST, + flags: NESTING_BASE_COST | ALT_PER_BYTE_COST | LARGE_BUFFERS, }, ]; @@ -628,19 +586,6 @@ pub fn main() { let cost = slope * cost_scale; println!(" time: per-32byte: {slope:.2}ns"); println!(" cost: per-32byte: {:.0}", cost); - if (op.flags & LIST_LENGTH_COST) != 0 { - write_gnuplot_header(&mut *gnuplot, op, "per-pair", "num pairs"); - let mut output = maybe_open(options.plot, op.name, "per-pair.log"); - let (slope, intercept): (f64, f64) = - time_per_cons_for_list(&mut a, slope, op, &mut output); - let cost = slope * cost_scale; - - println!(" time: per-node: {:.2}ns", slope); - println!(" cost: per-node: {:.0}", cost); - println!(" intercept: {:.2}", intercept); - - print_plot(&mut *gnuplot, &slope, &intercept, op.name, "per-pair"); - } slope } else { 0.0 diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 452fa9aa4..13951b96d 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -10,6 +10,9 @@ use std::time::Instant; /* This file is for comparing the native sha256tree with the clvm implementation which previously existed. The costs for the native implementation should be lower as it is not required to make allocations. + +This file also outputs the timings for both the native and clvm versions so we can check that the costs +are closely aligned with the actual work done on the CPU. */ // this function is for comparing the cost per 32byte chunk of hashing between the native and clvm implementation From 46478d29aa91c9d07e27c7afddb951356437d2a5 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 5 Dec 2025 17:15:52 +0000 Subject: [PATCH 083/110] update costing to more closely reflect the sha256 operator --- op-tests/test-sha256tree-hash.txt | 60 ++++++++++++++-------------- op-tests/test-sha256tree.txt | 14 +++---- src/treehash.rs | 7 +++- tools/generate-sha256tree-tests.py | 5 ++- tools/src/bin/sha256tree-benching.rs | 8 ++-- 5 files changed, 50 insertions(+), 44 deletions(-) diff --git a/op-tests/test-sha256tree-hash.txt b/op-tests/test-sha256tree-hash.txt index d9b363081..e82746790 100644 --- a/op-tests/test-sha256tree-hash.txt +++ b/op-tests/test-sha256tree-hash.txt @@ -1,32 +1,32 @@ ; This file was generated by tools/generate-sha256tree-tests.py -sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 51507 -sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 9507 -sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1807 -sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 10207 -sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 12307 -sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 7407 -sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 1807 -sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 19307 -sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 42407 -sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 1807 -sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 20707 -sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 3907 -sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 11607 -sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 1807 -sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 38907 -sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 6707 -sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 4607 -sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 35407 -sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 6707 -sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 13007 -sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 1807 -sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 7407 -sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 3907 -sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 1107 -sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 16507 -sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 37507 -sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 26307 -sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 5307 -sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1107 -sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 6707 +sha256tree (((((0 . 0x1f7061813451ffb2830d0aab69351d593dab69e339835167) . (0 . 0xd09ba26eb2ba7592a67659ca19fc923a1631b38cd5aebb0b53baa7c68d2d89faad5c2f8e2d9ffeb9714f50a59a21ab4e)) . 0x02) . (((0x177ba5f23f0e140f805254e78fac8c2bee4fe850fc37ead18cb51314c9181e31 . 0x666f6f626172) . (0x02 . 0x666f6f626172)) . 0)) . (((0xc4c6b5a3bc74be71b5631a1dfd4cc0aae3ee363ecbb0214d . (0x01 . 0)) . (0x06d17ae983679967e26dd593cf637c98951adb3e90f349ec421212eaa30109e51b12120fc9ac3d6e527e383aaad939ae . (0 . 0x5c8b34f9fac0f365af055ef59678414793a7a5859715ea8f76bfa5ea13ff7c49))) . (0x01 . 0))) => 0x6768523ba0f32733a54551b101688a6300311e0b10e212049226d98bcd7f869d | 22579 +sha256tree (0xcdbe2cffebfda5b5dfe499e05894967d10d0e55d5bd0c9d3 . (0x16f905cd314124664cf8070035bff4faa40fdea48447770a . (0x01 . 0))) => 0x157c1e83a68a2c6cbbd92aa7ff82552b0cb2679885858695f85ed9404126ac80 | 4739 +sha256tree 0xc3041da8237d1d10610210bbad12e483fcce196c80cd189f650e6d86fda57f92 => 0x291b0356fa82ec28b5db8254c56f368273478de1fc6c8d7b85ca80706958cfa0 | 1035 +sha256tree ((0xfac7754adb5942ea853a150bbfe72c4165d7a36b35bf125240b81e5764cf52b04c6ec11682626aa49e3872e68979808a . 0) . (0x666f6f626172 . 0x666f6f626172)) => 0x9ff9d9c782362027084f4ef5ce131621d35ecde7e9cf5320b36ad7e45cf1c7e1 | 4803 +sha256tree (((0 . 0x02) . (0x18dd1f6517ae25cf64f9ddc232b7a756c3dfe0b60221d62d . 0x02)) . 0) => 0x591fbedc01c82c80596e03d4dcd75532e40520577008c6d5c6764ac2a984aa27 | 5995 +sha256tree ((0x666f6f626172 . 0x02) . 0x2a7776815b9b83c9c9049b6f5218739552e0dba6937b5e69943776aef44c9dff78f688d40641dddf52bf6818417523df) => 0xfd2359fd4190f1424be253889a1604cf8e106f0b1b791165e262dd8a652d0a05 | 3547 +sha256tree 0x68ed3d3389d15111a797fdf9434045841b3a3753d93310d593cd3c80ff27e2213e1faf566c4565ffede6bc89ec96bf89 => 0x8c9d292643d2b79627b488e277710209b0bf33e2526f96ba64019d17faf0e7d8 | 1035 +sha256tree (((0 . 0x119c26c141c87f1359bbfb2a82afd7a060043f446760e7f60229a485388eaa68) . (0x01 . 0x02)) . (0xdcc048db813648ccb794f644e9cafc6094bbe1bea5ef388f . (0x02 . 0x9c7483847dc6e351081a37c527bbda1e24b015e31f60d353b96370b77f26530d))) => 0xdec44a4d2d8752c305825b09d9e0b24031628f33f041354ccb2fc457131e750c | 8635 +sha256tree ((0 . (((0 . 0xa5c8efe9fc85db1b35d96edf0d1fef862c30fea73d1c5318de02dd1504cb88e8fded7f1ef8479b9f37204f368ac8e379) . (0x02 . 0x0df17fa06f30f7429162e282f00722e520e0a2676c69cfd2b92f678b28729524)) . 0x666f6f626172)) . (0x02 . (((0x8302df3c2a02bcca20648da3a1aa96d3f9dbd47960d90a53 . 0) . (0x01 . 0x02)) . ((0xa31acd511f4560b50be8bf18d47c1f422bab6bcdda0e69ea0152ad49c28833a78c2d4613ec8aa512cdf3a948eadcd7b9 . 0x02) . (0x6b3b39ef6fe8fb41a13a495599cadb2ded8da6ddd25518b1 . 0x01))))) => 0x7f6f3a1dd8af7087774438b0ab2770dcaacf3f42d632f3544a9f5b63c7378212 | 18747 +sha256tree 0x51cde38bba7b68ba39903e1e83c152969d896608efde132ce9f9f4bcdc6833e5fa3d21238a6cf5cceb132adf1d371a60 => 0xa2d4a03a19d0ed8bf52df8a7e26433e98c672be16ffc2137ceb7209be333c98b | 1035 +sha256tree (((0x02 . 0xa21186440ff93ecba8304bfee4997e3618b0c84c31b21533) . (0 . 0x28b70cdb54d9cc014c6aea054082229d47ea269a9b280770)) . ((0xa8dc249448c80f5004f13abd6401ad4bcb6b52a97c5a359b . 0x0f0df3479340a2d5a7d7c47cb0ea209f60953c9f85a423b5) . (0x02 . 0x666f6f626172))) => 0x1ba6e28d4eb3b955faa2d6f348261a6be3a5427d44489647ab06e366de0b2479 | 9763 +sha256tree (0x104e55ad9b1db96359ed05d6ed3ec4c207c3b6213b46b974 . 0x01) => 0xeaadf0be76b05cbbdaaae7f3d805e678039814c0a5620794ae8961052f123400 | 2227 +sha256tree (((0x6f815e4db56a306afba86916d4b29b194ceed7f492fc8a33a85d9a15cfe0536d . 0x01) . 0x95199b2f16397b23a2a70704c656f51b28e3d3d22680823d76ae4029ec4eb71a12760c82b4441a9d39b5baec010f6b7d) . 0x3c370f10cd9ec3d572cfa6a8724856b6c68a0e60912fa563f0a182323bcd6d31) => 0x4c71c2c6b8d98390697a61bd228bf1113b685f835e97552cf62feeef0d7069d1 | 4931 +sha256tree 0x7a9aa9d0946b816656299f1bd609cd0692602ce26f2f124649955b4ba54b9e8d16dcf5b392bed7189da48020f79c5bbd => 0x28a2096bc8056d3b3287a605b2dd183489e4eeb4113093d841243c2a08d83719 | 1035 +sha256tree ((((0xb941c50724b5c81213f40b621efbec33d30d2b1839ca80c81a7728bde1ba73fa . 0x8bd83d18af286870a712bb04d61d4b7fe29489cd0c7fced8dec078b631a9b0d9) . (0x02 . 0x02)) . (0x02 . 0x02)) . (((0x01 . 0x01) . (0 . 0x666f6f626172)) . ((0x01 . 0x02) . (0x01 . 0x666f6f626172)))) => 0x883633e181c8d3aee1667d530d4a2807ef242c4f961678f301b0b40ee55ab948 | 17427 +sha256tree ((0 . 0x01) . 0x01) => 0x9b28d247c983abca94e6961e6cfe6623cb9e9415d0f9e0cb04a46b3ae43369bd | 3483 +sha256tree (0x02 . 0xd6af8d56786198cde353189f35566ece010a122c7c6f181991803d7cff673b7f) => 0x938ba2209eb55f5daa22c51677f94adbbdf84566c6fb3b89ff0fa8d08d5905f2 | 2291 +sha256tree (((0 . (0xa79fbde48fc3ddeccdd64774441ab7bbd6a34f5ad1a6d78e3a3a960eea3dfbf0 . 0x666f6f626172)) . ((0xe02c71ebcfdfef147f01ed578a0844726265fb0e86cf091f93308866491f72d2 . 0x50e86350e1ddae6f2289626bd722568991a62b94f9f9ef1d4e19aa9cf5c8aee7) . (0xe98e1cfcff183a5637969e9ae20e07acb20bd662fa4262b9 . 0x02))) . (0xbb9437c1ba52ab7ca465599c57ab6d71991fca001d67274028407a245406f34f . ((0xb9a4001e4d6b805d3a3d8b6b7976ef91810056bbaca737739b8e0bda6fd5b661 . 0x02) . (0x01 . 0x666f6f626172)))) => 0xca1aa87e0b15e86b3cf1a975bb61440b607d3ded9a6d95300b19c59765d575c5 | 15107 +sha256tree ((0 . 0x02) . 0x882d2f48ed804484d9f23d38053f523e3574119071b3a7f6) => 0xb64c99c57ce8bac8cd73351de0eae02473271755f053504a7270fe02921d1a0e | 3483 +sha256tree (((0x666f6f626172 . 0x02) . (0 . 0x666f6f626172)) . 0x3d2f00fbfd0278404cd508c4afb460f55b116e5733240c4f4a30a5a790beed1c) => 0x3f6498812d3a5d119db27f679b938558e7d74c9cd921bc28bb6245823ca1f7fe | 6059 +sha256tree 0x57afc714e65003547db455176d5f85cc6594b88e5d28f87bf49ec82135fa791b09d6a102c34787fec98544486b888023 => 0xbc6c9852407944ab4bd3749b61216e3ab64c1d1e11b56be0cc30fd0344e34085 | 1035 +sha256tree ((0x3e9be38550994f6fa12c2ba30bc582ceda9f715d5a3a4af06d3a4f35d1b5d242a0b825d74a3b7e3086590b22172f5eeb . 0x02) . 0) => 0xc8d024ec5e8b001598920071454558cd6a2831742829cb507e744d79b0dbb09c | 3547 +sha256tree (0 . 0x51e7d76d53afdbbe486df4f6eb42197aa985154283ed028c) => 0xbc02de5bb1e9feccc7adc78cdc6ca567c0311df0543e46434dc795edebc67dbf | 2227 +sha256tree 0x666f6f626172 => 0xf03942eca4827c93931fee97f117479ef474c9aaa449655ddffb48886bde58ad | 971 +sha256tree (0x934128806851cb5eba9d244dfce1734a41c190bde679560a22fe90a87a4f775c . (0x02 . ((0 . 0x02) . (0x62e282852844353b813fd3a967e9af12b596a2ba912236363ed6869f2669546e . 0x02)))) => 0x9063f5dcb016de08e12d1f8b4c893a9f1c008d08cd0c543c5707a2a328feb6c9 | 7379 +sha256tree (((((0x02 . 0x02) . (0 . 0x01)) . (0xd69626fe507014737c49d9b7cf950721c7142ce1b217d2c4c654b2274615129a2374a9a5700d37188047b55ecf1d55dd . (0x4dca4553f9115e233dda1e184759e201f1de5fe11d3f2dc0 . 0xbb2aa251bf2f429ddf6a48a0d70ca6ceeb9dda50428cabde))) . (((0x02 . 0) . 0x01) . (0x16f07f1f29699553d8064284a6a324b906ff081a195075e5a9d0c6c399b2492a2e13aa69bd7a236ae2de60a2a7725306 . 0xfc7fd2354396ab20b07d6f07259c8691456ef7989feee44a04f2d87c96d7b4cecfc039e2ba04f86da20b062bc9ccda38))) . 0x1e122d35a2ac075a48dc607a8a428586a3a06591f6e20e71c95bb94dcaaf95a6165d97a7d8f90e8ae5517468cba60afc) => 0xcf43ccb9fdabc87ac031c3f372addd99e91103ef5d89e389a186e0215a620be0 | 16299 +sha256tree ((0x4295ead455b30626cd3dc0aed97dd4be40f4fe965f337484092ba6650c1e5fb0 . ((0x666f6f626172 . 0x01) . (0x231bcedef942bcc4b1c99f8b26382a7433866dc96cd617f59d39ebd5f10fe23b . 0x02))) . (((0xa27954b60506b8f2cb4c56398797eb9d640ca976b9c21126a39e14af0fdb94ac . 0x01) . 0x67a0ad6c4e30c5e702cc723d57b00ff3bf818fc553093b1970aa6a9c2698f87098fbf322ee3047d144c266b2f425da32) . 0)) => 0xa0bf50e2536ab240ad5e55d8ff761722a87fef03f1d57eb13566cb7a41185db0 | 11275 +sha256tree (0x0fb2228f690832f6f2d1f08afeeaf00352b182d49a7db18c5bc335b95609d9503ba3f97054599526ae316a63a4d72fed . 0x64acfa2fd1990e8b55989a4ff9544b7368e9eccfe3c98716a1c330db640be65b) => 0xef13a74e8149d647e668f323049257a5f0ae16ec6d1f64a2ca2404cb670f233a | 2355 +sha256tree 0x01 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 971 +sha256tree (0x02 . (0 . 0x666f6f626172)) => 0xfd1c831b92f4ca52591d92502f97d00dbe004f454873c989bd1161978cb06ab6 | 3483 diff --git a/op-tests/test-sha256tree.txt b/op-tests/test-sha256tree.txt index 1026b5ec9..e2482011c 100644 --- a/op-tests/test-sha256tree.txt +++ b/op-tests/test-sha256tree.txt @@ -1,10 +1,10 @@ -sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 1107 -sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 1107 -sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 1107 -sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 3907 -sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 3907 -sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 12307 -sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 15107 +sha256tree 1 => 0x9dcf97a184f32623d11a73124ceb99a5709b083721e878a16d78f596718ba7b2 | 971 +sha256tree 0x00cafef00d => 0x60bc5062a80c4363cbe881815300d349c5524d98e3502a016466d14d1f3f25d9 | 971 +sha256tree () => 0x4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a | 971 +sha256tree (() . ()) => 0x52db9ef97986e7382ef78b8eae2dacdbb2ce823ed1396a0fb2f7f120a2b40a63 | 2227 +sha256tree (0x00cafe . 0x00f00d) => 0x005b24012b958cc8fc19b7d45438f82fb9034c24b2a77d219250316f482ba6b6 | 2227 +sha256tree (202 254 240 13) => 0xb5e2a23ac2de5105c72b9658428a3d389a9f909012eedd4b641504d981f81ac5 | 5995 +sha256tree (202 254 (240 13)) => 0x5edd993092c97418933750dc25add55a84ac9f07b2067d537d38f0044bd317ae | 7251 sha256tree 10 20 => FAIL sha256tree (10 . 20) 30 => FAIL sha256tree (10 . 20) (20 . 30) => FAIL \ No newline at end of file diff --git a/src/treehash.rs b/src/treehash.rs index d734fb21d..5254b1d7d 100644 --- a/src/treehash.rs +++ b/src/treehash.rs @@ -11,8 +11,11 @@ use chia_sha2::Sha256; // the base cost is the cost of calling it to begin with // this is set to the same as sha256 const SHA256TREE_BASE_COST: Cost = 87; +// this cost is applied for every node we traverse to +const SHA256TREE_NODE_COST: Cost = 500; // this is the cost for every 32 bytes in a sha256 call -const SHA256TREE_COST_PER_32_BYTES: Cost = 700; +// it is set to the same as sha256 +const SHA256TREE_COST_PER_32_BYTES: Cost = 64; pub fn tree_hash_atom(bytes: &[u8]) -> [u8; 32] { let mut sha256 = Sha256::new(); @@ -52,6 +55,8 @@ pub fn tree_hash_costed(a: &mut Allocator, node: NodePtr, cost_left: Cost) -> Re match op { TreeOp::SExp(node) => { // we could theoretically add a COST_PER_NODE on this line in the future + cost += SHA256TREE_NODE_COST; + check_cost(cost, cost_left)?; match a.node(node) { NodeVisitor::Buffer(bytes) => { // +1 byte to length because of prefix before atoms diff --git a/tools/generate-sha256tree-tests.py b/tools/generate-sha256tree-tests.py index 7714c5086..043ac90e5 100644 --- a/tools/generate-sha256tree-tests.py +++ b/tools/generate-sha256tree-tests.py @@ -8,7 +8,8 @@ seed(1337) SHA256TREE_BASE_COST = 87 -SHA256TREE_COST_PER_32_BYTES = 700 +SHA256TREE_COST_PER_NODE = 500 +SHA256TREE_COST_PER_32_BYTES = 64 MALLOC_COST_PER_BYTE = 10 SIZE = 30 @@ -52,7 +53,7 @@ def increment_bytes(amount: int) -> int: def compute_tree_hash_and_cost(obj) -> tuple[bytes, int]: - cost = 0 + cost = SHA256TREE_COST_PER_NODE if isinstance(obj, bytes): cost += increment_bytes(len(obj) + 1) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 13951b96d..dfebc28bd 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -136,8 +136,8 @@ fn time_per_cons_for_list( let duration = start.elapsed().as_nanos() as f64; // a new list entry is 2 nodes (a cons and a nil) and a 3 chunk hash operation and a 1 chunk hash operation // this equation lets us figure out a theoretical cost just for a node - let duration = (duration - (4.0 * bytes32_native_time)) / 2.0; - let cost = (cost as f64 - (4.0 * bytes32_native_cost)) / 2.0; + let duration = (duration - (500.0 + i as f64) * (4.0 * bytes32_native_time)) / 2.0; + let cost = (cost as f64 - (500.0 + i as f64) * (4.0 * bytes32_native_cost)) / 2.0; writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); samples_time_native.push((i as f64, duration)); @@ -153,8 +153,8 @@ fn time_per_cons_for_list( let duration = start.elapsed().as_nanos() as f64; // a new list entry is 2 nodes (a cons and a nil) and a 3 chunk hash operation and a 1 chunk hash operation // this equation lets us figure out a theoretical cost just for a node - let duration = (duration - (4.0 * bytes32_clvm_time)) / 2.0; - let cost = (cost as f64 - (4.0 * bytes32_clvm_cost)) / 2.0; + let duration = (duration - (500.0 + i as f64) * (4.0 * bytes32_clvm_time)) / 2.0; + let cost = (cost as f64 - (500.0 + i as f64) * (4.0 * bytes32_clvm_cost)) / 2.0; writeln!(output_clvm_time, "{}\t{}", i, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", i, cost).unwrap(); samples_time_clvm.push((i as f64, duration)); From 0f627185bd47d6708129ea09a819f08ee32abd00 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:17:10 +0000 Subject: [PATCH 084/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 10b4c80f5..86477e50a 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -310,7 +310,7 @@ const EXPONENTIAL_COST: u32 = 8; const LARGE_BUFFERS: u32 = 16; // permit the operator to fail in tests const ALLOW_FAILURE: u32 = 32; -// increase byte size by 32 per step when running the linear regression +// measure the timing per 32 bytes block of atom argument const ALT_PER_BYTE_COST: u32 = 128; struct Operator { From 2d0bc2deb39931faa87abba7172ff302e2f5e4bc Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:17:26 +0000 Subject: [PATCH 085/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 86477e50a..e06fad48a 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -582,7 +582,7 @@ pub fn main() { time_per_byte } else if (op.flags & ALT_PER_BYTE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); - let (slope, _intercept) = time_per_byte_for_atom(&mut a, op, &mut output); + let (slope, _intercept) = time_per_32bytes_for_atom(&mut a, op, &mut output); let cost = slope * cost_scale; println!(" time: per-32byte: {slope:.2}ns"); println!(" cost: per-32byte: {:.0}", cost); From 2823b16706f1020b75196e0fe4b746848708b927 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:17:59 +0000 Subject: [PATCH 086/110] Update tools/src/bin/benchmark-clvm-cost.rs Co-authored-by: Arvid Norberg --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index e06fad48a..bca1d5542 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -269,7 +269,7 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - // this adds 32 bytes at a time compared to per_byte which adds 5 at a time // at the moment this function is specialised for sha256tree but may be relevant to other hash operations in the future -fn time_per_byte_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { +fn time_per_32bytes_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree From 3ac31f7225b7448ed25215ed5132d4ab4956a555 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 9 Dec 2025 12:19:28 +0000 Subject: [PATCH 087/110] fmt --- tools/src/bin/benchmark-clvm-cost.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index bca1d5542..cdf92dd4b 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -269,7 +269,11 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - // this adds 32 bytes at a time compared to per_byte which adds 5 at a time // at the moment this function is specialised for sha256tree but may be relevant to other hash operations in the future -fn time_per_32bytes_for_atom(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> (f64, f64) { +fn time_per_32bytes_for_atom( + a: &mut Allocator, + op: &Operator, + output: &mut dyn Write, +) -> (f64, f64) { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree From dd4c4a9eb31cc1863bac36a704912df70b5b7c18 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 9 Dec 2025 16:44:21 +0000 Subject: [PATCH 088/110] add new cost_factor calculator into benchmark-clvm-cost.rs --- tools/src/bin/benchmark-clvm-cost.rs | 87 +++++++++++++++++++++------- 1 file changed, 67 insertions(+), 20 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index cdf92dd4b..2e338d83f 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -115,25 +115,38 @@ fn substitute(args: Placeholder, s: NodePtr) -> OpArgs { } } -fn time_invocation(a: &mut Allocator, op: u32, arg: OpArgs, flags: u32) -> f64 { +fn time_invocation( + a: &mut Allocator, + op: u32, + arg: OpArgs, + flags: u32, + cost_samples: &mut Vec<(f64, f64)>, +) -> f64 { let call = build_call(a, op, arg, 1, None); //println!("{:x?}", &Node::new(a, call)); let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11000000000); - if (flags & ALLOW_FAILURE) == 0 { - r.unwrap(); - } - if (flags & EXPONENTIAL_COST) != 0 { + let time = if (flags & EXPONENTIAL_COST) != 0 { (start.elapsed().as_nanos() as f64).sqrt() } else { start.elapsed().as_nanos() as f64 + }; + if (flags & ALLOW_FAILURE) == 0 { + let cost = r.unwrap().0; + cost_samples.push((time.clone(), cost as f64)); } + time } // returns the time per byte // measures run-time of many calls -fn time_per_byte(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { +fn time_per_byte( + a: &mut Allocator, + op: &Operator, + output: &mut dyn Write, + cost_samples: &mut Vec<(f64, f64)>, +) -> f64 { let checkpoint = a.checkpoint(); let mut samples = Vec::<(f64, f64)>::new(); let mut atom = vec![0; 10000000]; @@ -152,7 +165,7 @@ fn time_per_byte(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f6 let arg = substitute(op.arg, quote(a, subst)); let sample = ( i as f64 * scale as f64, - time_invocation(a, op.opcode, arg, op.flags), + time_invocation(a, op.opcode, arg, op.flags, cost_samples), ); writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); @@ -167,7 +180,12 @@ fn time_per_byte(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f6 // returns the time per argument // measures the run-time of many calls with varying number of arguments, to // establish how much time each additional argument contributes -fn time_per_arg(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { +fn time_per_arg( + a: &mut Allocator, + op: &Operator, + output: &mut dyn Write, + cost_samples: &mut Vec<(f64, f64)>, +) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); @@ -186,11 +204,12 @@ fn time_per_arg(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 let call = build_call(a, op.opcode, arg, i, op.extra); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11000000000); - if (op.flags & ALLOW_FAILURE) == 0 { - r.unwrap(); - } let duration = start.elapsed(); let sample = (i as f64, duration.as_nanos() as f64); + if (op.flags & ALLOW_FAILURE) == 0 { + let cost = r.unwrap().0; + cost_samples.push((sample.1, cost as f64)); + } writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); @@ -209,6 +228,7 @@ fn base_call_time( op: &Operator, per_arg_time: f64, output: &mut dyn Write, + cost_samples: &mut Vec<(f64, f64)>, ) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); @@ -229,12 +249,14 @@ fn base_call_time( let call = build_nested_call(a, op.opcode, arg, i, op.extra); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11_000_000_000); - if (op.flags & ALLOW_FAILURE) == 0 { - r.unwrap(); - } + let duration = start.elapsed(); let duration = (duration.as_nanos() as f64) - (per_arg_time * i as f64); let sample = (i as f64, duration); + if (op.flags & ALLOW_FAILURE) == 0 { + let cost = r.unwrap().0; + cost_samples.push((duration, cost as f64)); + } writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); } @@ -244,7 +266,12 @@ fn base_call_time( slope } -fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) -> f64 { +fn base_call_time_no_nest( + a: &mut Allocator, + op: &Operator, + per_arg_time: f64, + cost_samples: &mut Vec<(f64, f64)>, +) -> f64 { let mut total_time: f64 = 0.0; let mut num_samples = 0; @@ -260,7 +287,13 @@ fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) - for _i in 0..300 { a.restore_checkpoint(&checkpoint); - total_time += time_invocation(a, op.opcode, arg, op.flags & !EXPONENTIAL_COST); + total_time += time_invocation( + a, + op.opcode, + arg, + op.flags & !EXPONENTIAL_COST, + cost_samples, + ); num_samples += 1; } @@ -373,6 +406,8 @@ fn print_plot(gnuplot: &mut dyn Write, a: &f64, b: &f64, op: &str, name: &str) { pub fn main() { let options = Args::parse(); + let mut cost_factor_samples = Vec::<(f64, f64)>::new(); + let mut a = Allocator::new(); let g1 = a.new_atom(&hex::decode("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb").unwrap()).unwrap(); @@ -580,7 +615,7 @@ pub fn main() { println!("opcode: {} ({})", op.name, op.opcode); let time_per_byte = if (op.flags & PER_BYTE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); - let time_per_byte = time_per_byte(&mut a, op, &mut *output); + let time_per_byte = time_per_byte(&mut a, op, &mut *output, &mut cost_factor_samples); println!(" time: per-byte: {time_per_byte:.2}ns"); println!(" cost: per-byte: {:.0}", time_per_byte * cost_scale); time_per_byte @@ -596,7 +631,7 @@ pub fn main() { }; let time_per_arg = if (op.flags & PER_ARG_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-arg.log"); - let time_per_arg = time_per_arg(&mut a, op, &mut *output); + let time_per_arg = time_per_arg(&mut a, op, &mut *output, &mut cost_factor_samples); println!(" time: per-arg: {time_per_arg:.2}ns"); println!(" cost: per-arg: {:.0}", time_per_arg * arg_cost_scale); time_per_arg @@ -606,7 +641,13 @@ pub fn main() { let base_call_time = if (op.flags & NESTING_BASE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "base.log"); write_gnuplot_header(&mut *gnuplot, op, "base", "num nested calls"); - let base_call_time = base_call_time(&mut a, op, time_per_arg, &mut *output); + let base_call_time = base_call_time( + &mut a, + op, + time_per_arg, + &mut *output, + &mut cost_factor_samples, + ); println!(" time: base: {base_call_time:.2}ns"); println!( " cost: base: {:.0}", @@ -616,7 +657,8 @@ pub fn main() { print_plot(&mut *gnuplot, &base_call_time, &0.0, op.name, "base"); base_call_time } else { - let base_call_time = base_call_time_no_nest(&mut a, op, time_per_arg); + let base_call_time = + base_call_time_no_nest(&mut a, op, time_per_arg, &mut cost_factor_samples); println!(" time: base: {base_call_time:.2}ns"); println!( " cost: base: {:.0}", @@ -651,6 +693,11 @@ pub fn main() { ); } } + let new_cost_factor: (f64, f64) = linear_regression_of(&cost_factor_samples).unwrap(); + println!( + "Newly calculated cost_factor for this computer is: {}", + new_cost_factor.0 + ); if options.plot { println!("To generate plots, run:\n (cd measurements; gnuplot gen-graphs.gnuplot)"); } From 0519dc56d2ad01d88a7f448f8dc13a7aead80ab2 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Tue, 9 Dec 2025 18:08:04 +0000 Subject: [PATCH 089/110] bench per type --- tools/src/bin/benchmark-clvm-cost.rs | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 2e338d83f..4510a0eea 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -208,7 +208,9 @@ fn time_per_arg( let sample = (i as f64, duration.as_nanos() as f64); if (op.flags & ALLOW_FAILURE) == 0 { let cost = r.unwrap().0; - cost_samples.push((sample.1, cost as f64)); + if sample.1 > 0.0 { + cost_samples.push((sample.1, cost as f64)); + }; } writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); @@ -255,7 +257,9 @@ fn base_call_time( let sample = (i as f64, duration); if (op.flags & ALLOW_FAILURE) == 0 { let cost = r.unwrap().0; - cost_samples.push((duration, cost as f64)); + if duration > 0.0 { + cost_samples.push((duration, cost as f64)) + }; } writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); @@ -407,6 +411,8 @@ pub fn main() { let options = Args::parse(); let mut cost_factor_samples = Vec::<(f64, f64)>::new(); + let mut base_cost_factor_samples = Vec::<(f64, f64)>::new(); + let mut args_cost_factor_samples = Vec::<(f64, f64)>::new(); let mut a = Allocator::new(); @@ -631,7 +637,8 @@ pub fn main() { }; let time_per_arg = if (op.flags & PER_ARG_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-arg.log"); - let time_per_arg = time_per_arg(&mut a, op, &mut *output, &mut cost_factor_samples); + let time_per_arg = + time_per_arg(&mut a, op, &mut *output, &mut args_cost_factor_samples); println!(" time: per-arg: {time_per_arg:.2}ns"); println!(" cost: per-arg: {:.0}", time_per_arg * arg_cost_scale); time_per_arg @@ -646,7 +653,7 @@ pub fn main() { op, time_per_arg, &mut *output, - &mut cost_factor_samples, + &mut base_cost_factor_samples, ); println!(" time: base: {base_call_time:.2}ns"); println!( @@ -658,7 +665,7 @@ pub fn main() { base_call_time } else { let base_call_time = - base_call_time_no_nest(&mut a, op, time_per_arg, &mut cost_factor_samples); + base_call_time_no_nest(&mut a, op, time_per_arg, &mut base_cost_factor_samples); println!(" time: base: {base_call_time:.2}ns"); println!( " cost: base: {:.0}", @@ -698,6 +705,16 @@ pub fn main() { "Newly calculated cost_factor for this computer is: {}", new_cost_factor.0 ); + let new_base_cost_factor: (f64, f64) = linear_regression_of(&base_cost_factor_samples).unwrap(); + println!( + "Newly calculated base_cost_factor for this computer is: {}", + new_base_cost_factor.0 + ); + let new_args_cost_factor: (f64, f64) = linear_regression_of(&args_cost_factor_samples).unwrap(); + println!( + "Newly calculated args_cost_factor for this computer is: {}", + new_args_cost_factor.0 + ); if options.plot { println!("To generate plots, run:\n (cd measurements; gnuplot gen-graphs.gnuplot)"); } From b992caf7a2c6951112d031e9855044cc07c299b4 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 15 Dec 2025 15:18:47 +0000 Subject: [PATCH 090/110] add balanced binary tree --- tools/src/bin/sha256tree-benching.rs | 108 ++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index dfebc28bd..2c48231f2 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -15,6 +15,81 @@ This file also outputs the timings for both the native and clvm versions so we c are closely aligned with the actual work done on the CPU. */ +// this function calculates the cost per node theoretically +// for a perfectly balanced binary tree +#[allow(clippy::type_complexity)] +#[allow(clippy::too_many_arguments)] +fn time_per_cons_for_balanced_tree( + a: &mut Allocator, + sha_prog: NodePtr, + mut output_native_time: impl Write, + mut output_clvm_time: impl Write, + mut output_native_cost: impl Write, + mut output_clvm_cost: impl Write, +) -> ( + f64, + f64, // time slopes + f64, + f64, // cost slopes +) { + let mut samples_time_native = Vec::<(f64, f64)>::new(); + let mut samples_time_clvm = Vec::<(f64, f64)>::new(); + let mut samples_cost_native = Vec::<(f64, f64)>::new(); + let mut samples_cost_clvm = Vec::<(f64, f64)>::new(); + + let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); + let op_code = a.new_small_number(63).unwrap(); + let quote = a.one(); + + // leaf atom (not pre-processed) + let mut tree = a.new_atom(&[0xff, 0xff]).unwrap(); + + for i in 0..10 { + // double the number of leaves each iteration + tree = a.new_pair(tree, tree).unwrap(); + + let leaf_count = 2 ^ i; + + let q = a.new_pair(quote, tree).unwrap(); + let call = a.new_pair(q, a.nil()).unwrap(); + let call = a.new_pair(op_code, call).unwrap(); + + // native + let start = Instant::now(); + let red = run_program(a, &dialect, call, a.nil(), 11_000_000_000).unwrap(); + let cost = red.0; + let result_1 = node_to_bytes(a, red.1).expect("should work"); + let duration = start.elapsed().as_nanos() as f64; + + writeln!(output_native_time, "{}\t{}", leaf_count, duration).unwrap(); + writeln!(output_native_cost, "{}\t{}", leaf_count, cost).unwrap(); + + samples_time_native.push((leaf_count as f64, duration)); + samples_cost_native.push((leaf_count as f64, cost as f64)); + + // clvm + let start = Instant::now(); + let red = run_program(a, &dialect, sha_prog, tree, 11_000_000_000).unwrap(); + let cost = red.0; + let result_2 = node_to_bytes(a, red.1).expect("should work"); + assert_eq!(result_1, result_2); + let duration = start.elapsed().as_nanos() as f64; + + writeln!(output_clvm_time, "{}\t{}", leaf_count, duration).unwrap(); + writeln!(output_clvm_cost, "{}\t{}", leaf_count, cost).unwrap(); + + samples_time_clvm.push((leaf_count as f64, duration)); + samples_cost_clvm.push((leaf_count as f64, cost as f64)); + } + + ( + linear_regression_of(&samples_time_native).unwrap().0, + linear_regression_of(&samples_time_clvm).unwrap().0, + linear_regression_of(&samples_cost_native).unwrap().0, + linear_regression_of(&samples_cost_clvm).unwrap().0, + ) +} + // this function is for comparing the cost per 32byte chunk of hashing between the native and clvm implementation #[allow(clippy::type_complexity)] fn time_per_byte_for_atom( @@ -114,6 +189,7 @@ fn time_per_cons_for_list( let quote = a.one(); let mut list = a.nil(); + // we use an atom that isn't part of the pre-processed list let atom = a.new_atom(&[0xff, 0xff]).unwrap(); for _ in 0..500 { @@ -182,11 +258,15 @@ fn main() { let atom_clvm_time = BufWriter::new(File::create("atom_clvm.dat").unwrap()); let cons_native_time = BufWriter::new(File::create("cons_native.dat").unwrap()); let cons_clvm_time = BufWriter::new(File::create("cons_clvm.dat").unwrap()); + let tree_native_time = BufWriter::new(File::create("tree_native.dat").unwrap()); + let tree_clvm_time = BufWriter::new(File::create("tree_clvm.dat").unwrap()); let atom_native_cost = BufWriter::new(File::create("atom_native_cost.dat").unwrap()); let atom_clvm_cost = BufWriter::new(File::create("atom_clvm_cost.dat").unwrap()); let cons_native_cost = BufWriter::new(File::create("cons_native_cost.dat").unwrap()); let cons_clvm_cost = BufWriter::new(File::create("cons_clvm_cost.dat").unwrap()); + let tree_native_cost = BufWriter::new(File::create("tree_native_cost.dat").unwrap()); + let tree_clvm_cost = BufWriter::new(File::create("tree_clvm_cost.dat").unwrap()); let (atom_nat_t, atom_clvm_t, atom_nat_c, atom_clvm_c) = time_per_byte_for_atom( &mut a, @@ -197,6 +277,15 @@ fn main() { atom_clvm_cost, ); + let (leaf_nat_t, leaf_clvm_t, leaf_nat_c, leaf_clvm_c) = time_per_cons_for_balanced_tree( + &mut a, + shaprog, + tree_native_time, + tree_clvm_time, + tree_native_cost, + tree_clvm_cost, + ); + let (cons_nat_t, cons_clvm_t, cons_nat_c, cons_clvm_c) = time_per_cons_for_list( &mut a, shaprog, @@ -224,9 +313,26 @@ fn main() { "CLVM (time_per_bytes * cost_ratio : {:.4}", atom_clvm_t * cost_scale ); - println!("Native cost per bytes32 : {:.4}", atom_nat_c); println!("CLVM cost per bytes32 : {:.4}", atom_clvm_c); + println!(); + + // this is the costing of the balanced binary tree + println!("Costs based on balanced binary tree: "); + println!("Native time per leaf (ns): {:.4}", leaf_nat_t); + println!("CLVM time per leaf (ns): {:.4}", leaf_clvm_t); + println!( + "Native (time_per_leaf * cost_ratio): {:.4}", + leaf_nat_t * cost_scale + ); + println!( + "CLVM (time_per_leaf * cost_ratio : {:.4}", + leaf_clvm_t * cost_scale + ); + + println!("Native cost per leaf : {:.4}", leaf_nat_c); + println!("CLVM cost per leaf : {:.4}", leaf_clvm_c); + println!(); // this is described as estimated as we're adding a cons and a nil atom each time // and then we're subtracting the costs to calculate what a single node might theoretically cost From d9279937ecf60264acdabb0bee07613661931c0f Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 15 Dec 2025 16:11:52 +0000 Subject: [PATCH 091/110] clippy fix --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 4510a0eea..e91024818 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -134,7 +134,7 @@ fn time_invocation( }; if (flags & ALLOW_FAILURE) == 0 { let cost = r.unwrap().0; - cost_samples.push((time.clone(), cost as f64)); + cost_samples.push((time, cost as f64)); } time } From 92765ad0c62fe8cfcc54426f3c965f6005bd87af Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Mon, 15 Dec 2025 17:10:15 +0000 Subject: [PATCH 092/110] add percent factor and output its affect on cost --- tools/src/bin/sha256tree-benching.rs | 30 ++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 2c48231f2..ccf8d2eb5 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -305,6 +305,11 @@ fn main() { println!("Costs per bytes32 chunk: "); println!("Native time per bytes32 (ns): {:.4}", atom_nat_t); println!("CLVM time per bytes32 (ns): {:.4}", atom_clvm_t); + let native_vs_clvm_ratio = atom_nat_t / atom_clvm_t; + println!( + "Native implementation takes {:.4}% of the time.", + native_vs_clvm_ratio * 100.0 + ); println!( "Native (time_per_bytes32 * cost_ratio): {:.4}", atom_nat_t * cost_scale @@ -315,12 +320,22 @@ fn main() { ); println!("Native cost per bytes32 : {:.4}", atom_nat_c); println!("CLVM cost per bytes32 : {:.4}", atom_clvm_c); + println!( + "{:.4}% of the CLVM cost is: : {:.4}", + native_vs_clvm_ratio * 100.0, + atom_clvm_c * native_vs_clvm_ratio + ); println!(); // this is the costing of the balanced binary tree println!("Costs based on balanced binary tree: "); println!("Native time per leaf (ns): {:.4}", leaf_nat_t); println!("CLVM time per leaf (ns): {:.4}", leaf_clvm_t); + let native_vs_clvm_ratio = leaf_nat_t / leaf_clvm_t; + println!( + "Native implementation takes {:.4}% of the time.", + native_vs_clvm_ratio * 100.0 + ); println!( "Native (time_per_leaf * cost_ratio): {:.4}", leaf_nat_t * cost_scale @@ -332,6 +347,11 @@ fn main() { println!("Native cost per leaf : {:.4}", leaf_nat_c); println!("CLVM cost per leaf : {:.4}", leaf_clvm_c); + println!( + "{:.4}% of the CLVM cost is: : {:.4}", + native_vs_clvm_ratio * 100.0, + leaf_clvm_c * native_vs_clvm_ratio + ); println!(); // this is described as estimated as we're adding a cons and a nil atom each time @@ -339,6 +359,11 @@ fn main() { println!("Estimated costs per node results: "); println!("Native time per node (ns): {:.4}", cons_nat_t); println!("CLVM time per node (ns): {:.4}", cons_clvm_t); + let native_vs_clvm_ratio = cons_nat_t / cons_clvm_t; + println!( + "Native implementation takes {:.4}% of the time.", + native_vs_clvm_ratio * 100.0 + ); println!( "Native (time_per_node * cost_ratio): {:.4}", cons_nat_t * cost_scale @@ -349,6 +374,11 @@ fn main() { ); println!("Native cost per node : {:.4}", cons_nat_c); println!("CLVM cost per node : {:.4}", cons_clvm_c); + println!( + "{:.4}% of the CLVM cost is: : {:.4}", + native_vs_clvm_ratio * 100.0, + cons_clvm_c * native_vs_clvm_ratio + ); // gnuplot script let mut gp = File::create("plots.gnuplot").unwrap(); From fc17ebe3f9df492e18fd8a87dc6013bb4d8086fc Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 11:55:37 +0000 Subject: [PATCH 093/110] improve per-node calculation for balanced tree --- tools/src/bin/sha256tree-benching.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index ccf8d2eb5..3fcb248f2 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -64,8 +64,8 @@ fn time_per_cons_for_balanced_tree( writeln!(output_native_time, "{}\t{}", leaf_count, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", leaf_count, cost).unwrap(); - samples_time_native.push((leaf_count as f64, duration)); - samples_cost_native.push((leaf_count as f64, cost as f64)); + samples_time_native.push((((leaf_count * 2) - 1) as f64, duration)); + samples_cost_native.push((((leaf_count * 2) - 1) as f64, cost as f64)); // clvm let start = Instant::now(); @@ -78,8 +78,10 @@ fn time_per_cons_for_balanced_tree( writeln!(output_clvm_time, "{}\t{}", leaf_count, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", leaf_count, cost).unwrap(); - samples_time_clvm.push((leaf_count as f64, duration)); - samples_cost_clvm.push((leaf_count as f64, cost as f64)); + // internal node count == leaf_count - 1 + // total nodes == (leaf_count * 2) - 1 + samples_time_clvm.push((((leaf_count * 2) - 1) as f64, duration)); + samples_cost_clvm.push((((leaf_count * 2) - 1) as f64, cost as f64)); } ( @@ -329,24 +331,24 @@ fn main() { // this is the costing of the balanced binary tree println!("Costs based on balanced binary tree: "); - println!("Native time per leaf (ns): {:.4}", leaf_nat_t); - println!("CLVM time per leaf (ns): {:.4}", leaf_clvm_t); + println!("Native time per node (ns): {:.4}", leaf_nat_t); + println!("CLVM time per node (ns): {:.4}", leaf_clvm_t); let native_vs_clvm_ratio = leaf_nat_t / leaf_clvm_t; println!( "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); println!( - "Native (time_per_leaf * cost_ratio): {:.4}", + "Native (time_per_node * cost_ratio): {:.4}", leaf_nat_t * cost_scale ); println!( - "CLVM (time_per_leaf * cost_ratio : {:.4}", + "CLVM (time_per_node * cost_ratio : {:.4}", leaf_clvm_t * cost_scale ); - println!("Native cost per leaf : {:.4}", leaf_nat_c); - println!("CLVM cost per leaf : {:.4}", leaf_clvm_c); + println!("Native cost per node : {:.4}", leaf_nat_c); + println!("CLVM cost per node : {:.4}", leaf_clvm_c); println!( "{:.4}% of the CLVM cost is: : {:.4}", native_vs_clvm_ratio * 100.0, From 1c809d7cbcc2a1cd2cdad9f6bd8a099f279a0a05 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 12:59:07 +0000 Subject: [PATCH 094/110] clarify sections in terminal output --- tools/src/bin/sha256tree-benching.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 3fcb248f2..f2f774b8e 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -304,7 +304,7 @@ fn main() { // taken from benchmark-clvm-cost.rs let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; - println!("Costs per bytes32 chunk: "); + println!("Costs based on an increasing atom per bytes32 chunks: "); println!("Native time per bytes32 (ns): {:.4}", atom_nat_t); println!("CLVM time per bytes32 (ns): {:.4}", atom_clvm_t); let native_vs_clvm_ratio = atom_nat_t / atom_clvm_t; @@ -330,7 +330,7 @@ fn main() { println!(); // this is the costing of the balanced binary tree - println!("Costs based on balanced binary tree: "); + println!("Costs based on growing a balanced binary tree: "); println!("Native time per node (ns): {:.4}", leaf_nat_t); println!("CLVM time per node (ns): {:.4}", leaf_clvm_t); let native_vs_clvm_ratio = leaf_nat_t / leaf_clvm_t; @@ -358,7 +358,7 @@ fn main() { // this is described as estimated as we're adding a cons and a nil atom each time // and then we're subtracting the costs to calculate what a single node might theoretically cost - println!("Estimated costs per node results: "); + println!("Costs based on growing a list: "); println!("Native time per node (ns): {:.4}", cons_nat_t); println!("CLVM time per node (ns): {:.4}", cons_clvm_t); let native_vs_clvm_ratio = cons_nat_t / cons_clvm_t; From 9dc63675863010da5fdd59ebd1b7136d8c4f5c54 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 13:02:18 +0000 Subject: [PATCH 095/110] fix leaf count calculation --- tools/src/bin/sha256tree-benching.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index f2f774b8e..b6b264c3e 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -48,7 +48,7 @@ fn time_per_cons_for_balanced_tree( // double the number of leaves each iteration tree = a.new_pair(tree, tree).unwrap(); - let leaf_count = 2 ^ i; + let leaf_count = 2_i32.pow(i); let q = a.new_pair(quote, tree).unwrap(); let call = a.new_pair(q, a.nil()).unwrap(); From 804501380a48364295e30e2c0d38799876eace25 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 15:58:15 +0000 Subject: [PATCH 096/110] subtract out bytes32 chunk hashing in benchmark --- tools/src/bin/sha256tree-benching.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index b6b264c3e..9d318346c 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -22,6 +22,10 @@ are closely aligned with the actual work done on the CPU. fn time_per_cons_for_balanced_tree( a: &mut Allocator, sha_prog: NodePtr, + bytes32_native_cost: f64, + bytes32_clvm_cost: f64, + bytes32_native_time: f64, + bytes32_clvm_time: f64, mut output_native_time: impl Write, mut output_clvm_time: impl Write, mut output_native_cost: impl Write, @@ -61,9 +65,16 @@ fn time_per_cons_for_balanced_tree( let result_1 = node_to_bytes(a, red.1).expect("should work"); let duration = start.elapsed().as_nanos() as f64; + // subtract out the duration of hashing all the 32byte chunks + let duration = duration - ((2 * leaf_count - 1) as f64 * bytes32_native_time); + // subtract out the cost of hashing all the 32byte chunks + let cost = cost as f64 - ((2 * leaf_count - 1) as f64 * bytes32_native_cost); + writeln!(output_native_time, "{}\t{}", leaf_count, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", leaf_count, cost).unwrap(); + // internal node count == leaf_count - 1 + // total nodes == (leaf_count * 2) - 1 samples_time_native.push((((leaf_count * 2) - 1) as f64, duration)); samples_cost_native.push((((leaf_count * 2) - 1) as f64, cost as f64)); @@ -74,6 +85,10 @@ fn time_per_cons_for_balanced_tree( let result_2 = node_to_bytes(a, red.1).expect("should work"); assert_eq!(result_1, result_2); let duration = start.elapsed().as_nanos() as f64; + // subtract out the duration of hashing all the 32byte chunks + let duration = duration - ((2 * leaf_count - 1) as f64 * bytes32_clvm_time); + // subtract out the cost of hashing all the 32byte chunks + let cost = cost as f64 - ((2 * leaf_count - 1) as f64 * bytes32_clvm_cost); writeln!(output_clvm_time, "{}\t{}", leaf_count, duration).unwrap(); writeln!(output_clvm_cost, "{}\t{}", leaf_count, cost).unwrap(); @@ -282,6 +297,10 @@ fn main() { let (leaf_nat_t, leaf_clvm_t, leaf_nat_c, leaf_clvm_c) = time_per_cons_for_balanced_tree( &mut a, shaprog, + atom_nat_c, + atom_clvm_c, + atom_nat_t, + atom_clvm_t, tree_native_time, tree_clvm_time, tree_native_cost, From f0f54e45840e9c60f22b4fd2e7f916a15e4ace36 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 16:13:11 +0000 Subject: [PATCH 097/110] accurately track leaf amount and add missing parenthesis --- tools/src/bin/sha256tree-benching.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 9d318346c..68a96e16b 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -46,9 +46,10 @@ fn time_per_cons_for_balanced_tree( let quote = a.one(); // leaf atom (not pre-processed) + // small enough to still fit into one bytes32 chunk let mut tree = a.new_atom(&[0xff, 0xff]).unwrap(); - for i in 0..10 { + for i in 1..10 { // double the number of leaves each iteration tree = a.new_pair(tree, tree).unwrap(); @@ -336,7 +337,7 @@ fn main() { atom_nat_t * cost_scale ); println!( - "CLVM (time_per_bytes * cost_ratio : {:.4}", + "CLVM (time_per_bytes * cost_ratio) : {:.4}", atom_clvm_t * cost_scale ); println!("Native cost per bytes32 : {:.4}", atom_nat_c); @@ -362,7 +363,7 @@ fn main() { leaf_nat_t * cost_scale ); println!( - "CLVM (time_per_node * cost_ratio : {:.4}", + "CLVM (time_per_node * cost_ratio) : {:.4}", leaf_clvm_t * cost_scale ); From 8f68bcb03226b233a3d155d2007a490cbd037584 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 17:09:18 +0000 Subject: [PATCH 098/110] add md file explaining thought process --- docs/sha256tree.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 docs/sha256tree.md diff --git a/docs/sha256tree.md b/docs/sha256tree.md new file mode 100644 index 000000000..a9382a7c1 --- /dev/null +++ b/docs/sha256tree.md @@ -0,0 +1,63 @@ +# Information on the new (sha256tree) operator + +Adding `sha256tree` as a native operator made sense as it is one of the most common functions, used in nearly every shipped ChiaLisp puzzle. +Furthermore it has an innate inefficiency in it's in-language implementation. +Every internal hash is allocated as an atom in clvm_rs allocator. +In addition to this, a native operator also opens the door to future optimisations via caching. + +## Costing Goals + +The matter of how to assign Cost to the new operator was the subject of intense thought and debate. +It should be costed in proportion to: +- the time to Cost vs other operators, and especially the `sha256` operator +- the time to Cost ratio of the in-language implementation +- the its inputs + +The lattermost being a unique problem with regards to shatree as it is the first operator that parses trees, so utmost care must be taken when assigning cost. + +One final consideration while costing is that we required it to tally the cost during the runtime, rather than afterwards - which would be the most efficient calculation. This is because we want the oepration to fail immediately if `max_cost` is exceeded. + +## Costing Methodology + +The `BASE_COST` was set to equal the base cost of `sha256`. + +The `COST_PER_BYTES32` was designed as the sha256 operation operates on 32byte chunks. We set the Cost to be on parity with the Cost of `sha256` although sha256 costs `per byte` and `per arg`. +We can ignore `per arg` as `sha256tree` only takes a single argument, and we benchmarked the `cost-per-bytes32` so that it matches `sha256`'s `cost-per-byte`. + +Finally the `COST_PER_NODE` was the trickiest to pin down as it is the most unique to this operator. +The trick to costing was to compare with the "in-language" implementation and deduct the costs of the known hash operations using our previously costed `COST_PER_BYTES32`. + +The calculations for this can be seen in the file `sha256tree-benching.rs`. + + +``` +Costs based on an increasing atom per bytes32 chunks: +Native time per bytes32 (ns): 95.1425 +CLVM time per bytes32 (ns): 94.9895 +Native implementation takes 100.1611% of the time. +Native (time_per_bytes32 * cost_ratio): 611.3642 +CLVM (time_per_bytes * cost_ratio) : 610.3807 +Native cost per bytes32 : 64.0000 +CLVM cost per bytes32 : 64.0000 +100.1611% of the CLVM cost is: : 64.1031 + +Costs based on growing a balanced binary tree: +Native time per node (ns): 203.8718 +CLVM time per node (ns): 517.8038 +Native implementation takes 39.3724% of the time. +Native (time_per_node * cost_ratio): 1310.0339 +CLVM (time_per_node * cost_ratio) : 3327.2886 +Native cost per node : 564.0000 +CLVM cost per node : 1463.0000 +39.3724% of the CLVM cost is: : 576.0185 + +Costs based on growing a list: +Native time per node (ns): 115.0891 +CLVM time per node (ns): 397.1927 +Native implementation takes 28.9756% of the time. +Native (time_per_node * cost_ratio): 739.5365 +CLVM (time_per_node * cost_ratio): 2552.2694 +Native cost per node : 500.0000 +CLVM cost per node : 1399.0000 +28.9756% of the CLVM cost is: : 405.3693 +``` \ No newline at end of file From 6f75406b854b1471ed394275719157d961874fea Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Thu, 18 Dec 2025 17:52:23 +0000 Subject: [PATCH 099/110] add windows timings to MD file --- docs/sha256tree.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/sha256tree.md b/docs/sha256tree.md index a9382a7c1..e17dca56d 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -29,7 +29,7 @@ The trick to costing was to compare with the "in-language" implementation and de The calculations for this can be seen in the file `sha256tree-benching.rs`. - +`MacOS M1` ``` Costs based on an increasing atom per bytes32 chunks: Native time per bytes32 (ns): 95.1425 @@ -60,4 +60,37 @@ CLVM (time_per_node * cost_ratio): 2552.2694 Native cost per node : 500.0000 CLVM cost per node : 1399.0000 28.9756% of the CLVM cost is: : 405.3693 +``` + +`Windows` +``` +Costs based on an increasing atom per bytes32 chunks: +Native time per bytes32 (ns): 10.4049 +CLVM time per bytes32 (ns): 10.2604 +Native implementation takes 101.4084% of the time. +Native (time_per_bytes32 * cost_ratio): 66.8597 +CLVM (time_per_bytes * cost_ratio) : 65.9311 +Native cost per bytes32 : 64.0000 +CLVM cost per bytes32 : 64.0000 +101.4084% of the CLVM cost is: : 64.9014 + +Costs based on growing a balanced binary tree: +Native time per node (ns): 62.6417 +CLVM time per node (ns): 1350.7339 +Native implementation takes 4.6376% of the time. +Native (time_per_node * cost_ratio): 402.5212 +CLVM (time_per_node * cost_ratio) : 8679.5078 +Native cost per node : 564.0000 +CLVM cost per node : 1463.0000 +4.6376% of the CLVM cost is: : 67.8481 + +Costs based on growing a list: +Native time per node (ns): 61.1526 +CLVM time per node (ns): 608.9923 +Native implementation takes 10.0416% of the time. +Native (time_per_node * cost_ratio): 392.9526 +CLVM (time_per_node * cost_ratio): 3913.2455 +Native cost per node : 500.0000 +CLVM cost per node : 1399.0000 +10.0416% of the CLVM cost is: : 140.4821 ``` \ No newline at end of file From baa0e4353a206b29a318ee84140bdf657949edb9 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 08:43:01 +0000 Subject: [PATCH 100/110] extra formatting --- docs/sha256tree.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sha256tree.md b/docs/sha256tree.md index e17dca56d..ca64b900b 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -24,11 +24,15 @@ The `BASE_COST` was set to equal the base cost of `sha256`. The `COST_PER_BYTES32` was designed as the sha256 operation operates on 32byte chunks. We set the Cost to be on parity with the Cost of `sha256` although sha256 costs `per byte` and `per arg`. We can ignore `per arg` as `sha256tree` only takes a single argument, and we benchmarked the `cost-per-bytes32` so that it matches `sha256`'s `cost-per-byte`. +The calculations for this can be seen in the file `benchmark-clvm-cost.rs`. + Finally the `COST_PER_NODE` was the trickiest to pin down as it is the most unique to this operator. The trick to costing was to compare with the "in-language" implementation and deduct the costs of the known hash operations using our previously costed `COST_PER_BYTES32`. The calculations for this can be seen in the file `sha256tree-benching.rs`. +## Costing Results + `MacOS M1` ``` Costs based on an increasing atom per bytes32 chunks: From a7a6377069a9a011c51a5978841b8fa82d4f48a3 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 11:50:21 +0000 Subject: [PATCH 101/110] fix sentence fragment --- docs/sha256tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sha256tree.md b/docs/sha256tree.md index ca64b900b..6e5d4d5ce 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -11,7 +11,7 @@ The matter of how to assign Cost to the new operator was the subject of intense It should be costed in proportion to: - the time to Cost vs other operators, and especially the `sha256` operator - the time to Cost ratio of the in-language implementation -- the its inputs +- the size of its inputs The lattermost being a unique problem with regards to shatree as it is the first operator that parses trees, so utmost care must be taken when assigning cost. From ca4f14920410f676ee6f0651c20d6beef1434b90 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 11:56:03 +0000 Subject: [PATCH 102/110] remove cost_factor as it was confusing and no longer useful --- docs/sha256tree.md | 12 ------------ tools/src/bin/sha256tree-benching.rs | 28 ---------------------------- 2 files changed, 40 deletions(-) diff --git a/docs/sha256tree.md b/docs/sha256tree.md index 6e5d4d5ce..d8ec4f7a2 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -39,8 +39,6 @@ Costs based on an increasing atom per bytes32 chunks: Native time per bytes32 (ns): 95.1425 CLVM time per bytes32 (ns): 94.9895 Native implementation takes 100.1611% of the time. -Native (time_per_bytes32 * cost_ratio): 611.3642 -CLVM (time_per_bytes * cost_ratio) : 610.3807 Native cost per bytes32 : 64.0000 CLVM cost per bytes32 : 64.0000 100.1611% of the CLVM cost is: : 64.1031 @@ -49,8 +47,6 @@ Costs based on growing a balanced binary tree: Native time per node (ns): 203.8718 CLVM time per node (ns): 517.8038 Native implementation takes 39.3724% of the time. -Native (time_per_node * cost_ratio): 1310.0339 -CLVM (time_per_node * cost_ratio) : 3327.2886 Native cost per node : 564.0000 CLVM cost per node : 1463.0000 39.3724% of the CLVM cost is: : 576.0185 @@ -59,8 +55,6 @@ Costs based on growing a list: Native time per node (ns): 115.0891 CLVM time per node (ns): 397.1927 Native implementation takes 28.9756% of the time. -Native (time_per_node * cost_ratio): 739.5365 -CLVM (time_per_node * cost_ratio): 2552.2694 Native cost per node : 500.0000 CLVM cost per node : 1399.0000 28.9756% of the CLVM cost is: : 405.3693 @@ -72,8 +66,6 @@ Costs based on an increasing atom per bytes32 chunks: Native time per bytes32 (ns): 10.4049 CLVM time per bytes32 (ns): 10.2604 Native implementation takes 101.4084% of the time. -Native (time_per_bytes32 * cost_ratio): 66.8597 -CLVM (time_per_bytes * cost_ratio) : 65.9311 Native cost per bytes32 : 64.0000 CLVM cost per bytes32 : 64.0000 101.4084% of the CLVM cost is: : 64.9014 @@ -82,8 +74,6 @@ Costs based on growing a balanced binary tree: Native time per node (ns): 62.6417 CLVM time per node (ns): 1350.7339 Native implementation takes 4.6376% of the time. -Native (time_per_node * cost_ratio): 402.5212 -CLVM (time_per_node * cost_ratio) : 8679.5078 Native cost per node : 564.0000 CLVM cost per node : 1463.0000 4.6376% of the CLVM cost is: : 67.8481 @@ -92,8 +82,6 @@ Costs based on growing a list: Native time per node (ns): 61.1526 CLVM time per node (ns): 608.9923 Native implementation takes 10.0416% of the time. -Native (time_per_node * cost_ratio): 392.9526 -CLVM (time_per_node * cost_ratio): 3913.2455 Native cost per node : 500.0000 CLVM cost per node : 1399.0000 10.0416% of the CLVM cost is: : 140.4821 diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 68a96e16b..0e025de92 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -321,9 +321,6 @@ fn main() { cons_clvm_cost, ); - // taken from benchmark-clvm-cost.rs - let cost_scale = ((101094.0 / 39000.0) + (1343980.0 / 131000.0)) / 2.0; - println!("Costs based on an increasing atom per bytes32 chunks: "); println!("Native time per bytes32 (ns): {:.4}", atom_nat_t); println!("CLVM time per bytes32 (ns): {:.4}", atom_clvm_t); @@ -332,14 +329,6 @@ fn main() { "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); - println!( - "Native (time_per_bytes32 * cost_ratio): {:.4}", - atom_nat_t * cost_scale - ); - println!( - "CLVM (time_per_bytes * cost_ratio) : {:.4}", - atom_clvm_t * cost_scale - ); println!("Native cost per bytes32 : {:.4}", atom_nat_c); println!("CLVM cost per bytes32 : {:.4}", atom_clvm_c); println!( @@ -358,15 +347,6 @@ fn main() { "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); - println!( - "Native (time_per_node * cost_ratio): {:.4}", - leaf_nat_t * cost_scale - ); - println!( - "CLVM (time_per_node * cost_ratio) : {:.4}", - leaf_clvm_t * cost_scale - ); - println!("Native cost per node : {:.4}", leaf_nat_c); println!("CLVM cost per node : {:.4}", leaf_clvm_c); println!( @@ -386,14 +366,6 @@ fn main() { "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); - println!( - "Native (time_per_node * cost_ratio): {:.4}", - cons_nat_t * cost_scale - ); - println!( - "CLVM (time_per_node * cost_ratio): {:.4}", - cons_clvm_t * cost_scale - ); println!("Native cost per node : {:.4}", cons_nat_c); println!("CLVM cost per node : {:.4}", cons_clvm_c); println!( From 47f5f805e2b9906b7173e97e597b3701539cf6b3 Mon Sep 17 00:00:00 2001 From: matt-o-how <48453825+matt-o-how@users.noreply.github.com> Date: Fri, 19 Dec 2025 12:21:54 +0000 Subject: [PATCH 103/110] Update docs/sha256tree.md Co-authored-by: Arvid Norberg --- docs/sha256tree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sha256tree.md b/docs/sha256tree.md index d8ec4f7a2..dff6aa058 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -10,7 +10,7 @@ In addition to this, a native operator also opens the door to future optimisatio The matter of how to assign Cost to the new operator was the subject of intense thought and debate. It should be costed in proportion to: - the time to Cost vs other operators, and especially the `sha256` operator -- the time to Cost ratio of the in-language implementation +- the time to Cost ratio of the ChiaLisp implementation - the size of its inputs The lattermost being a unique problem with regards to shatree as it is the first operator that parses trees, so utmost care must be taken when assigning cost. From 9f94b89aca21d2f0d8b467a463289c82535b42d5 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 16:02:55 +0000 Subject: [PATCH 104/110] add explicit warning to cost --- tools/src/bin/sha256tree-benching.rs | 31 ++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index 0e025de92..b98683d5f 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -110,7 +110,7 @@ fn time_per_cons_for_balanced_tree( // this function is for comparing the cost per 32byte chunk of hashing between the native and clvm implementation #[allow(clippy::type_complexity)] -fn time_per_byte_for_atom( +fn time_per_bytes32_for_atom( a: &mut Allocator, sha_prog: NodePtr, mut output_native_time: impl Write, @@ -286,7 +286,7 @@ fn main() { let tree_native_cost = BufWriter::new(File::create("tree_native_cost.dat").unwrap()); let tree_clvm_cost = BufWriter::new(File::create("tree_clvm_cost.dat").unwrap()); - let (atom_nat_t, atom_clvm_t, atom_nat_c, atom_clvm_c) = time_per_byte_for_atom( + let (atom_nat_t, atom_clvm_t, atom_nat_c, atom_clvm_c) = time_per_bytes32_for_atom( &mut a, shaprog, atom_native_time, @@ -329,16 +329,23 @@ fn main() { "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); - println!("Native cost per bytes32 : {:.4}", atom_nat_c); println!("CLVM cost per bytes32 : {:.4}", atom_clvm_c); println!( "{:.4}% of the CLVM cost is: : {:.4}", native_vs_clvm_ratio * 100.0, atom_clvm_c * native_vs_clvm_ratio ); + // this output is what the current set cost values produce + // for setting the cost this should only be used to compare with calculated theoretical costs + // it is NOT a target or a proof of correctness + println!( + "With current cost values the native cost per bytes32 is: {:.4}", + atom_nat_c + ); println!(); // this is the costing of the balanced binary tree + // we are calculating the cost per node by dividing time by node count println!("Costs based on growing a balanced binary tree: "); println!("Native time per node (ns): {:.4}", leaf_nat_t); println!("CLVM time per node (ns): {:.4}", leaf_clvm_t); @@ -347,16 +354,22 @@ fn main() { "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); - println!("Native cost per node : {:.4}", leaf_nat_c); println!("CLVM cost per node : {:.4}", leaf_clvm_c); println!( "{:.4}% of the CLVM cost is: : {:.4}", native_vs_clvm_ratio * 100.0, leaf_clvm_c * native_vs_clvm_ratio ); + // this output is what the current set cost values produce + // for setting the cost this should only be used to compare with calculated theoretical costs + // it is NOT a target or a proof of correctness + println!( + "With current cost values the native cost per node is: {:.4}", + leaf_nat_c + ); println!(); - // this is described as estimated as we're adding a cons and a nil atom each time + // this is estimated as we're adding a cons and a nil atom each time // and then we're subtracting the costs to calculate what a single node might theoretically cost println!("Costs based on growing a list: "); println!("Native time per node (ns): {:.4}", cons_nat_t); @@ -366,13 +379,19 @@ fn main() { "Native implementation takes {:.4}% of the time.", native_vs_clvm_ratio * 100.0 ); - println!("Native cost per node : {:.4}", cons_nat_c); println!("CLVM cost per node : {:.4}", cons_clvm_c); println!( "{:.4}% of the CLVM cost is: : {:.4}", native_vs_clvm_ratio * 100.0, cons_clvm_c * native_vs_clvm_ratio ); + // this output is what the current set cost values produce + // for setting the cost this should only be used to compare with calculated theoretical costs + // it is NOT a target or a proof of correctness + println!( + "With current cost values the native cost per node is: {:.4}", + cons_nat_c + ); // gnuplot script let mut gp = File::create("plots.gnuplot").unwrap(); From 7d52bd35621e02e90cb4d489ddf6f5464d3d9f92 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 16:06:04 +0000 Subject: [PATCH 105/110] format i loop to track accurately --- tools/src/bin/sha256tree-benching.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index b98683d5f..fdea4a1fc 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -214,7 +214,7 @@ fn time_per_cons_for_list( list = a.new_pair(atom, list).unwrap(); } - for i in 0..1000 { + for i in 500..1500 { list = a.new_pair(atom, list).unwrap(); let q = a.new_pair(quote, list).unwrap(); let call = a.new_pair(q, a.nil()).unwrap(); @@ -230,8 +230,8 @@ fn time_per_cons_for_list( let duration = start.elapsed().as_nanos() as f64; // a new list entry is 2 nodes (a cons and a nil) and a 3 chunk hash operation and a 1 chunk hash operation // this equation lets us figure out a theoretical cost just for a node - let duration = (duration - (500.0 + i as f64) * (4.0 * bytes32_native_time)) / 2.0; - let cost = (cost as f64 - (500.0 + i as f64) * (4.0 * bytes32_native_cost)) / 2.0; + let duration = (duration - i as f64 * (4.0 * bytes32_native_time)) / 2.0; + let cost = (cost as f64 - i as f64 * (4.0 * bytes32_native_cost)) / 2.0; writeln!(output_native_time, "{}\t{}", i, duration).unwrap(); writeln!(output_native_cost, "{}\t{}", i, cost).unwrap(); samples_time_native.push((i as f64, duration)); From c95cfe812bd3380286170f37961b438566089fcd Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 16:48:42 +0000 Subject: [PATCH 106/110] update benching to generate graph for balanced tree and add all graphs to md file --- docs/graphs/atom_bench.png | Bin 0 -> 31045 bytes docs/graphs/atom_cost.png | Bin 0 -> 22801 bytes docs/graphs/cons_bench.png | Bin 0 -> 76166 bytes docs/graphs/cons_cost.png | Bin 0 -> 27676 bytes docs/graphs/tree_bench.png | Bin 0 -> 32231 bytes docs/graphs/tree_cost.png | Bin 0 -> 38178 bytes docs/sha256tree.md | 21 ++++++++++++++++++++- tools/src/bin/sha256tree-benching.rs | 16 ++++++++++++++++ 8 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 docs/graphs/atom_bench.png create mode 100644 docs/graphs/atom_cost.png create mode 100644 docs/graphs/cons_bench.png create mode 100644 docs/graphs/cons_cost.png create mode 100644 docs/graphs/tree_bench.png create mode 100644 docs/graphs/tree_cost.png diff --git a/docs/graphs/atom_bench.png b/docs/graphs/atom_bench.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1da896c7525759372e23386f09a12bb5f15cda GIT binary patch literal 31045 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 Nt*W+4p|aJL{{XO=L_Yuk literal 0 HcmV?d00001 diff --git a/docs/graphs/atom_cost.png b/docs/graphs/atom_cost.png new file mode 100644 index 0000000000000000000000000000000000000000..c7a1de26af32de4ac6c42f0565478e164a2aac5f GIT binary patch literal 22801 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 I+;!~#0mwR^W&i*H literal 0 HcmV?d00001 diff --git a/docs/graphs/cons_bench.png b/docs/graphs/cons_bench.png new file mode 100644 index 0000000000000000000000000000000000000000..d007de48baaa501fe01459886311149b6e6f8843 GIT binary patch literal 76166 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 literal 0 HcmV?d00001 diff --git a/docs/sha256tree.md b/docs/sha256tree.md index dff6aa058..d894c23cf 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -85,4 +85,23 @@ Native implementation takes 10.0416% of the time. Native cost per node : 500.0000 CLVM cost per node : 1399.0000 10.0416% of the CLVM cost is: : 140.4821 -``` \ No newline at end of file +``` + + +## Costing Graphs + +Below are the generated benchmarking graphs (PNG) from running `sha256tree-benching.rs` on Macbook M1. + +![Atom - Time per Byte](graphs/atom_bench.png) + +![Atom - Cost per Byte](graphs/atom_cost.png) + +![List - Time per Cons Cell](graphs/cons_bench.png) + +![List - Cost per Cons Cell](graphs/cons_cost.png) + +![Tree - Time per Node](graphs/tree_bench.png) + +![Tree - Cost per Node](graphs/tree_cost.png) + + diff --git a/tools/src/bin/sha256tree-benching.rs b/tools/src/bin/sha256tree-benching.rs index fdea4a1fc..f4bda74a0 100644 --- a/tools/src/bin/sha256tree-benching.rs +++ b/tools/src/bin/sha256tree-benching.rs @@ -431,6 +431,22 @@ set ylabel "Cost" plot \ "cons_native_cost.dat" using 1:2 with lines title "native", \ "cons_clvm_cost.dat" using 1:2 with lines title "clvm" + +set output "tree_bench.png" +set title "Time per Tree Node (Tree SHA-tree)" +set xlabel "Iteration" +set ylabel "Time (ns)" +plot \ + "tree_native.dat" using 1:2 with lines title "native", \ + "tree_clvm.dat" using 1:2 with lines title "clvm" + +set output "tree_cost.png" +set title "Cost per Tree Node (Tree SHA-tree)" +set xlabel "Iteration" +set ylabel "Cost" +plot \ + "tree_native_cost.dat" using 1:2 with lines title "native", \ + "tree_clvm_cost.dat" using 1:2 with lines title "clvm" "# ) .unwrap(); From e71d4edb2dee3f6308f2feb94c377baee9af903e Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 16:56:24 +0000 Subject: [PATCH 107/110] remove all changes except added comments from benchmark-clvm-cost --- tools/src/bin/benchmark-clvm-cost.rs | 180 +++++---------------------- 1 file changed, 31 insertions(+), 149 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index e91024818..5079364c7 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -1,6 +1,6 @@ use clap::Parser; use clvmr::allocator::{Allocator, NodePtr}; -use clvmr::chia_dialect::{ChiaDialect, ENABLE_SHA256_TREE}; +use clvmr::chia_dialect::ChiaDialect; use clvmr::run_program::run_program; use linreg::linear_regression_of; use std::fs::{create_dir_all, File}; @@ -115,38 +115,25 @@ fn substitute(args: Placeholder, s: NodePtr) -> OpArgs { } } -fn time_invocation( - a: &mut Allocator, - op: u32, - arg: OpArgs, - flags: u32, - cost_samples: &mut Vec<(f64, f64)>, -) -> f64 { +fn time_invocation(a: &mut Allocator, op: u32, arg: OpArgs, flags: u32) -> f64 { let call = build_call(a, op, arg, 1, None); //println!("{:x?}", &Node::new(a, call)); - let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); + let dialect = ChiaDialect::new(0); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11000000000); - let time = if (flags & EXPONENTIAL_COST) != 0 { + if (flags & ALLOW_FAILURE) == 0 { + r.unwrap(); + } + if (flags & EXPONENTIAL_COST) != 0 { (start.elapsed().as_nanos() as f64).sqrt() } else { start.elapsed().as_nanos() as f64 - }; - if (flags & ALLOW_FAILURE) == 0 { - let cost = r.unwrap().0; - cost_samples.push((time, cost as f64)); } - time } // returns the time per byte // measures run-time of many calls -fn time_per_byte( - a: &mut Allocator, - op: &Operator, - output: &mut dyn Write, - cost_samples: &mut Vec<(f64, f64)>, -) -> f64 { +fn time_per_byte(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { let checkpoint = a.checkpoint(); let mut samples = Vec::<(f64, f64)>::new(); let mut atom = vec![0; 10000000]; @@ -165,7 +152,7 @@ fn time_per_byte( let arg = substitute(op.arg, quote(a, subst)); let sample = ( i as f64 * scale as f64, - time_invocation(a, op.opcode, arg, op.flags, cost_samples), + time_invocation(a, op.opcode, arg, op.flags), ); writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); @@ -180,12 +167,7 @@ fn time_per_byte( // returns the time per argument // measures the run-time of many calls with varying number of arguments, to // establish how much time each additional argument contributes -fn time_per_arg( - a: &mut Allocator, - op: &Operator, - output: &mut dyn Write, - cost_samples: &mut Vec<(f64, f64)>, -) -> f64 { +fn time_per_arg(a: &mut Allocator, op: &Operator, output: &mut dyn Write) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); @@ -204,14 +186,11 @@ fn time_per_arg( let call = build_call(a, op.opcode, arg, i, op.extra); let start = Instant::now(); let r = run_program(a, &dialect, call, a.nil(), 11000000000); - let duration = start.elapsed(); - let sample = (i as f64, duration.as_nanos() as f64); if (op.flags & ALLOW_FAILURE) == 0 { - let cost = r.unwrap().0; - if sample.1 > 0.0 { - cost_samples.push((sample.1, cost as f64)); - }; + r.unwrap(); } + let duration = start.elapsed(); + let sample = (i as f64, duration.as_nanos() as f64); writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); @@ -224,13 +203,13 @@ fn time_per_arg( } // measure run-time of many *nested* calls, to establish how much longer it -// takes, approximately, for each additional nesting. +// takes, approximately, for each additional nesting. The per_arg_time is +// subtracted to get the base cost fn base_call_time( a: &mut Allocator, op: &Operator, per_arg_time: f64, output: &mut dyn Write, - cost_samples: &mut Vec<(f64, f64)>, ) -> f64 { let mut samples = Vec::<(f64, f64)>::new(); let dialect = ChiaDialect::new(0); @@ -250,17 +229,13 @@ fn base_call_time( a.restore_checkpoint(&checkpoint); let call = build_nested_call(a, op.opcode, arg, i, op.extra); let start = Instant::now(); - let r = run_program(a, &dialect, call, a.nil(), 11_000_000_000); - + let r = run_program(a, &dialect, call, a.nil(), 11000000000); + if (op.flags & ALLOW_FAILURE) == 0 { + r.unwrap(); + } let duration = start.elapsed(); let duration = (duration.as_nanos() as f64) - (per_arg_time * i as f64); let sample = (i as f64, duration); - if (op.flags & ALLOW_FAILURE) == 0 { - let cost = r.unwrap().0; - if duration > 0.0 { - cost_samples.push((duration, cost as f64)) - }; - } writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); samples.push(sample); } @@ -270,12 +245,7 @@ fn base_call_time( slope } -fn base_call_time_no_nest( - a: &mut Allocator, - op: &Operator, - per_arg_time: f64, - cost_samples: &mut Vec<(f64, f64)>, -) -> f64 { +fn base_call_time_no_nest(a: &mut Allocator, op: &Operator, per_arg_time: f64) -> f64 { let mut total_time: f64 = 0.0; let mut num_samples = 0; @@ -291,54 +261,13 @@ fn base_call_time_no_nest( for _i in 0..300 { a.restore_checkpoint(&checkpoint); - total_time += time_invocation( - a, - op.opcode, - arg, - op.flags & !EXPONENTIAL_COST, - cost_samples, - ); + total_time += time_invocation(a, op.opcode, arg, op.flags & !EXPONENTIAL_COST); num_samples += 1; } (total_time - per_arg_time * num_samples as f64) / num_samples as f64 } -// this adds 32 bytes at a time compared to per_byte which adds 5 at a time -// at the moment this function is specialised for sha256tree but may be relevant to other hash operations in the future -fn time_per_32bytes_for_atom( - a: &mut Allocator, - op: &Operator, - output: &mut dyn Write, -) -> (f64, f64) { - let mut samples = Vec::<(f64, f64)>::new(); - let dialect = ChiaDialect::new(ENABLE_SHA256_TREE); // enable shatree - - let op_code = a.new_small_number(op.opcode).unwrap(); - let quote = a.one(); - let mut atom = [0xff].repeat(10_000); - let checkpoint = a.checkpoint(); - - for i in 0..10000 { - // make the atom longer as a function of i - atom.append(&mut [(i % 89 + 10) as u8].repeat(32)); // just to mix it up - let atom_node = a.new_atom(&atom).unwrap(); - let args = a.new_pair(quote, atom_node).unwrap(); - let call = a.new_pair(args, a.nil()).unwrap(); - let call = a.new_pair(op_code, call).unwrap(); - let start = Instant::now(); - run_program(a, &dialect, call, a.nil(), 11000000000).unwrap(); - let duration = start.elapsed(); - let sample = (i as f64, duration.as_nanos() as f64); - writeln!(output, "{}\t{}", sample.0, sample.1).expect("failed to write"); - samples.push(sample); - - a.restore_checkpoint(&checkpoint); - } - - linear_regression_of(&samples).expect("linreg failed") -} - // cost one argument with increasing amount of bytes const PER_BYTE_COST: u32 = 1; // cost multiple arguments with increasing amount of arguments @@ -410,10 +339,6 @@ fn print_plot(gnuplot: &mut dyn Write, a: &f64, b: &f64, op: &str, name: &str) { pub fn main() { let options = Args::parse(); - let mut cost_factor_samples = Vec::<(f64, f64)>::new(); - let mut base_cost_factor_samples = Vec::<(f64, f64)>::new(); - let mut args_cost_factor_samples = Vec::<(f64, f64)>::new(); - let mut a = Allocator::new(); let g1 = a.new_atom(&hex::decode("97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb").unwrap()).unwrap(); @@ -461,7 +386,7 @@ pub fn main() { .unwrap(); let number = quote(&mut a, number); - let ops: [Operator; 20] = [ + let ops: [Operator; 19] = [ Operator { opcode: 60, name: "modpow (modulus cost)", @@ -586,7 +511,7 @@ pub fn main() { name: "sha256", arg: Placeholder::SingleArg(Some(g1)), extra: None, - flags: NESTING_BASE_COST | PER_ARG_COST | ALT_PER_BYTE_COST | LARGE_BUFFERS, + flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, }, Operator { opcode: 62, @@ -595,13 +520,6 @@ pub fn main() { extra: None, flags: NESTING_BASE_COST | PER_ARG_COST | PER_BYTE_COST | LARGE_BUFFERS, }, - Operator { - opcode: 63, - name: "sha256tree", - arg: Placeholder::SingleArg(None), - extra: None, - flags: NESTING_BASE_COST | ALT_PER_BYTE_COST | LARGE_BUFFERS, - }, ]; // this "magic" scaling depends on the computer you run the tests on. @@ -621,24 +539,16 @@ pub fn main() { println!("opcode: {} ({})", op.name, op.opcode); let time_per_byte = if (op.flags & PER_BYTE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-byte.log"); - let time_per_byte = time_per_byte(&mut a, op, &mut *output, &mut cost_factor_samples); + let time_per_byte = time_per_byte(&mut a, op, &mut *output); println!(" time: per-byte: {time_per_byte:.2}ns"); println!(" cost: per-byte: {:.0}", time_per_byte * cost_scale); time_per_byte - } else if (op.flags & ALT_PER_BYTE_COST) != 0 { - let mut output = maybe_open(options.plot, op.name, "per-byte.log"); - let (slope, _intercept) = time_per_32bytes_for_atom(&mut a, op, &mut output); - let cost = slope * cost_scale; - println!(" time: per-32byte: {slope:.2}ns"); - println!(" cost: per-32byte: {:.0}", cost); - slope } else { 0.0 }; let time_per_arg = if (op.flags & PER_ARG_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "per-arg.log"); - let time_per_arg = - time_per_arg(&mut a, op, &mut *output, &mut args_cost_factor_samples); + let time_per_arg = time_per_arg(&mut a, op, &mut *output); println!(" time: per-arg: {time_per_arg:.2}ns"); println!(" cost: per-arg: {:.0}", time_per_arg * arg_cost_scale); time_per_arg @@ -648,29 +558,16 @@ pub fn main() { let base_call_time = if (op.flags & NESTING_BASE_COST) != 0 { let mut output = maybe_open(options.plot, op.name, "base.log"); write_gnuplot_header(&mut *gnuplot, op, "base", "num nested calls"); - let base_call_time = base_call_time( - &mut a, - op, - time_per_arg, - &mut *output, - &mut base_cost_factor_samples, - ); + let base_call_time = base_call_time(&mut a, op, time_per_arg, &mut *output); println!(" time: base: {base_call_time:.2}ns"); - println!( - " cost: base: {:.0}", - (base_call_time * base_cost_scale).max(100.0) - ); + println!(" cost: base: {:.0}", base_call_time * base_cost_scale); print_plot(&mut *gnuplot, &base_call_time, &0.0, op.name, "base"); base_call_time } else { - let base_call_time = - base_call_time_no_nest(&mut a, op, time_per_arg, &mut base_cost_factor_samples); + let base_call_time = base_call_time_no_nest(&mut a, op, time_per_arg); println!(" time: base: {base_call_time:.2}ns"); - println!( - " cost: base: {:.0}", - (base_call_time * base_cost_scale).max(100.0) - ); + println!(" cost: base: {:.0}", base_call_time * base_cost_scale); base_call_time }; @@ -689,7 +586,7 @@ pub fn main() { op.name, "per-arg", ); - } else if (op.flags & PER_BYTE_COST) != 0 || (op.flags & ALT_PER_BYTE_COST) != 0 { + } else if (op.flags & PER_BYTE_COST) != 0 { write_gnuplot_header(&mut *gnuplot, op, "per-byte", "num bytes"); print_plot( &mut *gnuplot, @@ -700,22 +597,7 @@ pub fn main() { ); } } - let new_cost_factor: (f64, f64) = linear_regression_of(&cost_factor_samples).unwrap(); - println!( - "Newly calculated cost_factor for this computer is: {}", - new_cost_factor.0 - ); - let new_base_cost_factor: (f64, f64) = linear_regression_of(&base_cost_factor_samples).unwrap(); - println!( - "Newly calculated base_cost_factor for this computer is: {}", - new_base_cost_factor.0 - ); - let new_args_cost_factor: (f64, f64) = linear_regression_of(&args_cost_factor_samples).unwrap(); - println!( - "Newly calculated args_cost_factor for this computer is: {}", - new_args_cost_factor.0 - ); if options.plot { println!("To generate plots, run:\n (cd measurements; gnuplot gen-graphs.gnuplot)"); } -} +} \ No newline at end of file From 3b40d55fc7ac8856f6a0082aa9de00c870a3b41f Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 16:58:08 +0000 Subject: [PATCH 108/110] remove unused flag --- tools/src/bin/benchmark-clvm-cost.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index 5079364c7..c87037e85 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -280,8 +280,6 @@ const EXPONENTIAL_COST: u32 = 8; const LARGE_BUFFERS: u32 = 16; // permit the operator to fail in tests const ALLOW_FAILURE: u32 = 32; -// measure the timing per 32 bytes block of atom argument -const ALT_PER_BYTE_COST: u32 = 128; struct Operator { opcode: u32, From 2e13d524e3e5058786661ae8438fe44b0c60059b Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 17:11:40 +0000 Subject: [PATCH 109/110] fmt --- tools/src/bin/benchmark-clvm-cost.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/src/bin/benchmark-clvm-cost.rs b/tools/src/bin/benchmark-clvm-cost.rs index c87037e85..ad7b592cb 100644 --- a/tools/src/bin/benchmark-clvm-cost.rs +++ b/tools/src/bin/benchmark-clvm-cost.rs @@ -598,4 +598,4 @@ pub fn main() { if options.plot { println!("To generate plots, run:\n (cd measurements; gnuplot gen-graphs.gnuplot)"); } -} \ No newline at end of file +} From a7088f73892d24c2cc60c6003947d4bf4fb85f12 Mon Sep 17 00:00:00 2001 From: Matthew Howard Date: Fri, 19 Dec 2025 17:20:16 +0000 Subject: [PATCH 110/110] prettier md --- docs/sha256tree.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sha256tree.md b/docs/sha256tree.md index d894c23cf..02cfda62d 100644 --- a/docs/sha256tree.md +++ b/docs/sha256tree.md @@ -1,6 +1,6 @@ # Information on the new (sha256tree) operator -Adding `sha256tree` as a native operator made sense as it is one of the most common functions, used in nearly every shipped ChiaLisp puzzle. +Adding `sha256tree` as a native operator made sense as it is one of the most common functions, used in nearly every shipped ChiaLisp puzzle. Furthermore it has an innate inefficiency in it's in-language implementation. Every internal hash is allocated as an atom in clvm_rs allocator. In addition to this, a native operator also opens the door to future optimisations via caching. @@ -9,8 +9,9 @@ In addition to this, a native operator also opens the door to future optimisatio The matter of how to assign Cost to the new operator was the subject of intense thought and debate. It should be costed in proportion to: + - the time to Cost vs other operators, and especially the `sha256` operator -- the time to Cost ratio of the ChiaLisp implementation +- the time to Cost ratio of the ChiaLisp implementation - the size of its inputs The lattermost being a unique problem with regards to shatree as it is the first operator that parses trees, so utmost care must be taken when assigning cost. @@ -21,7 +22,7 @@ One final consideration while costing is that we required it to tally the cost d The `BASE_COST` was set to equal the base cost of `sha256`. -The `COST_PER_BYTES32` was designed as the sha256 operation operates on 32byte chunks. We set the Cost to be on parity with the Cost of `sha256` although sha256 costs `per byte` and `per arg`. +The `COST_PER_BYTES32` was designed as the sha256 operation operates on 32byte chunks. We set the Cost to be on parity with the Cost of `sha256` although sha256 costs `per byte` and `per arg`. We can ignore `per arg` as `sha256tree` only takes a single argument, and we benchmarked the `cost-per-bytes32` so that it matches `sha256`'s `cost-per-byte`. The calculations for this can be seen in the file `benchmark-clvm-cost.rs`. @@ -34,8 +35,9 @@ The calculations for this can be seen in the file `sha256tree-benching.rs`. ## Costing Results `MacOS M1` + ``` -Costs based on an increasing atom per bytes32 chunks: +Costs based on an increasing atom per bytes32 chunks: Native time per bytes32 (ns): 95.1425 CLVM time per bytes32 (ns): 94.9895 Native implementation takes 100.1611% of the time. @@ -43,7 +45,7 @@ Native cost per bytes32 : 64.0000 CLVM cost per bytes32 : 64.0000 100.1611% of the CLVM cost is: : 64.1031 -Costs based on growing a balanced binary tree: +Costs based on growing a balanced binary tree: Native time per node (ns): 203.8718 CLVM time per node (ns): 517.8038 Native implementation takes 39.3724% of the time. @@ -51,7 +53,7 @@ Native cost per node : 564.0000 CLVM cost per node : 1463.0000 39.3724% of the CLVM cost is: : 576.0185 -Costs based on growing a list: +Costs based on growing a list: Native time per node (ns): 115.0891 CLVM time per node (ns): 397.1927 Native implementation takes 28.9756% of the time. @@ -61,6 +63,7 @@ CLVM cost per node : 1399.0000 ``` `Windows` + ``` Costs based on an increasing atom per bytes32 chunks: Native time per bytes32 (ns): 10.4049 @@ -87,7 +90,6 @@ CLVM cost per node : 1399.0000 10.0416% of the CLVM cost is: : 140.4821 ``` - ## Costing Graphs Below are the generated benchmarking graphs (PNG) from running `sha256tree-benching.rs` on Macbook M1. @@ -103,5 +105,3 @@ Below are the generated benchmarking graphs (PNG) from running `sha256tree-bench ![Tree - Time per Node](graphs/tree_bench.png) ![Tree - Cost per Node](graphs/tree_cost.png) - -