use crate::sync_sim::node::SimNode;
use crate::sync_sim::types::StateDigest;
pub fn nodes_converged(a: &mut SimNode, b: &mut SimNode) -> bool {
a.state_digest() == b.state_digest()
}
pub fn all_converged(nodes: &mut [SimNode]) -> bool {
if nodes.is_empty() {
return true;
}
let first = nodes[0].state_digest();
nodes.iter_mut().all(|n| n.state_digest() == first)
}
pub fn majority_digest(nodes: &mut [SimNode]) -> Option<StateDigest> {
use std::collections::HashMap;
let mut counts: HashMap<StateDigest, usize> = HashMap::new();
for node in nodes.iter_mut() {
*counts.entry(node.state_digest()).or_default() += 1;
}
counts
.iter()
.max_by(|(d1, c1), (d2, c2)| {
c1.cmp(c2).then_with(|| {
d1.0.cmp(&d2.0)
})
})
.map(|(digest, _)| *digest)
}
pub fn divergence_percentage(a: &SimNode, b: &SimNode) -> f64 {
let a_count = a.entity_count();
let b_count = b.entity_count();
if a_count == 0 && b_count == 0 {
return 0.0;
}
let mut shared = 0;
for entity_a in a.iter_entities() {
if let Some(entity_b) = b.get_entity(&entity_a.id) {
if entity_a.data == entity_b.data && entity_a.metadata == entity_b.metadata {
shared += 1;
}
}
}
let total = a_count + b_count - shared;
if total == 0 {
return 0.0;
}
let different = total - shared;
different as f64 / total as f64
}
macro_rules! assert_converged {
($($node:expr),+ $(,)?) => {{
let nodes: &mut [&mut $crate::sync_sim::node::SimNode] = &mut [$(&mut $node),+];
let digests: Vec<_> = nodes.iter_mut().map(|n| n.state_digest()).collect();
if !digests.windows(2).all(|w| w[0] == w[1]) {
panic!(
"Nodes not converged!\nDigests:\n{}",
nodes.iter()
.zip(digests.iter())
.map(|(n, d)| format!(" {}: {:?}", n.id(), d))
.collect::<Vec<_>>()
.join("\n")
);
}
}};
}
macro_rules! assert_not_converged {
($($node:expr),+ $(,)?) => {{
let nodes: &mut [&mut $crate::sync_sim::node::SimNode] = &mut [$(&mut $node),+];
let digests: Vec<_> = nodes.iter_mut().map(|n| n.state_digest()).collect();
if digests.windows(2).all(|w| w[0] == w[1]) {
panic!("Nodes unexpectedly converged with digest: {:?}", digests[0]);
}
}};
}
macro_rules! assert_entity_count {
($node:expr, $count:expr) => {{
let actual = $node.entity_count();
let expected = $count;
if actual != expected {
panic!(
"Node {} entity count mismatch: expected {}, got {}",
$node.id(),
expected,
actual
);
}
}};
}
macro_rules! assert_has_entity {
($node:expr, $id:expr) => {{
if !$node.has_entity(&$id) {
panic!("Node {} missing entity {:?}", $node.id(), $id);
}
}};
}
macro_rules! assert_no_entity {
($node:expr, $id:expr) => {{
if $node.has_entity(&$id) {
panic!("Node {} unexpectedly has entity {:?}", $node.id(), $id);
}
}};
}
macro_rules! assert_idle {
($node:expr) => {{
if !$node.sync_state.is_idle() {
panic!(
"Node {} not idle, state: {:?}",
$node.id(),
$node.sync_state
);
}
}};
}
macro_rules! assert_buffer_empty {
($node:expr) => {{
if !$node.delta_buffer.is_empty() {
panic!(
"Node {} buffer not empty, size: {}",
$node.id(),
$node.buffer_size()
);
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sync_sim::types::EntityId;
use calimero_primitives::crdt::CrdtType;
#[test]
fn test_nodes_converged() {
let mut a = SimNode::new("a");
let mut b = SimNode::new("b");
assert!(nodes_converged(&mut a, &mut b));
let id = EntityId::from_u64(1);
a.insert_entity(id, vec![1, 2, 3], CrdtType::lww_register("test"));
b.insert_entity(id, vec![1, 2, 3], CrdtType::lww_register("test"));
assert!(nodes_converged(&mut a, &mut b));
a.insert_entity(
EntityId::from_u64(2),
vec![4, 5, 6],
CrdtType::lww_register("test"),
);
assert!(!nodes_converged(&mut a, &mut b));
}
#[test]
fn test_all_converged() {
let mut nodes = vec![SimNode::new("a"), SimNode::new("b"), SimNode::new("c")];
assert!(all_converged(&mut nodes));
let id = EntityId::from_u64(1);
for node in &mut nodes {
node.insert_entity(id, vec![1, 2, 3], CrdtType::lww_register("test"));
}
assert!(all_converged(&mut nodes));
nodes[1].insert_entity(
EntityId::from_u64(2),
vec![4],
CrdtType::lww_register("test"),
);
assert!(!all_converged(&mut nodes));
}
#[test]
fn test_divergence_percentage() {
let mut a = SimNode::new("a");
let mut b = SimNode::new("b");
assert_eq!(divergence_percentage(&a, &b), 0.0);
let id = EntityId::from_u64(1);
a.insert_entity(id, vec![1], CrdtType::lww_register("test"));
b.insert_entity(id, vec![1], CrdtType::lww_register("test"));
assert_eq!(divergence_percentage(&a, &b), 0.0);
a.insert_entity(
EntityId::from_u64(2),
vec![2],
CrdtType::lww_register("test"),
);
let div = divergence_percentage(&a, &b);
assert!((div - 0.5).abs() < 0.001);
}
#[test]
fn test_divergence_content_aware() {
let mut a = SimNode::new("a");
let mut b = SimNode::new("b");
let id = EntityId::from_u64(1);
a.insert_entity(id, vec![1, 2, 3], CrdtType::lww_register("test"));
b.insert_entity(id, vec![4, 5, 6], CrdtType::lww_register("test"));
let div = divergence_percentage(&a, &b);
assert!(
(div - 1.0).abs() < 0.001,
"Expected 100% divergence for conflicting content, got {}",
div
);
}
#[test]
fn test_assert_converged_macro() {
let mut a = SimNode::new("a");
let mut b = SimNode::new("b");
assert_converged!(a, b);
let id = EntityId::from_u64(1);
a.insert_entity(id, vec![1], CrdtType::lww_register("test"));
b.insert_entity(id, vec![1], CrdtType::lww_register("test"));
assert_converged!(a, b);
}
#[test]
#[should_panic(expected = "Nodes not converged")]
fn test_assert_converged_macro_fails() {
let mut a = SimNode::new("a");
let mut b = SimNode::new("b");
a.insert_entity(
EntityId::from_u64(1),
vec![1],
CrdtType::lww_register("test"),
);
assert_converged!(a, b);
}
#[test]
fn test_assert_not_converged_macro() {
let mut a = SimNode::new("a");
let mut b = SimNode::new("b");
a.insert_entity(
EntityId::from_u64(1),
vec![1],
CrdtType::lww_register("test"),
);
assert_not_converged!(a, b);
}
#[test]
fn test_assert_entity_count_macro() {
let mut a = SimNode::new("a");
assert_entity_count!(a, 0);
a.insert_entity(
EntityId::from_u64(1),
vec![1],
CrdtType::lww_register("test"),
);
assert_entity_count!(a, 1);
}
#[test]
fn test_assert_has_entity_macro() {
let mut a = SimNode::new("a");
let id = EntityId::from_u64(1);
a.insert_entity(id, vec![1], CrdtType::lww_register("test"));
assert_has_entity!(a, id);
}
#[test]
fn test_assert_idle_macro() {
let a = SimNode::new("a");
assert_idle!(a);
}
#[test]
fn test_assert_buffer_empty_macro() {
let a = SimNode::new("a");
assert_buffer_empty!(a);
}
}