use std::sync::atomic::{
AtomicU64,
Ordering
};
use rusqlite::Result as SqlResult;
use crate::peer::NodeID;
use crate::store::{
Action,
Conspirator,
Op,
Store,
Version
};
struct Permuter<const N: usize> {
index: usize,
stack: [usize; N],
previous: Option<[usize; N]>
}
impl<const N: usize> Permuter<N> {
fn new() -> Self {
let index = 0;
let stack = [0; N];
Self {index, stack, previous: None}
}
}
impl<const N: usize> Iterator for Permuter<N> {
type Item = [usize; N];
fn next(&mut self) -> Option<Self::Item> {
let Some(previous) = self.previous.as_mut() else {
let mut i = 0..N;
self.previous = Some([0; N].map(|_| {
i.next().unwrap()
}));
return self.previous;
};
loop {
if self.index >= N {return None;}
if self.stack[self.index] < self.index {
if self.index % 2 == 0 {
previous.swap(0, self.index);
}
else {
previous.swap(self.stack[self.index], self.index);
}
self.stack[self.index] += 1;
self.index = 1;
break Some(*previous);
}
else {
self.stack[self.index] = 0;
self.index += 1;
}
}
}
}
struct Node(NodeID, AtomicU64);
impl Node {
fn create(id: NodeID, name: String) -> (Self, Op) {
let node = Self(id, AtomicU64::new(0));
let op = Op {
origin: node.next_origin(),
previous: None,
target: id,
action: Action::Name(name)
};
(node, op)
}
#[must_use]
fn next_origin(&self) -> Version {
Version {
node: self.0,
counter: self.1.fetch_add(1, Ordering::SeqCst)
}
}
}
impl Store {
fn print_ops(&self) -> SqlResult<()> {
let ops: Vec<Op> = self.query_all(Op::SORT_SQL)?;
if ops.is_empty() {
println!("no ops");
return Ok(());
}
println!(" origin ( previous ) | target: action");
for Op { origin, previous, target, action } in ops {
if let Some(p) = previous {
println!("{origin} ({p}) | {target}: {action:?}");
}
else {
println!("{origin} ( / ) | {target}: {action:?}");
}
}
Ok(())
}
}
#[test]
fn converge() -> SqlResult<()> {
let (node_a, name_a) = Node::create(NodeID::random(), "A".to_string());
let (node_b, name_b) = Node::create(NodeID::random(), "B".to_string());
let (node_c, name_c) = Node::create(NodeID::random(), "C".to_string());
let active_c = Op {
origin: node_c.next_origin(),
previous: Some(name_c.origin),
target: node_c.0,
action: Action::Active(true)
};
let rename_b = Op {
origin: node_b.next_origin(),
previous: Some(active_c.origin),
target: node_b.0,
action: Action::Name(String::from("B2"))
};
let mut ops = vec![
name_a,
name_b,
name_c,
];
ops.push(active_c);
ops.push(rename_b);
let store = Store::create_in_memory()?;
for op in ops.clone() {
store.absorb_op(op)?;
}
store.print_ops()?;
let hash = store.hash()?;
println!("Hash: {hash:016x}");
let list = store.get_all::<Conspirator>()?;
println!("{list:?}");
for node in [&node_a, &node_b, &node_c] {
assert!(list.iter().any(|c| c.id == node.0));
}
let check_list = |other: &[Conspirator]| {
assert_eq!(other.len(), 3);
for node in [&node_a, &node_b, &node_c] {
assert_eq!(
list.iter().find(|c| c.id == node.0).unwrap(),
other.iter().find(|c| c.id == node.0).unwrap()
);
}
};
let store2 = Store::create_in_memory()?;
for op in ops.clone() {
store2.absorb_op(op)?;
}
let list2 = store2.get_all::<Conspirator>()?;
assert_eq!(list, list2);
check_list(&list2);
let hash2 = store2.hash()?;
assert_eq!(hash, hash2);
for permutation in Permuter::<5>::new() {
let permuted_store = Store::create_in_memory()?;
for idx in permutation {
permuted_store.absorb_op(ops[idx].clone())?;
}
let permuted_list = permuted_store.get_all::<Conspirator>()?;
let permuted_hash = permuted_store.hash()?;
println!("{permutation:?}: {permuted_hash:016x}");
check_list(&permuted_list);
if hash != permuted_hash {
permuted_store.print_ops()?;
}
assert_eq!(hash, permuted_hash);
}
let double_store = Store::create_in_memory()?;
for op in ops.clone() {
double_store.absorb_op(op)?;
}
for op in ops.clone() {
double_store.absorb_op(op)?;
}
let double_hash = double_store.hash()?;
assert_eq!(hash, double_hash);
let double_list = double_store.get_all::<Conspirator>()?;
check_list(&double_list);
Ok(())
}
#[test]
fn sync() -> SqlResult<()> {
let store = Store::create_in_memory()?;
let id = NodeID::random();
let id_2 = NodeID::random();
let op = store.create_op(id, id, Action::Active(true))?;
store.absorb_op(op.clone())?;
assert!(store.sync(&[])?.contains(&op));
assert!(store.sync(&[Version::new(id, 0)])?.is_empty());
assert!(store.sync(&[Version::new(id, 1)])?.is_empty());
assert!(store.sync(&[Version::new(id_2, 3)])?.contains(&op));
let op_2 = store.create_op(id_2, id_2, Action::Active(true))?;
store.absorb_op(op_2.clone())?;
let ops = store.sync(&[])?;
assert!(ops.contains(&op));
assert!(ops.contains(&op_2));
Ok(())
}
#[test]
fn hold() -> SqlResult<()> {
let store_a = Store::create_in_memory()?;
let store_b = Store::create_in_memory()?;
let id_a = NodeID::random();
let id_b = NodeID::random();
let op_a = store_a.create_op(id_a, id_a, Action::Active(true))?;
store_a.absorb_op(op_a.clone())?;
let op_b = store_a.create_op(id_b, id_b, Action::Active(true))?;
store_b.absorb_op(op_b.clone())?;
assert_eq!(store_b.previous()?, None);
assert_eq!(store_b.node_counter(id_b)?, None);
assert_eq!(store_b.node_counter(id_a)?, None);
store_b.absorb_op(op_a.clone())?;
assert_eq!(store_b.previous()?, Some(Version { node: id_b, counter: 0 }));
assert_eq!(store_b.node_counter(id_a)?, Some(0));
assert_eq!(store_b.node_counter(id_b)?, Some(0));
Ok(())
}