//! Scale test: 20 nodes with distributed put/get. //! //! Runs 20 nodes with distributed put/get to verify //! correctness at scale. use std::time::Duration; use tesseras_dht::Node; use tesseras_dht::nat::NatState; fn poll_all(nodes: &mut [Node], rounds: usize) { let fast = Duration::from_millis(1); for _ in 0..rounds { for n in nodes.iter_mut() { n.poll_timeout(fast).ok(); } } } fn make_network(n: usize) -> Vec { let mut nodes = Vec::with_capacity(n); let bootstrap = Node::bind(0).unwrap(); let bp = bootstrap.local_addr().unwrap().port(); nodes.push(bootstrap); nodes[0].set_nat_state(NatState::Global); for _ in 1..n { let mut node = Node::bind(0).unwrap(); node.set_nat_state(NatState::Global); node.join("127.0.0.1", bp).unwrap(); nodes.push(node); } std::thread::sleep(Duration::from_millis(100)); poll_all(&mut nodes, 10); nodes } #[test] fn twenty_nodes_routing() { let nodes = make_network(20); // Every node should have at least 1 peer for (i, node) in nodes.iter().enumerate() { assert!( node.routing_table_size() >= 1, "Node {i} has empty routing table" ); } // Average routing table should be > 5 let total: usize = nodes.iter().map(|n| n.routing_table_size()).sum(); let avg = total / nodes.len(); assert!(avg >= 5, "Average routing table {avg} too low"); } #[test] fn twenty_nodes_put_get() { let mut nodes = make_network(20); // Each node stores one value for i in 0..20u32 { let key = format!("scale-key-{i}"); let val = format!("scale-val-{i}"); nodes[i as usize].put(key.as_bytes(), val.as_bytes(), 300, false); } // Poll to distribute std::thread::sleep(Duration::from_millis(100)); poll_all(&mut nodes, 10); // Each node should have its own value for i in 0..20u32 { let key = format!("scale-key-{i}"); let vals = nodes[i as usize].get(key.as_bytes()); assert!(!vals.is_empty(), "Node {i} lost its own value"); } // Count total stored values across network let total_stored: usize = nodes.iter().map(|n| n.storage_count()).sum(); assert!( total_stored >= 20, "Total stored {total_stored} should be >= 20" ); } #[test] #[ignore] // timing-sensitive, consumes 100% CPU with 20 nodes polling fn twenty_nodes_remote_get() { let mut nodes = make_network(20); // Node 0 stores a value nodes[0].put(b"find-me", b"found", 300, false); std::thread::sleep(Duration::from_millis(100)); poll_all(&mut nodes, 10); // Node 19 tries to get it — trigger FIND_VALUE let _ = nodes[19].get(b"find-me"); // Poll all nodes to let FIND_VALUE propagate for _ in 0..40 { poll_all(&mut nodes, 5); std::thread::sleep(Duration::from_millis(30)); let vals = nodes[19].get(b"find-me"); if !vals.is_empty() { assert_eq!(vals[0], b"found"); return; } } panic!("Node 19 should find the value via FIND_VALUE"); } #[test] fn twenty_nodes_multiple_puts() { let mut nodes = make_network(20); // 5 nodes store 10 values each for n in 0..5 { for k in 0..10u32 { let key = format!("n{n}-k{k}"); let val = format!("n{n}-v{k}"); nodes[n].put(key.as_bytes(), val.as_bytes(), 300, false); } } std::thread::sleep(Duration::from_millis(100)); poll_all(&mut nodes, 10); // Verify origin nodes have their values for n in 0..5 { for k in 0..10u32 { let key = format!("n{n}-k{k}"); let vals = nodes[n].get(key.as_bytes()); assert!(!vals.is_empty(), "Node {n} lost key n{n}-k{k}"); } } }