//! Deterministic primitives shared across the whole framework: stable ids, //! a stable content hash, and a deterministic RNG. Everything here is //! reproducible from a seed so that replay is bit-exact. use std::fmt; /// Number of independent world domains. The spec mandates >= 8. pub const NUM_DOMAINS: usize = 8; /// Observed value lanes per domain. pub const LANES: usize = 4; /// Hidden (unobserved) value lanes per domain. These create the /// hidden/observed state divergence the spec requires. pub const HIDDEN_LANES: usize = 2; macro_rules! id_type { ($name:ident, $inner:ty) => { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct $name(pub $inner); impl fmt::Debug for $name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}({})", stringify!($name), self.0) } } impl fmt::Display for $name { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } }; } id_type!(WorldId, u64); id_type!(ProgramId, u64); id_type!(ContractId, u64); /// Identifies one of the [`NUM_DOMAINS`] domains. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct DomainId(pub u8); impl fmt::Debug for DomainId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "DomainId({})", self.0) } } /// A stable 64-bit content hash. Used for canonical comparison, replay /// hashes, and behavior fingerprints. Implemented with FNV-1a so the value /// is identical across machines and runs. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Hash(pub u64); impl fmt::Debug for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Hash({:016x})", self.0) } } impl fmt::Display for Hash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:016x}", self.0) } } const FNV_OFFSET: u64 = 0xcbf29ce484222325; const FNV_PRIME: u64 = 0x100000001b3; /// Streaming stable hasher (FNV-1a, 64 bit). #[derive(Clone, Copy)] pub struct Hasher { state: u64, } impl Default for Hasher { fn default() -> Self { Hasher { state: FNV_OFFSET } } } impl Hasher { pub fn new() -> Self { Self::default() } #[inline] pub fn write_u8(&mut self, b: u8) { self.state ^= b as u64; self.state = self.state.wrapping_mul(FNV_PRIME); } #[inline] pub fn write_u64(&mut self, v: u64) { for i in 0..8 { self.write_u8(((v >> (i * 8)) & 0xff) as u8); } } #[inline] pub fn write_i64(&mut self, v: i64) { self.write_u64(v as u64); } #[inline] pub fn write_usize(&mut self, v: usize) { self.write_u64(v as u64); } #[inline] pub fn write_bytes(&mut self, bytes: &[u8]) { for &b in bytes { self.write_u8(b); } } /// Mix in a label so structurally different streams that happen to share /// numbers do not collide. #[inline] pub fn write_tag(&mut self, tag: &str) { self.write_bytes(tag.as_bytes()); self.write_u8(0xff); } #[inline] pub fn finish(&self) -> Hash { Hash(self.state) } } /// Hash a slice of i64 with a tag. pub fn hash_i64_slice(tag: &str, vals: &[i64]) -> Hash { let mut h = Hasher::new(); h.write_tag(tag); h.write_usize(vals.len()); for &v in vals { h.write_i64(v); } h.finish() } /// Combine several hashes into one (order sensitive). pub fn combine_hashes(tag: &str, hashes: &[Hash]) -> Hash { let mut h = Hasher::new(); h.write_tag(tag); for hh in hashes { h.write_u64(hh.0); } h.finish() } /// Deterministic SplitMix64 RNG. Fully reproducible from a seed. #[derive(Clone, Copy, Debug)] pub struct Rng { state: u64, } impl Rng { pub fn new(seed: u64) -> Self { // Avoid the trivial all-zero state. Rng { state: seed ^ 0x9e3779b97f4a7c15, } } /// Derive a sub-stream from a seed and a label, so independent concerns /// never accidentally share a stream. pub fn derive(seed: u64, tag: &str) -> Self { let mut h = Hasher::new(); h.write_tag(tag); h.write_u64(seed); Rng::new(h.finish().0) } #[inline] pub fn next_u64(&mut self) -> u64 { self.state = self.state.wrapping_add(0x9e3779b97f4a7c15); let mut z = self.state; z = (z ^ (z >> 30)).wrapping_mul(0xbf58476d1ce4e5b9); z = (z ^ (z >> 27)).wrapping_mul(0x94d049bb133111eb); z ^ (z >> 31) } #[inline] pub fn next_i64(&mut self) -> i64 { self.next_u64() as i64 } /// Uniform-ish integer in `[0, n)`. #[inline] pub fn below(&mut self, n: usize) -> usize { if n == 0 { return 0; } (self.next_u64() % (n as u64)) as usize } /// Integer in `[lo, hi]` inclusive. #[inline] pub fn range_i64(&mut self, lo: i64, hi: i64) -> i64 { if hi <= lo { return lo; } let span = (hi - lo) as u64 + 1; lo + (self.next_u64() % span) as i64 } #[inline] pub fn chance(&mut self, p: f64) -> bool { (self.next_u64() as f64 / u64::MAX as f64) < p } #[inline] pub fn next_bool(&mut self) -> bool { self.next_u64() & 1 == 1 } }