1use crate::{NetworkConfig, NetworkNode};
11use anyhow::{bail, Context, Result};
12use cid::Cid;
13use libp2p::{Multiaddr, PeerId};
14use std::str::FromStr;
15use std::time::Duration;
16use tracing::{debug, info, warn};
17
18pub const IPFS_BOOTSTRAP_NODES: &[&str] = &[
20 "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
22 "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
23 "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb",
24 "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
25 "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
27 "/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
28];
29
30pub const TEST_CIDS: &[&str] = &[
32 "QmZULkCELmmk5XNfCgTnCyFgAVxBRBXyDHGGMVoLFLiXEN",
34 "QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u",
36];
37
38#[derive(Debug, Clone)]
40pub struct IpfsCompatTestResults {
41 pub bootstrap_connected: bool,
43 pub connected_ipfs_nodes: usize,
45 pub dht_queries_work: bool,
47 pub identify_protocol_works: bool,
49 pub ping_protocol_works: bool,
51 pub provider_records_work: bool,
53 pub test_duration: Duration,
55 pub errors: Vec<String>,
57}
58
59impl IpfsCompatTestResults {
60 pub fn all_passed(&self) -> bool {
62 self.bootstrap_connected
63 && self.connected_ipfs_nodes > 0
64 && self.dht_queries_work
65 && self.identify_protocol_works
66 && self.ping_protocol_works
67 && self.errors.is_empty()
68 }
69
70 pub fn summary(&self) -> String {
72 format!(
73 "IPFS Compatibility Test Results:\n\
74 - Bootstrap connected: {}\n\
75 - Connected IPFS nodes: {}\n\
76 - DHT queries work: {}\n\
77 - Identify protocol works: {}\n\
78 - Ping protocol works: {}\n\
79 - Provider records work: {}\n\
80 - Test duration: {:?}\n\
81 - Errors: {}",
82 self.bootstrap_connected,
83 self.connected_ipfs_nodes,
84 self.dht_queries_work,
85 self.identify_protocol_works,
86 self.ping_protocol_works,
87 self.provider_records_work,
88 self.test_duration,
89 if self.errors.is_empty() {
90 "None".to_string()
91 } else {
92 format!("{}", self.errors.len())
93 }
94 )
95 }
96}
97
98pub async fn test_ipfs_connectivity(
115 node: &mut NetworkNode,
116 timeout: Duration,
117) -> Result<IpfsCompatTestResults> {
118 let start = std::time::Instant::now();
119 let mut results = IpfsCompatTestResults {
120 bootstrap_connected: false,
121 connected_ipfs_nodes: 0,
122 dht_queries_work: false,
123 identify_protocol_works: false,
124 ping_protocol_works: false,
125 provider_records_work: false,
126 test_duration: Duration::default(),
127 errors: Vec::new(),
128 };
129
130 info!("Starting IPFS compatibility test");
131
132 info!("Test 1: Connecting to IPFS bootstrap nodes");
134 match test_bootstrap_connection(node, timeout).await {
135 Ok(count) => {
136 results.bootstrap_connected = true;
137 results.connected_ipfs_nodes = count;
138 info!("Successfully connected to {} IPFS bootstrap nodes", count);
139 }
140 Err(e) => {
141 warn!("Failed to connect to IPFS bootstrap nodes: {}", e);
142 results
143 .errors
144 .push(format!("Bootstrap connection failed: {}", e));
145 }
146 }
147
148 info!("Test 2: Testing identify protocol");
150 match test_identify_protocol(node).await {
151 Ok(_) => {
152 results.identify_protocol_works = true;
153 info!("Identify protocol works");
154 }
155 Err(e) => {
156 warn!("Identify protocol test failed: {}", e);
157 results
158 .errors
159 .push(format!("Identify protocol failed: {}", e));
160 }
161 }
162
163 info!("Test 3: Testing ping protocol");
165 match test_ping_protocol(node).await {
166 Ok(_) => {
167 results.ping_protocol_works = true;
168 info!("Ping protocol works");
169 }
170 Err(e) => {
171 warn!("Ping protocol test failed: {}", e);
172 results.errors.push(format!("Ping protocol failed: {}", e));
173 }
174 }
175
176 info!("Test 4: Testing DHT queries");
178 match test_dht_queries(node, timeout).await {
179 Ok(_) => {
180 results.dht_queries_work = true;
181 info!("DHT queries work");
182 }
183 Err(e) => {
184 warn!("DHT query test failed: {}", e);
185 results.errors.push(format!("DHT queries failed: {}", e));
186 }
187 }
188
189 info!("Test 5: Testing provider records");
191 match test_provider_records(node, timeout).await {
192 Ok(_) => {
193 results.provider_records_work = true;
194 info!("Provider records work");
195 }
196 Err(e) => {
197 warn!("Provider record test failed: {}", e);
198 results
199 .errors
200 .push(format!("Provider records failed: {}", e));
201 }
202 }
203
204 results.test_duration = start.elapsed();
205 info!(
206 "IPFS compatibility test completed in {:?}",
207 results.test_duration
208 );
209 info!("{}", results.summary());
210
211 Ok(results)
212}
213
214async fn test_bootstrap_connection(node: &mut NetworkNode, timeout: Duration) -> Result<usize> {
216 let mut connected_count = 0;
217
218 for bootstrap_addr_str in IPFS_BOOTSTRAP_NODES.iter().take(3) {
219 match parse_multiaddr_with_peer(bootstrap_addr_str) {
221 Ok((addr, _peer_id)) => {
222 debug!("Attempting to connect to bootstrap node: {}", addr);
223 match tokio::time::timeout(timeout, node.connect(addr.clone())).await {
224 Ok(Ok(_)) => {
225 connected_count += 1;
226 info!("Connected to bootstrap node: {}", addr);
227 }
228 Ok(Err(e)) => {
229 warn!("Failed to connect to {}: {}", addr, e);
230 }
231 Err(_) => {
232 warn!("Timeout connecting to {}", addr);
233 }
234 }
235 }
236 Err(e) => {
237 warn!(
238 "Failed to parse bootstrap address {}: {}",
239 bootstrap_addr_str, e
240 );
241 }
242 }
243 }
244
245 if connected_count == 0 {
246 bail!("Failed to connect to any IPFS bootstrap nodes");
247 }
248
249 Ok(connected_count)
250}
251
252async fn test_identify_protocol(node: &NetworkNode) -> Result<()> {
254 let peer_count = node.get_peer_count();
257 if peer_count == 0 {
258 bail!("No peers connected, cannot test identify protocol");
259 }
260
261 debug!("Identify protocol operational with {} peers", peer_count);
262 Ok(())
263}
264
265async fn test_ping_protocol(node: &NetworkNode) -> Result<()> {
267 let peer_count = node.get_peer_count();
270 if peer_count == 0 {
271 bail!("No peers connected, cannot test ping protocol");
272 }
273
274 debug!("Ping protocol operational with {} peers", peer_count);
275 Ok(())
276}
277
278async fn test_dht_queries(node: &mut NetworkNode, timeout: Duration) -> Result<()> {
280 debug!("Bootstrapping DHT");
282 match tokio::time::timeout(timeout, node.bootstrap_dht()).await {
283 Ok(Ok(_)) => {
284 info!("DHT bootstrap successful");
285 Ok(())
286 }
287 Ok(Err(e)) => {
288 bail!("DHT bootstrap failed: {}", e);
289 }
290 Err(_) => {
291 bail!("DHT bootstrap timed out");
292 }
293 }
294}
295
296async fn test_provider_records(node: &mut NetworkNode, timeout: Duration) -> Result<()> {
298 let test_cid = Cid::from_str(TEST_CIDS[0]).context("Failed to parse test CID")?;
300
301 debug!("Testing provider records with CID: {}", test_cid);
302
303 match tokio::time::timeout(timeout, node.provide(&test_cid)).await {
305 Ok(Ok(_)) => {
306 info!("Successfully published provider record");
307 }
308 Ok(Err(e)) => {
309 warn!("Failed to publish provider record: {}", e);
310 }
312 Err(_) => {
313 warn!("Provider record publish timed out");
314 }
315 }
316
317 match tokio::time::timeout(timeout, node.find_providers(&test_cid)).await {
319 Ok(Ok(_)) => {
320 info!("Successfully queried for providers of test CID");
321 Ok(())
322 }
323 Ok(Err(e)) => {
324 warn!("Failed to find providers: {}", e);
325 Ok(())
327 }
328 Err(_) => {
329 warn!("Provider query timed out");
330 Ok(())
331 }
332 }
333}
334
335fn parse_multiaddr_with_peer(addr_str: &str) -> Result<(Multiaddr, Option<PeerId>)> {
337 let addr = Multiaddr::from_str(addr_str).context("Failed to parse multiaddr")?;
338
339 let peer_id = addr.iter().find_map(|protocol| {
341 if let libp2p::multiaddr::Protocol::P2p(peer_id) = protocol {
342 Some(peer_id)
343 } else {
344 None
345 }
346 });
347
348 Ok((addr, peer_id))
349}
350
351pub fn ipfs_test_config() -> NetworkConfig {
353 NetworkConfig {
354 bootstrap_peers: IPFS_BOOTSTRAP_NODES.iter().map(|s| s.to_string()).collect(),
355 enable_quic: true,
356 enable_mdns: false, enable_nat_traversal: true,
358 ..Default::default()
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_parse_multiaddr_with_peer() {
368 let addr_str =
369 "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ";
370 let result = parse_multiaddr_with_peer(addr_str);
371 assert!(result.is_ok());
372
373 let (addr, peer_id) = result.unwrap();
374 assert!(peer_id.is_some());
375 assert!(addr.to_string().contains("104.131.131.82"));
376 }
377
378 #[test]
379 fn test_ipfs_bootstrap_nodes_valid() {
380 for addr_str in IPFS_BOOTSTRAP_NODES {
381 let result = parse_multiaddr_with_peer(addr_str);
382 assert!(result.is_ok(), "Invalid bootstrap address: {}", addr_str);
383 }
384 }
385
386 #[test]
387 fn test_cids_valid() {
388 for cid_str in TEST_CIDS {
389 let result = Cid::from_str(cid_str);
390 assert!(result.is_ok(), "Invalid test CID: {}", cid_str);
391 }
392 }
393
394 #[test]
395 fn test_ipfs_test_config() {
396 let config = ipfs_test_config();
397 assert!(config.enable_quic);
398 assert!(config.enable_nat_traversal);
399 assert!(!config.bootstrap_peers.is_empty());
400 assert_eq!(config.kademlia.replication_factor, 20);
401 }
402
403 #[test]
404 fn test_compat_results_all_passed() {
405 let mut results = IpfsCompatTestResults {
406 bootstrap_connected: true,
407 connected_ipfs_nodes: 3,
408 dht_queries_work: true,
409 identify_protocol_works: true,
410 ping_protocol_works: true,
411 provider_records_work: true,
412 test_duration: Duration::from_secs(10),
413 errors: Vec::new(),
414 };
415
416 assert!(results.all_passed());
417
418 results.errors.push("Test error".to_string());
419 assert!(!results.all_passed());
420 }
421
422 #[test]
423 fn test_compat_results_summary() {
424 let results = IpfsCompatTestResults {
425 bootstrap_connected: true,
426 connected_ipfs_nodes: 3,
427 dht_queries_work: true,
428 identify_protocol_works: true,
429 ping_protocol_works: true,
430 provider_records_work: false,
431 test_duration: Duration::from_secs(10),
432 errors: vec!["Test error".to_string()],
433 };
434
435 let summary = results.summary();
436 assert!(summary.contains("Bootstrap connected: true"));
437 assert!(summary.contains("Connected IPFS nodes: 3"));
438 assert!(summary.contains("Errors: 1"));
439 }
440}