use anyhow::Result;
use p2p_foundation::{
P2PNode, NodeConfig,
bootstrap::CacheConfig,
};
use std::net::SocketAddr;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::sleep;
use tracing::info;
struct SimpleE2ENetwork {
nodes: Vec<P2PNode>,
_cache_dirs: Vec<TempDir>, base_port: u16,
}
impl SimpleE2ENetwork {
async fn new(size: usize) -> Result<Self> {
let base_port = 26000 + (rand::random::<u16>() % 1000);
let mut nodes = Vec::new();
let mut cache_dirs = Vec::new();
for i in 0..size {
let cache_dir = TempDir::new()?;
let cache_config = CacheConfig {
cache_dir: cache_dir.path().to_path_buf(),
max_contacts: 100,
..CacheConfig::default()
};
let listen_addr: SocketAddr = format!("127.0.0.1:{}", base_port + i as u16).parse()?;
let mut config = NodeConfig::default();
config.listen_addr = listen_addr;
config.bootstrap_cache_config = Some(cache_config);
config.max_connections = 10;
config.max_incoming_connections = 5;
if i > 0 {
config.bootstrap_peers_str = vec![
format!("127.0.0.1:{}", base_port),
];
}
let node = P2PNode::new(config).await?;
nodes.push(node);
cache_dirs.push(cache_dir);
}
Ok(Self {
nodes,
_cache_dirs: cache_dirs,
base_port,
})
}
async fn start_all(&mut self) -> Result<()> {
for (i, node) in self.nodes.iter_mut().enumerate() {
node.start().await?;
info!("Started simple E2E node {} on port {}", i, self.base_port + i as u16);
sleep(Duration::from_millis(100)).await;
}
sleep(Duration::from_secs(3)).await;
Ok(())
}
async fn stop_all(&mut self) -> Result<()> {
for node in self.nodes.iter_mut() {
let _ = node.stop().await;
}
Ok(())
}
fn get_node(&self, index: usize) -> &P2PNode {
&self.nodes[index]
}
async fn get_basic_stats(&self) -> NetworkStats {
let mut total_peers = 0;
let mut total_cached_contacts = 0;
let mut nodes_with_peers = 0;
for node in &self.nodes {
let peer_count = node.peer_count().await;
total_peers += peer_count;
if peer_count > 0 {
nodes_with_peers += 1;
}
total_cached_contacts += node.cached_peer_count().await;
}
NetworkStats {
total_nodes: self.nodes.len(),
total_peers,
total_cached_contacts,
nodes_with_peers,
avg_peers_per_node: if self.nodes.is_empty() { 0.0 } else { total_peers as f64 / self.nodes.len() as f64 },
}
}
}
#[derive(Debug)]
struct NetworkStats {
total_nodes: usize,
total_peers: usize,
total_cached_contacts: usize,
nodes_with_peers: usize,
avg_peers_per_node: f64,
}
#[tokio::test]
async fn test_basic_network_formation() -> Result<()> {
let mut network = SimpleE2ENetwork::new(5).await?;
info!("Testing basic network formation with 5 nodes");
network.start_all().await?;
sleep(Duration::from_secs(2)).await;
let stats = network.get_basic_stats().await;
info!("Network stats: {:?}", stats);
assert_eq!(stats.total_nodes, 5);
assert!(stats.nodes_with_peers >= 1, "At least one node should have peers");
assert!(stats.total_peers > 0, "Network should have peer connections");
network.stop_all().await?;
info!("✅ Basic network formation test passed");
Ok(())
}
#[tokio::test]
async fn test_bootstrap_cache_functionality() -> Result<()> {
let mut network = SimpleE2ENetwork::new(4).await?;
info!("Testing bootstrap cache functionality");
network.start_all().await?;
sleep(Duration::from_secs(5)).await;
let stats = network.get_basic_stats().await;
info!("Bootstrap cache stats: {:?}", stats);
let mut nodes_with_cache_stats = 0;
for (i, node) in network.nodes.iter().enumerate() {
if let Ok(Some(cache_stats)) = node.get_bootstrap_cache_stats().await {
nodes_with_cache_stats += 1;
info!("Node {} cache stats: {} total contacts", i, cache_stats.total_contacts);
}
}
assert!(stats.total_cached_contacts > 0, "Bootstrap cache should contain contacts");
assert!(nodes_with_cache_stats > 0, "At least one node should have cache stats");
network.stop_all().await?;
info!("✅ Bootstrap cache functionality test passed");
Ok(())
}
#[tokio::test]
async fn test_node_restart_and_persistence() -> Result<()> {
let mut network = SimpleE2ENetwork::new(3).await?;
info!("Testing node restart and cache persistence");
network.start_all().await?;
sleep(Duration::from_secs(3)).await;
let initial_stats = network.get_basic_stats().await;
info!("Initial stats: {:?}", initial_stats);
network.stop_all().await?;
sleep(Duration::from_secs(1)).await;
network.start_all().await?;
sleep(Duration::from_secs(3)).await;
let restart_stats = network.get_basic_stats().await;
info!("Restart stats: {:?}", restart_stats);
assert_eq!(restart_stats.total_nodes, 3);
assert!(restart_stats.nodes_with_peers >= 1, "Network should reconnect after restart");
network.stop_all().await?;
info!("✅ Node restart and persistence test passed");
Ok(())
}
#[tokio::test]
async fn test_peer_discovery_and_growth() -> Result<()> {
let mut core_network = SimpleE2ENetwork::new(2).await?;
info!("Testing peer discovery and network growth");
core_network.start_all().await?;
sleep(Duration::from_secs(2)).await;
let initial_stats = core_network.get_basic_stats().await;
info!("Initial core network stats: {:?}", initial_stats);
let mut new_nodes = Vec::new();
let mut new_cache_dirs = Vec::new();
for i in 0..2 {
let cache_dir = TempDir::new()?;
let cache_config = CacheConfig {
cache_dir: cache_dir.path().to_path_buf(),
max_contacts: 100,
..CacheConfig::default()
};
let listen_addr: SocketAddr = format!("127.0.0.1:{}", core_network.base_port + 10 + i as u16).parse()?;
let mut config = NodeConfig::default();
config.listen_addr = listen_addr;
config.bootstrap_cache_config = Some(cache_config);
config.bootstrap_peers_str = vec![
format!("127.0.0.1:{}", core_network.base_port),
];
let node = P2PNode::new(config).await?;
node.start().await?;
new_nodes.push(node);
new_cache_dirs.push(cache_dir);
info!("Started new discovery node {}", i);
sleep(Duration::from_millis(500)).await;
}
sleep(Duration::from_secs(5)).await;
let mut new_nodes_with_peers = 0;
for (i, node) in new_nodes.iter().enumerate() {
let peer_count = node.peer_count().await;
if peer_count > 0 {
new_nodes_with_peers += 1;
}
info!("New node {} has {} peers", i, peer_count);
}
for node in new_nodes.iter_mut() {
let _ = node.stop().await;
}
core_network.stop_all().await?;
assert!(new_nodes_with_peers >= 1, "At least one new node should discover peers");
info!("✅ Peer discovery and network growth test passed");
Ok(())
}
#[tokio::test]
async fn test_network_under_load() -> Result<()> {
let mut network = SimpleE2ENetwork::new(6).await?;
info!("Testing network under simulated load");
network.start_all().await?;
sleep(Duration::from_secs(3)).await;
let initial_stats = network.get_basic_stats().await;
info!("Initial stats under load: {:?}", initial_stats);
for i in 0..50 {
let node_index = i % network.nodes.len();
let node = network.get_node(node_index);
let peer_id = format!("load_peer_{}", i);
let addr = format!("192.168.1.{}", (i % 254) + 1);
if let Err(e) = node.add_discovered_peer(peer_id.into(), vec![addr]).await {
info!("Failed to add peer {} (expected under load): {}", i, e);
}
if i % 10 == 0 {
sleep(Duration::from_millis(10)).await;
}
}
sleep(Duration::from_secs(3)).await;
let load_stats = network.get_basic_stats().await;
info!("Stats after load test: {:?}", load_stats);
assert_eq!(load_stats.total_nodes, 6);
assert!(load_stats.nodes_with_peers >= 1, "Network should remain functional under load");
assert!(load_stats.total_cached_contacts > initial_stats.total_cached_contacts,
"Cache should grow under load");
network.stop_all().await?;
info!("✅ Network under load test passed");
Ok(())
}