|
| 1 | +//! Host-only keccak-hash counters for measuring the cost of VERIFYING a proof |
| 2 | +//! (a proxy for the recursion guest's dominant work: keccak hashing). |
| 3 | +//! |
| 4 | +//! Behind the `hash-metrics` cargo feature: a normal build keeps |
| 5 | +//! `PlatformKeccak256 = sha3::Keccak256` and every counter call compiles to |
| 6 | +//! nothing, so the prover is provably unchanged. With the feature on (host only), |
| 7 | +//! the host `PlatformKeccak256` wrapper bumps [`count_total`] on EVERY keccak-256 |
| 8 | +//! finalize — Merkle trees, the Fiat-Shamir transcript, the program-id/ELF fold. |
| 9 | +//! The Merkle backends split their share ([`count_merkle`] / [`count_merkle_node`]) |
| 10 | +//! and grinding tags its own ([`count_grinding`]), so a caller can report |
| 11 | +//! `total`, `merkle` (nodes/leaves), `grinding`, and everything else. |
| 12 | +//! |
| 13 | +//! No enable/disable toggle and nothing in the verifier: counting is always on |
| 14 | +//! under the feature, and a measuring caller just [`reset`]s before the verify |
| 15 | +//! and reads [`snapshot`] after. Grinding is separated by counter, not excluded |
| 16 | +//! at a call site, so there is no cross-thread race under a parallel verify. |
| 17 | +
|
| 18 | +#[cfg(all(not(target_arch = "riscv64"), feature = "hash-metrics"))] |
| 19 | +mod imp { |
| 20 | + use core::sync::atomic::{AtomicU64, Ordering}; |
| 21 | + |
| 22 | + static TOTAL: AtomicU64 = AtomicU64::new(0); |
| 23 | + static MERKLE: AtomicU64 = AtomicU64::new(0); |
| 24 | + static MERKLE_NODES: AtomicU64 = AtomicU64::new(0); |
| 25 | + static GRINDING: AtomicU64 = AtomicU64::new(0); |
| 26 | + |
| 27 | + /// Every keccak-256 finalize, from any site (host `PlatformKeccak256`). |
| 28 | + #[inline(always)] |
| 29 | + pub fn count_total() { |
| 30 | + TOTAL.fetch_add(1, Ordering::Relaxed); |
| 31 | + } |
| 32 | + |
| 33 | + /// A Merkle finalize (leaf or node), counted ONLY when the backend digest is |
| 34 | + /// the platform keccak wrapper — the one whose `finalize` also bumps |
| 35 | + /// [`count_total`]. This keeps `merkle` a strict subset of `total` for ANY |
| 36 | + /// `D` (a non-keccak backend, as in the crypto tests, does not go through the |
| 37 | + /// counted wrapper, so counting it here would let `merkle` exceed `total`). |
| 38 | + #[inline(always)] |
| 39 | + pub fn count_merkle<D: 'static>() { |
| 40 | + if core::any::TypeId::of::<D>() |
| 41 | + == core::any::TypeId::of::<crate::hash::platform_keccak::PlatformKeccak256>() |
| 42 | + { |
| 43 | + MERKLE.fetch_add(1, Ordering::Relaxed); |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /// A Merkle parent (auth-path) compression. Subset of [`count_merkle`]; |
| 48 | + /// same keccak-only guard. |
| 49 | + #[inline(always)] |
| 50 | + pub fn count_merkle_node<D: 'static>() { |
| 51 | + if core::any::TypeId::of::<D>() |
| 52 | + == core::any::TypeId::of::<crate::hash::platform_keccak::PlatformKeccak256>() |
| 53 | + { |
| 54 | + MERKLE_NODES.fetch_add(1, Ordering::Relaxed); |
| 55 | + } |
| 56 | + } |
| 57 | + |
| 58 | + /// A grinding (proof-of-work) finalize. Subset of [`count_total`]; a caller |
| 59 | + /// reports `total - grinding` to exclude the PoW check. |
| 60 | + #[inline(always)] |
| 61 | + pub fn count_grinding() { |
| 62 | + GRINDING.fetch_add(1, Ordering::Relaxed); |
| 63 | + } |
| 64 | + |
| 65 | + /// Zero all counters. |
| 66 | + pub fn reset() { |
| 67 | + TOTAL.store(0, Ordering::Relaxed); |
| 68 | + MERKLE.store(0, Ordering::Relaxed); |
| 69 | + MERKLE_NODES.store(0, Ordering::Relaxed); |
| 70 | + GRINDING.store(0, Ordering::Relaxed); |
| 71 | + } |
| 72 | + |
| 73 | + /// `(total, merkle, merkle_nodes, grinding)`. leaves = merkle − nodes; |
| 74 | + /// transcript+other = total − merkle − grinding; excl. grinding = total − grinding. |
| 75 | + pub fn snapshot() -> (u64, u64, u64, u64) { |
| 76 | + ( |
| 77 | + TOTAL.load(Ordering::Relaxed), |
| 78 | + MERKLE.load(Ordering::Relaxed), |
| 79 | + MERKLE_NODES.load(Ordering::Relaxed), |
| 80 | + GRINDING.load(Ordering::Relaxed), |
| 81 | + ) |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +// Feature off, or the riscv64 guest: every entry compiles to nothing. |
| 86 | +#[cfg(any(target_arch = "riscv64", not(feature = "hash-metrics")))] |
| 87 | +mod imp { |
| 88 | + #[inline(always)] |
| 89 | + pub fn count_total() {} |
| 90 | + #[inline(always)] |
| 91 | + pub fn count_merkle<D: 'static>() {} |
| 92 | + #[inline(always)] |
| 93 | + pub fn count_merkle_node<D: 'static>() {} |
| 94 | + #[inline(always)] |
| 95 | + pub fn count_grinding() {} |
| 96 | + pub fn reset() {} |
| 97 | + pub fn snapshot() -> (u64, u64, u64, u64) { |
| 98 | + (0, 0, 0, 0) |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +pub use imp::{count_grinding, count_merkle, count_merkle_node, count_total, reset, snapshot}; |
0 commit comments