add initial counter setup (#130)

* add initial counter setup

We can extend it to also count the POD operations or other kind of logic
that we might want to count.


Co-authored-by: Ahmad Afuni <root@ahmadafuni.com>

---------

Co-authored-by: Ahmad Afuni <root@ahmadafuni.com>
This commit is contained in:
arnaucube 2025-03-12 14:29:55 +01:00 committed by GitHub
parent a77b522128
commit 12ec220de6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 98 additions and 16 deletions

73
src/backends/counter.rs Normal file
View file

@ -0,0 +1,73 @@
//! This module allows to count operations involved in tests, isolating by test.
//!
//! Example of usage:
//! ```rust
//! #[test]
//! fn test_example() {
//! // [...]
//! println!("{}", counter::counter_get());
//! }
//! ```
//!
use std::cell::RefCell;
use std::fmt;
use std::thread_local;
thread_local! {
static COUNTER: RefCell<Counter> = RefCell::new(Counter::new());
}
#[derive(Clone, Debug)]
pub(crate) struct Counter {
hash: usize,
tree_insert: usize,
tree_proof_gen: usize,
}
impl Counter {
const fn new() -> Self {
Counter {
hash: 0,
tree_insert: 0,
tree_proof_gen: 0,
}
}
}
impl fmt::Display for Counter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let counter = counter_get();
write!(f, "Counter:\n")?;
write!(f, " hashes: {},\n", counter.hash)?;
write!(f, " tree_inserts: {},\n", counter.tree_insert)?;
write!(f, " tree_proof_gens: {}\n", counter.tree_proof_gen)?;
Ok(())
}
}
pub(crate) fn count_hash() {
#[cfg(test)]
COUNTER.with(|c| c.borrow_mut().hash += 1);
}
pub(crate) fn count_tree_insert() {
#[cfg(test)]
COUNTER.with(|c| c.borrow_mut().tree_insert += 1);
}
pub(crate) fn count_tree_proof_gen() {
#[cfg(test)]
COUNTER.with(|c| c.borrow_mut().tree_proof_gen += 1);
}
pub(crate) fn counter_get() -> Counter {
COUNTER.with(|c| c.borrow().clone())
}
pub(crate) fn counter_reset() {
COUNTER.with(|c| {
c.borrow_mut().hash = 0;
c.borrow_mut().tree_insert = 0;
c.borrow_mut().tree_proof_gen = 0;
});
}

View file

@ -1,2 +1,4 @@
pub(crate) mod counter;
#[cfg(feature = "backend_plonky2")] #[cfg(feature = "backend_plonky2")]
pub mod plonky2; pub mod plonky2;

View file

@ -15,6 +15,8 @@ use std::fmt;
use crate::middleware::{Params, ToFields}; use crate::middleware::{Params, ToFields};
use crate::backends::counter;
/// F is the native field we use everywhere. Currently it's Goldilocks from plonky2 /// F is the native field we use everywhere. Currently it's Goldilocks from plonky2
pub type F = GoldilocksField; pub type F = GoldilocksField;
/// C is the Plonky2 config used in POD2 to work with Plonky2 recursion. /// C is the Plonky2 config used in POD2 to work with Plonky2 recursion.
@ -119,10 +121,14 @@ impl fmt::Display for Value {
pub struct Hash(pub [F; HASH_SIZE]); pub struct Hash(pub [F; HASH_SIZE]);
pub fn hash_value(input: &Value) -> Hash { pub fn hash_value(input: &Value) -> Hash {
Hash(PoseidonHash::hash_no_pad(&input.0).elements) hash_fields(&input.0)
} }
pub fn hash_fields(input: &[F]) -> Hash { pub fn hash_fields(input: &[F]) -> Hash {
Hash(PoseidonHash::hash_no_pad(input).elements) // Note: the counter counts when this method is called, but different input
// sizes will have different costs in-circuit.
counter::count_hash();
Hash(PoseidonHash::hash_no_pad(&input).elements)
} }
impl From<Value> for Hash { impl From<Value> for Hash {
@ -203,7 +209,7 @@ pub fn hash_str(s: &str) -> Hash {
F::from_canonical_u64(v) F::from_canonical_u64(v)
}) })
.collect(); .collect();
Hash(PoseidonHash::hash_no_pad(&input).elements) hash_fields(&input)
} }
#[cfg(test)] #[cfg(test)]

View file

