confium_test_harness/
env.rs1use std::cell::Cell;
20use std::sync::Mutex;
21
22#[derive(Debug, Clone)]
33pub struct DeterministicRng {
34 state: u64,
35}
36
37impl DeterministicRng {
38 pub fn from_seed(seed: u64) -> Self {
42 DeterministicRng { state: seed }
43 }
44
45 pub fn next_u64(&mut self) -> u64 {
47 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
51 let mut z = self.state;
52 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
53 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
54 z ^ (z >> 31)
55 }
56
57 pub fn fill(&mut self, out: &mut [u8]) {
59 let mut i = 0;
60 while i + 8 <= out.len() {
61 let w = self.next_u64().to_le_bytes();
62 out[i..i + 8].copy_from_slice(&w);
63 i += 8;
64 }
65 if i < out.len() {
66 let w = self.next_u64().to_le_bytes();
67 let remaining = out.len() - i;
68 out[i..].copy_from_slice(&w[..remaining]);
69 }
70 }
71}
72
73#[derive(Debug, Default)]
81pub struct DeterministicClock {
82 nanos: Cell<u128>,
83}
84
85impl DeterministicClock {
86 pub fn new() -> Self {
87 DeterministicClock {
88 nanos: Cell::new(0),
89 }
90 }
91
92 pub fn now_nanos(&self) -> u128 {
94 self.nanos.get()
95 }
96
97 pub fn advance(&self, nanos: u64) {
99 self.nanos
100 .set(self.nanos.get().saturating_add(nanos as u128));
101 }
102}
103
104#[derive(Debug, Default)]
112pub struct MemoryCounter {
113 inner: Mutex<MemoryState>,
114}
115
116#[derive(Debug, Default, Clone, Copy)]
117struct MemoryState {
118 current: u64,
119 peak: u64,
120}
121
122impl MemoryCounter {
123 pub fn new() -> Self {
124 MemoryCounter::default()
125 }
126
127 pub fn track(&self, bytes: u64) {
130 let mut state = self.inner.lock().expect("memory counter poisoned");
131 state.current = state.current.saturating_add(bytes);
132 if state.current > state.peak {
133 state.peak = state.current;
134 }
135 }
136
137 pub fn release(&self, bytes: u64) {
139 let mut state = self.inner.lock().expect("memory counter poisoned");
140 state.current = state.current.saturating_sub(bytes);
141 }
142
143 pub fn peak_bytes(&self) -> u64 {
145 self.inner.lock().expect("memory counter poisoned").peak
146 }
147
148 pub fn current_bytes(&self) -> u64 {
150 self.inner.lock().expect("memory counter poisoned").current
151 }
152}
153
154#[derive(Debug)]
161pub struct DeterministicEnv {
162 seed: u64,
163 clock: DeterministicClock,
164 memory: MemoryCounter,
165}
166
167impl DeterministicEnv {
168 pub fn from_seed(seed: u64) -> Self {
170 DeterministicEnv {
171 seed,
172 clock: DeterministicClock::new(),
173 memory: MemoryCounter::new(),
174 }
175 }
176
177 pub fn seed(&self) -> u64 {
178 self.seed
179 }
180
181 pub fn rng_for(&self, party_idx: usize) -> DeterministicRng {
185 let mixed = self
186 .seed
187 .wrapping_mul(0x9E37_79B9_7F4A_7C15)
188 .wrapping_add(party_idx as u64);
189 DeterministicRng::from_seed(mixed)
190 }
191
192 pub fn fork(&self, party_idx: usize) -> ForkedEnv<'_> {
195 ForkedEnv {
196 rng: self.rng_for(party_idx),
197 clock: &self.clock,
198 memory: &self.memory,
199 }
200 }
201
202 pub fn clock(&self) -> &DeterministicClock {
203 &self.clock
204 }
205
206 pub fn memory(&self) -> &MemoryCounter {
207 &self.memory
208 }
209}
210
211#[derive(Debug, Clone)]
214pub struct ForkedEnv<'a> {
215 pub rng: DeterministicRng,
216 pub clock: &'a DeterministicClock,
217 pub memory: &'a MemoryCounter,
218}
219
220impl<'a> ForkedEnv<'a> {
221 pub fn rng_mut(&mut self) -> &mut DeterministicRng {
223 &mut self.rng
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn seeded_rng_is_reproducible() {
233 let mut a = DeterministicRng::from_seed(0xDEAD_BEEF_CAFE_BABE);
234 let mut b = DeterministicRng::from_seed(0xDEAD_BEEF_CAFE_BABE);
235 for _ in 0..32 {
236 assert_eq!(a.next_u64(), b.next_u64());
237 }
238 }
239
240 #[test]
241 fn different_seeds_diverge() {
242 let mut a = DeterministicRng::from_seed(1);
243 let mut b = DeterministicRng::from_seed(2);
244 let differs = (0..16).any(|_| a.next_u64() != b.next_u64());
246 assert!(differs, "two different seeds produced identical streams");
247 }
248
249 #[test]
250 fn zero_seed_still_produces_output() {
251 let mut rng = DeterministicRng::from_seed(0);
252 let first = rng.next_u64();
255 let second = rng.next_u64();
256 assert_ne!(first, second);
257 }
258
259 #[test]
260 fn fill_is_byte_reproducible() {
261 let mut a = [0u8; 17];
262 let mut b = [0u8; 17];
263 DeterministicRng::from_seed(42).fill(&mut a);
264 DeterministicRng::from_seed(42).fill(&mut b);
265 assert_eq!(a, b);
266 assert!(a.iter().any(|&x| x != 0));
267 }
268
269 #[test]
270 fn clock_starts_at_zero_and_advances() {
271 let clock = DeterministicClock::new();
272 assert_eq!(clock.now_nanos(), 0);
273 clock.advance(1_000_000_000);
274 assert_eq!(clock.now_nanos(), 1_000_000_000);
275 clock.advance(500_000_000);
276 assert_eq!(clock.now_nanos(), 1_500_000_000);
277 }
278
279 #[test]
280 fn clock_does_not_overflow() {
281 let clock = DeterministicClock::new();
282 clock.advance(u64::MAX);
283 clock.advance(u64::MAX);
284 assert!(clock.now_nanos() > u64::MAX as u128);
285 }
286
287 #[test]
288 fn memory_counter_tracks_peak() {
289 let mem = MemoryCounter::new();
290 mem.track(100);
291 mem.track(200);
292 assert_eq!(mem.current_bytes(), 300);
293 assert_eq!(mem.peak_bytes(), 300);
294 mem.release(150);
295 assert_eq!(mem.current_bytes(), 150);
296 assert_eq!(mem.peak_bytes(), 300, "peak is sticky");
297 mem.track(400);
298 assert_eq!(mem.peak_bytes(), 550);
299 }
300
301 #[test]
302 fn memory_counter_release_underflows_to_zero() {
303 let mem = MemoryCounter::new();
304 mem.release(1_000_000);
305 assert_eq!(mem.current_bytes(), 0);
306 assert_eq!(mem.peak_bytes(), 0);
307 }
308
309 #[test]
310 fn env_forks_diverge_by_party_index() {
311 let env = DeterministicEnv::from_seed(99);
312 let mut fork_a = env.fork(0);
313 let mut fork_b = env.fork(1);
314 let x = fork_a.rng_mut().next_u64();
315 let y = fork_b.rng_mut().next_u64();
316 assert_ne!(
317 x, y,
318 "two parties must not share an RNG stream in the same session"
319 );
320 }
321
322 #[test]
323 fn env_fork_shares_clock_and_memory() {
324 let env = DeterministicEnv::from_seed(7);
325 let fork_a = env.fork(0);
326 let fork_b = env.fork(1);
327 fork_a.memory.track(128);
328 assert_eq!(fork_b.memory.peak_bytes(), 128);
329 fork_a.clock.advance(5);
330 assert_eq!(fork_b.clock.now_nanos(), 5);
331 }
332}