use chaincraft::{
clear_local_registry, network::PeerId, shared_object::ApplicationObject,
storage::MemoryStorage, BalanceLedger, ChaincraftNode,
};
use std::sync::Arc;
use tokio::time::{sleep, Duration};
async fn create_network(num_nodes: usize) -> Vec<ChaincraftNode> {
clear_local_registry();
let mut nodes = Vec::new();
for _ in 0..num_nodes {
let id = PeerId::new();
let storage = Arc::new(MemoryStorage::new());
let mut node = ChaincraftNode::new(id, storage);
node.set_port(0);
node.disable_local_discovery();
let ledger: Box<dyn ApplicationObject> = Box::new(BalanceLedger::new());
node.add_shared_object(ledger).await.unwrap();
node.start().await.unwrap();
nodes.push(node);
}
nodes
}
async fn connect_nodes(nodes: &mut [ChaincraftNode]) {
let num_nodes = nodes.len();
for i in 0..num_nodes {
let next_node = (i + 1) % num_nodes;
let next_host = nodes[next_node].host();
let next_port = nodes[next_node].port();
let connect_addr = format!("{next_host}:{next_port}");
nodes[i].connect_to_peer(&connect_addr).await.unwrap();
}
}
#[tokio::test]
async fn test_network_creation() {
let num_nodes = 5;
let mut nodes = create_network(num_nodes).await;
connect_nodes(&mut nodes).await;
sleep(Duration::from_secs(1)).await;
assert_eq!(nodes.len(), num_nodes);
for node in &nodes {
assert!(node.is_running_async().await);
assert!(node.shared_object_count().await > 0);
}
for mut node in nodes {
node.close().await.unwrap();
}
}
#[tokio::test]
async fn test_shared_object_propagation() {
let num_nodes = 5;
let mut nodes = create_network(num_nodes).await;
connect_nodes(&mut nodes).await;
sleep(Duration::from_secs(1)).await;
for i in 0..nodes.len() {
let value = (i + 1) as f64;
let account = format!("user-{i}");
let data = serde_json::json!({
"action": "credit",
"account": account,
"amount": value,
});
nodes[i]
.create_shared_message_with_data(data)
.await
.unwrap();
sleep(Duration::from_millis(500)).await;
for (j, n) in nodes.iter().enumerate() {
let shared_objects = n.shared_objects().await;
if let Some(obj) = shared_objects.first() {
if let Some(ledger) = obj.as_any().downcast_ref::<BalanceLedger>() {
println!("Node {}: Ledger entries: {}", j, ledger.balances.len());
}
}
}
}
for (i, node) in nodes.iter().enumerate() {
let shared_objects = node.shared_objects().await;
if let Some(obj) = shared_objects.first() {
if let Some(ledger) = obj.as_any().downcast_ref::<BalanceLedger>() {
let account = format!("user-{i}");
assert!(ledger.balances.get(&account).cloned().unwrap_or(0.0) >= (i + 1) as f64);
}
}
}
for mut node in nodes {
node.close().await.unwrap();
}
}
#[tokio::test]
async fn test_message_deduplication() {
let mut node = create_network(1).await.into_iter().next().unwrap();
let data = serde_json::json!({
"action": "credit",
"account": "alice",
"amount": 42.0,
});
for _ in 0..5 {
node.create_shared_message_with_data(data.clone())
.await
.unwrap();
}
let shared_objects = node.shared_objects().await;
if let Some(obj) = shared_objects.first() {
if let Some(ledger) = obj.as_any().downcast_ref::<BalanceLedger>() {
assert_eq!(ledger.balances.get("alice").cloned().unwrap_or(0.0), 42.0);
}
}
node.close().await.unwrap();
}
#[tokio::test]
async fn test_shared_object_state() {
let mut node = create_network(1).await.into_iter().next().unwrap();
let values = vec![10.0, 20.0, 30.0];
for value in &values {
let data = serde_json::json!({
"action": "credit",
"account": "alice",
"amount": value,
});
node.create_shared_message_with_data(data).await.unwrap();
}
let shared_objects = node.shared_objects().await;
if let Some(obj) = shared_objects.first() {
if let Some(ledger) = obj.as_any().downcast_ref::<BalanceLedger>() {
let expected_sum: f64 = values.iter().sum();
assert_eq!(ledger.balances.get("alice").cloned().unwrap_or(0.0), expected_sum);
let state = obj.get_state().await.unwrap();
assert_eq!(state["balance_count"], 1);
}
}
node.close().await.unwrap();
}