1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
|
//! 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<Node> {
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}");
}
}
}
|