@ -2,13 +2,12 @@
//! https://0xparc.github.io/pod2/merkletree.html . //! https://0xparc.github.io/pod2/merkletree.html .
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use plonky2::field::goldilocks_field::GoldilocksField; use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::hash::poseidon::PoseidonHash;
use plonky2::plonk::config::Hasher;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt; use std::fmt;
use std::iter::IntoIterator; use std::iter::IntoIterator;
use crate::backends::plonky2::basetypes::{Hash, Value, F, NULL}; use crate::backends::counter;
use crate::backends::plonky2::basetypes::{hash_fields, Hash, Value, F, NULL};
/// Implements the MerkleTree specified at /// Implements the MerkleTree specified at
/// https://0xparc.github.io/pod2/merkletree.html /// https://0xparc.github.io/pod2/merkletree.html
@ -71,6 +70,8 @@ impl MerkleTree {
/// the tree. It returns the `value` of the leaf at the given `key`, and the /// the tree. It returns the `value` of the leaf at the given `key`, and the
/// `MerkleProof`. /// `MerkleProof`.
pub fn prove(&self, key: &Value) -> Result<(Value, MerkleProof)> { pub fn prove(&self, key: &Value) -> Result<(Value, MerkleProof)> {
counter::count_tree_proof_gen();
let path = keypath(self.max_depth, *key)?; let path = keypath(self.max_depth, *key)?;
let mut siblings: Vec<Hash> = Vec::new(); let mut siblings: Vec<Hash> = Vec::new();
@ -96,6 +97,8 @@ impl MerkleTree {
/// the key-value pair in the leaf reached as a result of /// the key-value pair in the leaf reached as a result of
/// resolving `key` as well as a `MerkleProof`. /// resolving `key` as well as a `MerkleProof`.
pub fn prove_nonexistence(&self, key: &Value) -> Result<MerkleProof> { pub fn prove_nonexistence(&self, key: &Value) -> Result<MerkleProof> {
counter::count_tree_proof_gen();
let path = keypath(self.max_depth, *key)?; let path = keypath(self.max_depth, *key)?;
let mut siblings: Vec<Hash> = Vec::new(); let mut siblings: Vec<Hash> = Vec::new();
@ -175,14 +178,7 @@ impl MerkleTree {
/// mitigate fake proofs. /// mitigate fake proofs.
pub fn kv_hash(key: &Value, value: Option<Value>) -> Hash { pub fn kv_hash(key: &Value, value: Option<Value>) -> Hash {
value value
.map(|v| { .map(|v| hash_fields(&[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat()))
Hash(
PoseidonHash::hash_no_pad(
&[key.0.to_vec(), v.0.to_vec(), vec![GoldilocksField(1)]].concat(),
)
.elements,
)
})
.unwrap_or(Hash([GoldilocksField(0); 4])) .unwrap_or(Hash([GoldilocksField(0); 4]))
} }
@ -253,7 +249,7 @@ impl MerkleProof {
} else { } else {
[h.0, sibling.0].concat() [h.0, sibling.0].concat()
}; };
h = Hash(PoseidonHash::hash_no_pad(&input).elements); h = hash_fields(&input);
} }
Ok(h) Ok(h)
} }
@ -365,6 +361,8 @@ impl Node {
// adds the leaf at the tree from the current node (self), without computing any hash // adds the leaf at the tree from the current node (self), without computing any hash
fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> { fn add_leaf(&mut self, lvl: usize, max_depth: usize, leaf: Leaf) -> Result<()> {
counter::count_tree_insert();
if lvl >= max_depth { if lvl >= max_depth {
return Err(anyhow!("max depth reached")); return Err(anyhow!("max depth reached"));
} }
@ -480,7 +478,7 @@ impl Intermediate {
let l_hash = self.left.compute_hash(); let l_hash = self.left.compute_hash();
let r_hash = self.right.compute_hash(); let r_hash = self.right.compute_hash();
let input: Vec<F> = [l_hash.0, r_hash.0].concat(); let input: Vec<F> = [l_hash.0, r_hash.0].concat();
let h = Hash(PoseidonHash::hash_no_pad(&input).elements); let h = hash_fields(&input);
self.hash = Some(h); self.hash = Some(h);
h h
} }
@ -599,8 +597,11 @@ pub mod tests {
let (v, proof) = tree.prove(&Value::from(13))?; let (v, proof) = tree.prove(&Value::from(13))?;
assert_eq!(v, Value::from(1013)); assert_eq!(v, Value::from(1013));
println!("{}", proof); println!("{}", proof);
println!("after proof generation, {}", counter::counter_get());
counter::counter_reset();
MerkleTree::verify(32, tree.root(), &proof, &key, &value)?; MerkleTree::verify(32, tree.root(), &proof, &key, &value)?;
println!("after verify, {}", counter::counter_get());
// Exclusion checks // Exclusion checks
let key = Value::from(12); let key = Value::from(12);