1use std::collections::HashMap;
11
12use elara_core::{
13 Event, EventType, MutationOp, NodeId, StateAtom, StateId, StateTime, StateType, VersionVector,
14};
15use elara_state::ReconciliationEngine;
16use elara_time::TimeEngine;
17use rand::rngs::StdRng;
18use rand::{Rng, SeedableRng};
19
20#[derive(Clone, Debug)]
22pub struct FuzzerConfig {
23 pub node_count: usize,
25 pub state_count: usize,
27 pub event_count: usize,
29 pub concurrent_prob: f64,
31 pub reorder_prob: f64,
33 pub partition_prob: f64,
35 pub seed: u64,
37}
38
39impl Default for FuzzerConfig {
40 fn default() -> Self {
41 FuzzerConfig {
42 node_count: 5,
43 state_count: 10,
44 event_count: 1000,
45 concurrent_prob: 0.3,
46 reorder_prob: 0.2,
47 partition_prob: 0.05,
48 seed: 42,
49 }
50 }
51}
52
53impl FuzzerConfig {
54 pub fn light() -> Self {
56 FuzzerConfig {
57 node_count: 3,
58 state_count: 5,
59 event_count: 100,
60 concurrent_prob: 0.2,
61 reorder_prob: 0.1,
62 partition_prob: 0.0,
63 seed: 42,
64 }
65 }
66
67 pub fn heavy() -> Self {
69 FuzzerConfig {
70 node_count: 10,
71 state_count: 50,
72 event_count: 10000,
73 concurrent_prob: 0.5,
74 reorder_prob: 0.4,
75 partition_prob: 0.1,
76 seed: 42,
77 }
78 }
79
80 pub fn adversarial() -> Self {
82 FuzzerConfig {
83 node_count: 20,
84 state_count: 100,
85 event_count: 5000,
86 concurrent_prob: 0.7,
87 reorder_prob: 0.6,
88 partition_prob: 0.2,
89 seed: 42,
90 }
91 }
92}
93
94#[derive(Clone, Debug)]
96pub struct FuzzEvent {
97 pub event: Event,
98 pub delivery_order: u64,
99 pub partition_id: u32,
100}
101
102pub struct FuzzNode {
104 pub node_id: NodeId,
105 pub engine: ReconciliationEngine,
106 pub time_engine: TimeEngine,
107 pub partition_id: u32,
108 pub events_processed: u64,
109}
110
111impl FuzzNode {
112 pub fn new(node_id: NodeId) -> Self {
113 FuzzNode {
114 node_id,
115 engine: ReconciliationEngine::new(),
116 time_engine: TimeEngine::new(),
117 partition_id: 0,
118 events_processed: 0,
119 }
120 }
121
122 pub fn process(&mut self, event: Event) {
124 let _ = self.engine.process_events(vec![event], &self.time_engine);
125 self.events_processed += 1;
126 }
127
128 pub fn get_state(&self, id: StateId) -> Option<&StateAtom> {
130 self.engine.field().get(id)
131 }
132}
133
134pub struct StateFuzzer {
136 config: FuzzerConfig,
137 nodes: HashMap<NodeId, FuzzNode>,
138 state_ids: Vec<StateId>,
139 rng: StdRng,
140 event_seq: u64,
141 current_time: StateTime,
142}
143
144impl StateFuzzer {
145 pub fn new(config: FuzzerConfig) -> Self {
147 let rng = StdRng::seed_from_u64(config.seed);
148 let mut nodes = HashMap::new();
149
150 for i in 0..config.node_count {
152 let node_id = NodeId::new(i as u64);
153 nodes.insert(node_id, FuzzNode::new(node_id));
154 }
155
156 let state_ids: Vec<StateId> = (0..config.state_count)
158 .map(|i| StateId::new(i as u64))
159 .collect();
160
161 StateFuzzer {
162 config,
163 nodes,
164 state_ids,
165 rng,
166 event_seq: 0,
167 current_time: StateTime::ZERO,
168 }
169 }
170
171 pub fn initialize_states(&mut self) {
173 let node_ids: Vec<NodeId> = self.nodes.keys().copied().collect();
174
175 for state_id in &self.state_ids {
176 let owner_idx = self.rng.gen_range(0..node_ids.len());
178 let owner = node_ids[owner_idx];
179
180 for node in self.nodes.values_mut() {
182 let atom = StateAtom::new(*state_id, StateType::Core, owner);
183 node.engine.field_mut().insert(atom);
184 }
185 }
186 }
187
188 fn generate_event(&mut self) -> FuzzEvent {
190 let node_ids: Vec<NodeId> = self.nodes.keys().copied().collect();
191 let source_idx = self.rng.gen_range(0..node_ids.len());
192 let source = node_ids[source_idx];
193
194 let state_idx = self.rng.gen_range(0..self.state_ids.len());
195 let target_state = self.state_ids[state_idx];
196
197 self.event_seq += 1;
198 self.current_time = StateTime::from_millis(self.current_time.as_millis() + 10);
199
200 let mutation = self.generate_mutation();
201
202 let event = Event::new(
203 source,
204 self.event_seq,
205 EventType::StateUpdate,
206 target_state,
207 mutation,
208 );
209
210 let delivery_order = if self.rng.gen::<f64>() < self.config.reorder_prob {
211 self.rng.gen_range(0..self.event_seq)
213 } else {
214 self.event_seq
215 };
216
217 let partition_id = if self.rng.gen::<f64>() < self.config.partition_prob {
218 self.rng.gen_range(0..3)
219 } else {
220 0
221 };
222
223 FuzzEvent {
224 event,
225 delivery_order,
226 partition_id,
227 }
228 }
229
230 fn generate_mutation(&mut self) -> MutationOp {
232 let mutation_type = self.rng.gen_range(0..4);
233 match mutation_type {
234 0 => {
235 let len = self.rng.gen_range(1..100);
236 let data: Vec<u8> = (0..len).map(|_| self.rng.gen()).collect();
237 MutationOp::Set(data)
238 }
239 1 => {
240 let len = self.rng.gen_range(1..50);
241 let data: Vec<u8> = (0..len).map(|_| self.rng.gen()).collect();
242 MutationOp::Append(data)
243 }
244 2 => {
245 let delta = self.rng.gen_range(-100..100);
246 MutationOp::Increment(delta)
247 }
248 _ => MutationOp::Delete,
249 }
250 }
251
252 pub fn run(&mut self) -> FuzzResult {
254 self.initialize_states();
255
256 let mut events: Vec<FuzzEvent> = Vec::new();
257
258 for _ in 0..self.config.event_count {
260 events.push(self.generate_event());
261 }
262
263 events.sort_by_key(|e| e.delivery_order);
265
266 for fuzz_event in events {
268 for node in self.nodes.values_mut() {
269 if fuzz_event.partition_id != 0 && node.partition_id != fuzz_event.partition_id {
271 continue;
272 }
273
274 node.process(fuzz_event.event.clone());
275 }
276 }
277
278 self.check_invariants()
280 }
281
282 fn check_invariants(&self) -> FuzzResult {
284 let mut result = FuzzResult::new();
285
286 result.convergence = self.check_convergence();
288
289 result.authority_violations = self.check_authority();
291
292 result.causality_violations = self.check_causality();
294
295 result
296 }
297
298 fn check_convergence(&self) -> ConvergenceResult {
300 let node_ids: Vec<NodeId> = self.nodes.keys().copied().collect();
301 if node_ids.len() < 2 {
302 return ConvergenceResult::Converged;
303 }
304
305 let reference = &self.nodes[&node_ids[0]];
306 let mut divergent_states = Vec::new();
307
308 for state_id in &self.state_ids {
309 let ref_atom = reference.get_state(*state_id);
310
311 for &node_id in &node_ids[1..] {
312 let node = &self.nodes[&node_id];
313 let atom = node.get_state(*state_id);
314
315 match (ref_atom, atom) {
316 (Some(r), Some(a)) => {
317 if r.value != a.value {
318 divergent_states.push(*state_id);
319 }
320 }
321 (None, Some(_)) | (Some(_), None) => {
322 divergent_states.push(*state_id);
323 }
324 (None, None) => {}
325 }
326 }
327 }
328
329 if divergent_states.is_empty() {
330 ConvergenceResult::Converged
331 } else {
332 ConvergenceResult::Diverged(divergent_states)
333 }
334 }
335
336 fn check_authority(&self) -> u32 {
338 0
341 }
342
343 fn check_causality(&self) -> u32 {
345 0
347 }
348}
349
350#[derive(Debug)]
352pub enum ConvergenceResult {
353 Converged,
354 Diverged(Vec<StateId>),
355}
356
357impl ConvergenceResult {
358 pub fn is_converged(&self) -> bool {
359 matches!(self, ConvergenceResult::Converged)
360 }
361}
362
363#[derive(Debug)]
365pub struct FuzzResult {
366 pub convergence: ConvergenceResult,
367 pub authority_violations: u32,
368 pub causality_violations: u32,
369}
370
371impl FuzzResult {
372 pub fn new() -> Self {
373 FuzzResult {
374 convergence: ConvergenceResult::Converged,
375 authority_violations: 0,
376 causality_violations: 0,
377 }
378 }
379
380 pub fn is_valid(&self) -> bool {
381 self.convergence.is_converged()
382 && self.authority_violations == 0
383 && self.causality_violations == 0
384 }
385}
386
387impl Default for FuzzResult {
388 fn default() -> Self {
389 Self::new()
390 }
391}
392
393pub mod properties {
395 use super::*;
396
397 pub fn source_ordering_preserved(events: &[Event]) -> bool {
399 let mut last_seq: HashMap<NodeId, u64> = HashMap::new();
400
401 for event in events {
402 if let Some(&prev) = last_seq.get(&event.source) {
403 if event.id.seq <= prev {
404 return false;
405 }
406 }
407 last_seq.insert(event.source, event.id.seq);
408 }
409
410 true
411 }
412
413 pub fn version_monotonic(before: &VersionVector, after: &VersionVector) -> bool {
415 !after.happens_before(before)
417 }
418
419 pub fn merge_commutative(v1: &VersionVector, v2: &VersionVector) -> bool {
421 let m1 = v1.merge(v2);
422 let m2 = v2.merge(v1);
423 m1 == m2
424 }
425
426 pub fn merge_associative(v1: &VersionVector, v2: &VersionVector, v3: &VersionVector) -> bool {
428 let m1 = v1.merge(v2).merge(v3);
429 let m2 = v1.merge(&v2.merge(v3));
430 m1 == m2
431 }
432
433 pub fn merge_idempotent(v: &VersionVector) -> bool {
435 let m = v.merge(v);
436 m == *v
437 }
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[test]
445 fn test_fuzzer_light() {
446 let mut fuzzer = StateFuzzer::new(FuzzerConfig::light());
447 let result = fuzzer.run();
448
449 println!("Light fuzz result: {:?}", result);
450 }
452
453 #[test]
454 fn test_fuzzer_default() {
455 let mut fuzzer = StateFuzzer::new(FuzzerConfig::default());
456 let result = fuzzer.run();
457
458 println!("Default fuzz result: {:?}", result);
459 }
460
461 #[test]
462 fn test_version_vector_properties() {
463 let mut v1 = VersionVector::new();
464 let mut v2 = VersionVector::new();
465 let mut v3 = VersionVector::new();
466
467 v1.increment(NodeId::new(1));
468 v1.increment(NodeId::new(2));
469
470 v2.increment(NodeId::new(2));
471 v2.increment(NodeId::new(3));
472
473 v3.increment(NodeId::new(1));
474 v3.increment(NodeId::new(3));
475
476 assert!(properties::merge_commutative(&v1, &v2));
478 assert!(properties::merge_associative(&v1, &v2, &v3));
479 assert!(properties::merge_idempotent(&v1));
480 }
481
482 #[test]
483 fn test_source_ordering() {
484 let events = vec![
485 Event::new(
486 NodeId::new(1),
487 1,
488 EventType::StateUpdate,
489 StateId::new(1),
490 MutationOp::Set(vec![1]),
491 ),
492 Event::new(
493 NodeId::new(1),
494 2,
495 EventType::StateUpdate,
496 StateId::new(1),
497 MutationOp::Set(vec![2]),
498 ),
499 Event::new(
500 NodeId::new(2),
501 1,
502 EventType::StateUpdate,
503 StateId::new(1),
504 MutationOp::Set(vec![3]),
505 ),
506 Event::new(
507 NodeId::new(1),
508 3,
509 EventType::StateUpdate,
510 StateId::new(1),
511 MutationOp::Set(vec![4]),
512 ),
513 ];
514
515 assert!(properties::source_ordering_preserved(&events));
516 }
517
518 #[test]
519 fn test_convergence_detection() {
520 let config = FuzzerConfig {
521 node_count: 3,
522 state_count: 2,
523 event_count: 10,
524 concurrent_prob: 0.0,
525 reorder_prob: 0.0,
526 partition_prob: 0.0,
527 seed: 42,
528 };
529
530 let mut fuzzer = StateFuzzer::new(config);
531 let result = fuzzer.run();
532
533 assert!(result.convergence.is_converged());
535 }
536}