1use hickory_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts};
2use hickory_resolver::Resolver;
3use log::info;
4use serde::{Deserialize, Serialize};
5use std::net::{IpAddr, SocketAddr};
6use std::process::Command;
7use std::time::{Duration, Instant};
8use tokio::net::TcpStream;
9use tokio::time::timeout;
10
11#[derive(Clone, Debug, Serialize, Deserialize)]
13pub struct NetworkValidationConfig {
14 pub endpoints: Vec<String>,
15 pub dns_servers: Vec<String>,
16 pub required_ports: Vec<u16>,
17 pub latency_threshold_ms: u64,
18 pub bandwidth_threshold_mbps: f64,
19 pub perform_traceroute: bool,
20 pub check_firewall: bool,
21 pub check_ipv6: bool,
22 pub check_ssl: bool,
23}
24
25impl Default for NetworkValidationConfig {
26 fn default() -> Self {
27 Self {
28 endpoints: vec![
29 "https://bitcoin-rpc.publicnode.com".to_string(),
30 "https://bitcoin-testnet-rpc.publicnode.com".to_string(),
31 ],
32 dns_servers: vec!["8.8.8.8".to_string(), "1.1.1.1".to_string()],
33 required_ports: vec![22, 80, 443, 8333, 18333],
34 latency_threshold_ms: 300,
35 bandwidth_threshold_mbps: 10.0,
36 perform_traceroute: true,
37 check_firewall: true,
38 check_ipv6: true,
39 check_ssl: true,
40 }
41 }
42}
43
44#[derive(Clone, Debug, Serialize, Deserialize)]
46pub struct NetworkValidationResult {
47 pub connectivity: NetworkConnectivityResult,
48 pub dns: DnsValidationResult,
49 pub bandwidth: BandwidthValidationResult,
50 pub latency: LatencyValidationResult,
51 pub ports: PortValidationResult,
52 pub ssl: Option<SslValidationResult>,
53 pub firewall: Option<FirewallValidationResult>,
54 pub route: Option<RouteValidationResult>,
55 pub vpn_detected: bool,
56 pub recommendations: Vec<String>,
57 pub overall_status: ValidationStatus,
58}
59
60pub(crate) struct ValidationResults<'a> {
62 pub connectivity: &'a NetworkConnectivityResult,
63 pub dns: &'a DnsValidationResult,
64 pub bandwidth: &'a BandwidthValidationResult,
65 pub latency: &'a LatencyValidationResult,
66 pub ports: &'a PortValidationResult,
67 pub ssl: &'a Option<SslValidationResult>,
68 pub firewall: &'a Option<FirewallValidationResult>,
69 pub route: &'a Option<RouteValidationResult>,
70}
71
72#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
73pub enum ValidationStatus {
74 Pass,
75 Warning,
76 Fail,
77 Skipped,
78}
79
80#[derive(Clone, Debug, Serialize, Deserialize)]
82pub struct NetworkConnectivityResult {
83 pub internet_available: bool,
84 pub endpoints_reachable: Vec<(String, bool)>,
85 pub status: ValidationStatus,
86}
87
88#[derive(Clone, Debug, Serialize, Deserialize)]
89pub struct DnsValidationResult {
90 pub resolvers_available: Vec<(String, bool)>,
91 pub resolution_times_ms: Vec<(String, u64)>,
92 pub status: ValidationStatus,
93}
94
95#[derive(Clone, Debug, Serialize, Deserialize)]
96pub struct BandwidthValidationResult {
97 pub download_mbps: f64,
98 pub upload_mbps: f64,
99 pub status: ValidationStatus,
100}
101
102#[derive(Clone, Debug, Serialize, Deserialize)]
103pub struct LatencyValidationResult {
104 pub average_ms: u64,
105 pub min_ms: u64,
106 pub max_ms: u64,
107 pub endpoint_latencies: Vec<(String, u64)>,
108 pub status: ValidationStatus,
109}
110
111#[derive(Clone, Debug, Serialize, Deserialize)]
112pub struct PortValidationResult {
113 pub open_ports: Vec<u16>,
114 pub closed_ports: Vec<u16>,
115 pub status: ValidationStatus,
116}
117
118#[derive(Clone, Debug, Serialize, Deserialize)]
119pub struct SslValidationResult {
120 pub endpoints_secure: Vec<(String, bool)>,
121 pub certificate_issues: Vec<(String, String)>,
122 pub status: ValidationStatus,
123}
124
125#[derive(Clone, Debug, Serialize, Deserialize)]
126pub struct FirewallValidationResult {
127 pub detected: bool,
128 pub blocks_bitcoin: bool,
129 pub blocks_required_ports: Vec<u16>,
130 pub status: ValidationStatus,
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
134pub struct RouteValidationResult {
135 pub average_hops: u32,
136 pub problematic_hops: Vec<String>,
137 pub routes: Vec<(String, Vec<String>)>,
138 pub status: ValidationStatus,
139}
140
141pub struct NetworkValidator {
143 config: NetworkValidationConfig,
144}
145
146impl NetworkValidator {
147 pub fn new(config: NetworkValidationConfig) -> Self {
148 Self { config }
149 }
150
151 pub async fn validate_network(&self) -> NetworkValidationResult {
153 info!("Running comprehensive network validation...");
154
155 let connectivity = self.validate_connectivity().await;
156
157 if !connectivity.internet_available {
158 return NetworkValidationResult {
159 connectivity,
160 dns: DnsValidationResult {
161 resolvers_available: Vec::new(),
162 resolution_times_ms: Vec::new(),
163 status: ValidationStatus::Skipped,
164 },
165 bandwidth: BandwidthValidationResult {
166 download_mbps: 0.0,
167 upload_mbps: 0.0,
168 status: ValidationStatus::Skipped,
169 },
170 latency: LatencyValidationResult {
171 average_ms: 0,
172 min_ms: 0,
173 max_ms: 0,
174 endpoint_latencies: Vec::new(),
175 status: ValidationStatus::Skipped,
176 },
177 ports: PortValidationResult {
178 open_ports: Vec::new(),
179 closed_ports: Vec::new(),
180 status: ValidationStatus::Skipped,
181 },
182 ssl: None,
183 firewall: None,
184 route: None,
185 vpn_detected: false,
186 recommendations: vec!["Check your internet connection".to_string()],
187 overall_status: ValidationStatus::Fail,
188 };
189 }
190
191 let (dns, bandwidth, latency, ports) = futures::join!(
193 self.validate_dns(),
194 self.validate_bandwidth(),
195 self.validate_latency(),
196 self.validate_ports()
197 );
198
199 let ssl = if self.config.check_ssl {
200 Some(self.validate_ssl().await)
201 } else {
202 None
203 };
204
205 let firewall = if self.config.check_firewall {
206 Some(self.validate_firewall().await)
207 } else {
208 None
209 };
210
211 let route = if self.config.perform_traceroute {
212 Some(self.validate_routes().await)
213 } else {
214 None
215 };
216
217 let vpn_detected = self.detect_vpn().await;
218
219 let results = ValidationResults {
220 connectivity: &connectivity,
221 dns: &dns,
222 bandwidth: &bandwidth,
223 latency: &latency,
224 ports: &ports,
225 ssl: &ssl,
226 firewall: &firewall,
227 route: &route,
228 };
229
230 let recommendations = self.generate_recommendations(&results);
231 let overall_status = self.determine_overall_status(&results);
232
233 NetworkValidationResult {
234 connectivity,
235 dns,
236 bandwidth,
237 latency,
238 ports,
239 ssl,
240 firewall,
241 route,
242 vpn_detected,
243 recommendations,
244 overall_status,
245 }
246 }
247
248 async fn validate_connectivity(&self) -> NetworkConnectivityResult {
249 info!("Validating internet connectivity...");
250
251 let internet_check = tokio::spawn(async {
252 let addresses = ["1.1.1.1:443", "8.8.8.8:443", "9.9.9.9:443"];
253 for addr in &addresses {
254 if let Ok(Ok(_)) = timeout(Duration::from_secs(5), TcpStream::connect(addr)).await {
255 return true;
256 }
257 }
258 false
259 })
260 .await
261 .unwrap_or(false);
262
263 let mut endpoints_reachable = Vec::new();
264 for endpoint in &self.config.endpoints {
265 let result = self.is_endpoint_reachable(endpoint).await;
266 endpoints_reachable.push((endpoint.clone(), result));
267 }
268
269 let status = if !internet_check {
270 ValidationStatus::Fail
271 } else if endpoints_reachable.iter().all(|(_, reachable)| *reachable) {
272 ValidationStatus::Pass
273 } else if endpoints_reachable.iter().any(|(_, reachable)| *reachable) {
274 ValidationStatus::Warning
275 } else {
276 ValidationStatus::Fail
277 };
278
279 NetworkConnectivityResult {
280 internet_available: internet_check,
281 endpoints_reachable,
282 status,
283 }
284 }
285
286 async fn is_endpoint_reachable(&self, endpoint: &str) -> bool {
287 if let Ok(url) = url::Url::parse(endpoint) {
288 let host = match url.host_str() {
289 Some(h) => h,
290 None => return false,
291 };
292 let port = url.port().unwrap_or_else(|| match url.scheme() {
293 "https" => 443,
294 "http" => 80,
295 _ => 0,
296 });
297 if port == 0 {
298 return false;
299 }
300 let addr = format!("{host}:{port}");
301 matches!(
302 timeout(Duration::from_secs(5), TcpStream::connect(&addr)).await,
303 Ok(Ok(_))
304 )
305 } else {
306 false
307 }
308 }
309
310 async fn validate_dns(&self) -> DnsValidationResult {
311 info!("Validating DNS resolution...");
312
313 let mut resolvers_available = Vec::new();
314 let mut resolution_times_ms = Vec::new();
315
316 for dns in &self.config.dns_servers {
317 let resolver_ip: IpAddr = match dns.parse() {
318 Ok(ip) => ip,
319 Err(_) => continue,
320 };
321
322 let nameserver_config = NameServerConfig {
323 socket_addr: SocketAddr::new(resolver_ip, 53),
324 protocol: Protocol::Udp,
325 tls_dns_name: None,
326 bind_addr: None,
327 trust_negative_responses: true,
328 };
329
330 let resolver_config = ResolverConfig::from_parts(None, vec![], vec![nameserver_config]);
331
332 let resolver = match Resolver::new(resolver_config, ResolverOpts::default()) {
333 Ok(r) => r,
334 Err(_) => {
335 resolvers_available.push((dns.clone(), false));
336 continue;
337 }
338 };
339
340 let start = Instant::now();
341 let lookup_result = resolver.lookup_ip("google.com");
342 let elapsed = start.elapsed();
343
344 match lookup_result {
345 Ok(_) => {
346 resolvers_available.push((dns.clone(), true));
347 resolution_times_ms.push((dns.clone(), elapsed.as_millis() as u64));
348 }
349 Err(_) => {
350 resolvers_available.push((dns.clone(), false));
351 }
352 }
353 }
354
355 let status = if resolvers_available.iter().all(|(_, available)| !available) {
356 ValidationStatus::Fail
357 } else if resolvers_available.iter().all(|(_, available)| *available) {
358 ValidationStatus::Pass
359 } else {
360 ValidationStatus::Warning
361 };
362
363 DnsValidationResult {
364 resolvers_available,
365 resolution_times_ms,
366 status,
367 }
368 }
369
370 async fn validate_bandwidth(&self) -> BandwidthValidationResult {
371 info!("Validating network bandwidth...");
372
373 let download_start = Instant::now();
374 let download_result =
375 reqwest::get("https://speed.cloudflare.com/__down?bytes=5000000").await;
376
377 let download_mbps = match download_result {
378 Ok(resp) => {
379 if let Ok(bytes) = resp.bytes().await {
380 let elapsed = download_start.elapsed();
381 let bits = bytes.len() as f64 * 8.0;
382 let seconds = elapsed.as_secs_f64();
383 (bits / 1_000_000.0) / seconds
384 } else {
385 0.0
386 }
387 }
388 Err(_) => 0.0,
389 };
390
391 let client = reqwest::Client::new();
392 let upload_bytes = vec![0u8; 1_000_000];
393
394 let upload_start = Instant::now();
395 let upload_result = client
396 .post("https://speed.cloudflare.com/__up")
397 .body(upload_bytes)
398 .send()
399 .await;
400
401 let upload_mbps = match upload_result {
402 Ok(_) => {
403 let elapsed = upload_start.elapsed();
404 let bits = 1_000_000 * 8;
405 let seconds = elapsed.as_secs_f64();
406 (bits as f64 / 1_000_000.0) / seconds
407 }
408 Err(_) => 0.0,
409 };
410
411 let status = if download_mbps < 1.0 || upload_mbps < 0.5 {
412 ValidationStatus::Fail
413 } else if download_mbps < self.config.bandwidth_threshold_mbps {
414 ValidationStatus::Warning
415 } else {
416 ValidationStatus::Pass
417 };
418
419 BandwidthValidationResult {
420 download_mbps,
421 upload_mbps,
422 status,
423 }
424 }
425
426 async fn validate_latency(&self) -> LatencyValidationResult {
427 info!("Validating network latency...");
428
429 let mut endpoint_latencies = Vec::new();
430 let mut total_latency = 0u64;
431 let mut count = 0u64;
432 let mut min_ms = u64::MAX;
433 let mut max_ms = 0u64;
434
435 for endpoint in &self.config.endpoints {
436 if let Ok(url) = url::Url::parse(endpoint) {
437 if let Some(host) = url.host_str() {
438 let latency = self.measure_latency(host).await;
439 if latency > 0 {
440 endpoint_latencies.push((endpoint.clone(), latency));
441 total_latency += latency;
442 count += 1;
443 min_ms = min_ms.min(latency);
444 max_ms = max_ms.max(latency);
445 }
446 }
447 }
448 }
449
450 let average_ms = if count > 0 { total_latency / count } else { 0 };
451
452 let status = if count == 0 {
453 ValidationStatus::Fail
454 } else if average_ms > self.config.latency_threshold_ms {
455 ValidationStatus::Warning
456 } else {
457 ValidationStatus::Pass
458 };
459
460 LatencyValidationResult {
461 average_ms,
462 min_ms: if min_ms == u64::MAX { 0 } else { min_ms },
463 max_ms,
464 endpoint_latencies,
465 status,
466 }
467 }
468
469 async fn measure_latency(&self, host: &str) -> u64 {
470 let mut total_ms = 0u64;
471 let mut successful_pings = 0u64;
472
473 for _ in 0..3 {
474 let start = Instant::now();
475 let result = TcpStream::connect(format!("{host}:443")).await;
476 let elapsed = start.elapsed();
477
478 if result.is_ok() {
479 total_ms += elapsed.as_millis() as u64;
480 successful_pings += 1;
481 }
482 }
483
484 if successful_pings > 0 {
485 total_ms / successful_pings
486 } else {
487 0
488 }
489 }
490
491 pub async fn validate_ports(&self) -> PortValidationResult {
492 info!("Validating required ports with BIP-341 compliance...");
493
494 let mut open_ports = Vec::new();
495 let mut closed_ports = Vec::new();
496
497 let bip341_ports = [8333, 18333, 8433];
498
499 for &port in bip341_ports.iter().chain(&self.config.required_ports) {
500 let is_open = if port == 8433 {
501 self.validate_taproot_port(port).await
502 } else {
503 self.is_port_open("localhost", port).await
504 };
505
506 if is_open {
507 open_ports.push(port);
508 } else {
509 closed_ports.push(port);
510 }
511 }
512
513 let status = if closed_ports.contains(&8333) && closed_ports.contains(&18333) {
514 ValidationStatus::Fail
515 } else if !closed_ports.is_empty() {
516 ValidationStatus::Warning
517 } else {
518 ValidationStatus::Pass
519 };
520
521 PortValidationResult {
522 open_ports,
523 closed_ports,
524 status,
525 }
526 }
527
528 async fn is_port_open(&self, host: &str, port: u16) -> bool {
529 let addr = format!("{host}:{port}");
530 matches!(
531 timeout(Duration::from_secs(3), TcpStream::connect(&addr)).await,
532 Ok(Ok(_))
533 )
534 }
535
536 async fn validate_ssl(&self) -> SslValidationResult {
537 info!("Validating SSL certificates...");
538
539 let mut endpoints_secure = Vec::new();
540 let mut certificate_issues = Vec::new();
541
542 for endpoint in &self.config.endpoints {
543 if let Ok(url) = url::Url::parse(endpoint) {
544 if url.scheme() != "https" {
545 continue;
546 }
547 if let Some(_host) = url.host_str() {
548 let client = reqwest::Client::builder()
549 .danger_accept_invalid_certs(true)
550 .build()
551 .unwrap_or_default();
552
553 let response = client.get(endpoint).send().await;
554
555 match response {
556 Ok(resp) => {
557 let is_secure = resp.status().is_success();
558 endpoints_secure.push((endpoint.clone(), is_secure));
559 if resp.status().is_client_error() {
560 certificate_issues.push((
561 endpoint.clone(),
562 format!("Certificate error: {}", resp.status()),
563 ));
564 }
565 }
566 Err(e) => {
567 endpoints_secure.push((endpoint.clone(), false));
568 certificate_issues
569 .push((endpoint.clone(), format!("Connection error: {e}")));
570 }
571 }
572 }
573 }
574 }
575
576 let status = if endpoints_secure.iter().all(|(_, secure)| !secure) {
577 ValidationStatus::Fail
578 } else if endpoints_secure.iter().all(|(_, secure)| *secure) {
579 ValidationStatus::Pass
580 } else {
581 ValidationStatus::Warning
582 };
583
584 SslValidationResult {
585 endpoints_secure,
586 certificate_issues,
587 status,
588 }
589 }
590
591 async fn validate_firewall(&self) -> FirewallValidationResult {
592 info!("Validating firewall settings...");
593
594 let firewall_detected = self.detect_firewall().await;
595 let blocks_bitcoin = self.check_firewall_blocks_bitcoin().await;
596 let blocks_required_ports = self.check_blocked_ports().await;
597
598 let status = if blocks_bitcoin {
599 ValidationStatus::Fail
600 } else if !blocks_required_ports.is_empty() {
601 ValidationStatus::Warning
602 } else {
603 ValidationStatus::Pass
604 };
605
606 FirewallValidationResult {
607 detected: firewall_detected,
608 blocks_bitcoin,
609 blocks_required_ports,
610 status,
611 }
612 }
613
614 async fn detect_firewall(&self) -> bool {
615 #[cfg(target_os = "windows")]
616 {
617 let output = Command::new("netsh")
618 .args(&["advfirewall", "show", "currentprofile"])
619 .output();
620
621 match output {
622 Ok(output) => {
623 let stdout = String::from_utf8_lossy(&output.stdout);
624 stdout.contains("State ON")
625 }
626 Err(_) => false,
627 }
628 }
629
630 #[cfg(target_os = "linux")]
631 {
632 let output = Command::new("sudo").args(["iptables", "-L"]).output();
633
634 match output {
635 Ok(output) => {
636 let stdout = String::from_utf8_lossy(&output.stdout);
637 !stdout.trim().is_empty() && !stdout.contains("No rules")
638 }
639 Err(_) => false,
640 }
641 }
642
643 #[cfg(target_os = "macos")]
644 {
645 let output = Command::new("defaults")
646 .args(&["read", "/Library/Preferences/com.apple.alf", "globalstate"])
647 .output();
648
649 match output {
650 Ok(output) => {
651 let stdout = String::from_utf8_lossy(&output.stdout);
652 stdout.trim() == "1" || stdout.trim() == "2"
653 }
654 Err(_) => false,
655 }
656 }
657
658 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
659 {
660 false
661 }
662 }
663
664 async fn check_firewall_blocks_bitcoin(&self) -> bool {
665 !(self.is_port_open("bitcoin.org", 8333).await
666 || self.is_port_open("bitcoin.org", 18333).await
667 || self.is_port_open("bitcoin.org", 8433).await)
668 }
669
670 async fn check_blocked_ports(&self) -> Vec<u16> {
671 let mut blocked_ports = Vec::new();
672 for &port in &self.config.required_ports {
673 if !self.is_port_open("example.com", port).await {
674 blocked_ports.push(port);
675 }
676 }
677 blocked_ports
678 }
679
680 async fn validate_routes(&self) -> RouteValidationResult {
681 info!("Validating network routes...");
682
683 let mut routes = Vec::new();
684 let mut total_hops = 0u32;
685 let mut problematic_hops = Vec::new();
686 let mut route_count = 0u32;
687
688 for endpoint in &self.config.endpoints {
689 if let Ok(url) = url::Url::parse(endpoint) {
690 if let Some(host) = url.host_str() {
691 let (route_hops, route_problems) = self.trace_route(host).await;
692 if !route_hops.is_empty() {
693 total_hops += route_hops.len() as u32;
694 route_count += 1;
695 routes.push((host.to_string(), route_hops));
696 problematic_hops.extend(route_problems);
697 }
698 }
699 }
700 }
701
702 let average_hops = if route_count > 0 {
703 total_hops / route_count
704 } else {
705 0
706 };
707
708 let status = if route_count == 0 {
709 ValidationStatus::Fail
710 } else if !problematic_hops.is_empty() {
711 ValidationStatus::Warning
712 } else {
713 ValidationStatus::Pass
714 };
715
716 RouteValidationResult {
717 average_hops,
718 problematic_hops,
719 routes,
720 status,
721 }
722 }
723
724 async fn trace_route(&self, host: &str) -> (Vec<String>, Vec<String>) {
725 let mut hops = Vec::new();
726 let mut problematic = Vec::new();
727
728 #[cfg(any(target_os = "linux", target_os = "macos"))]
729 {
730 let output = Command::new("traceroute").args(["-m", "15", host]).output();
731
732 if let Ok(output) = output {
733 let stdout = String::from_utf8_lossy(&output.stdout);
734 for line in stdout.lines().skip(1) {
735 if line.contains("* * *") {
736 problematic.push(format!("Hop timeout: {line}"));
737 } else {
738 hops.push(line.to_string());
739 }
740 }
741 }
742 }
743
744 #[cfg(target_os = "windows")]
745 {
746 let output = Command::new("tracert").args(&["-h", "15", host]).output();
747
748 match output {
749 Ok(output) => {
750 let stdout = String::from_utf8_lossy(&output.stdout);
751 for line in stdout.lines().skip(4) {
752 if line.contains("*") {
753 problematic.push(format!("Hop timeout: {}", line));
754 } else if !line.trim().is_empty() {
755 hops.push(line.to_string());
756 }
757 }
758 }
759 Err(_) => (),
760 }
761 }
762
763 (hops, problematic)
764 }
765
766 async fn detect_vpn(&self) -> bool {
767 #[cfg(target_os = "windows")]
768 {
769 let output = Command::new("netsh")
770 .args(&["interface", "show", "interface"])
771 .output();
772
773 match output {
774 Ok(output) => {
775 let stdout = String::from_utf8_lossy(&output.stdout);
776 stdout.contains("PPP") || stdout.contains("VPN") || stdout.contains("Tunnel")
777 }
778 Err(_) => false,
779 }
780 }
781
782 #[cfg(target_os = "linux")]
783 {
784 let output = Command::new("ip").args(["tuntap", "list"]).output();
785
786 match output {
787 Ok(output) => {
788 let stdout = String::from_utf8_lossy(&output.stdout);
789 !stdout.trim().is_empty()
790 }
791 Err(_) => false,
792 }
793 }
794
795 #[cfg(target_os = "macos")]
796 {
797 let output = Command::new("networksetup")
798 .args(&["-listallnetworkservices"])
799 .output();
800
801 match output {
802 Ok(output) => {
803 let stdout = String::from_utf8_lossy(&output.stdout);
804 stdout.contains("VPN")
805 || stdout.contains("Cisco")
806 || stdout.contains("Global Protect")
807 }
808 Err(_) => false,
809 }
810 }
811
812 #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
813 {
814 false
815 }
816 }
817
818 fn generate_recommendations(&self, results: &ValidationResults) -> Vec<String> {
819 let mut recommendations = Vec::new();
820
821 if !results.connectivity.internet_available {
822 recommendations
823 .push("Check your internet connection, it appears to be offline".to_string());
824 } else if !results
825 .connectivity
826 .endpoints_reachable
827 .iter()
828 .all(|(_, reachable)| *reachable)
829 {
830 recommendations.push(
831 "Some Bitcoin RPC endpoints are unreachable, check your network configuration"
832 .to_string(),
833 );
834 }
835
836 if results.dns.status == ValidationStatus::Fail {
837 recommendations.push("DNS resolution is failing, check your DNS servers or try using alternative DNS like 1.1.1.1 or 8.8.8.8".to_string());
838 } else if results.dns.status == ValidationStatus::Warning {
839 recommendations.push(
840 "Some DNS servers are not responding, consider using more reliable DNS servers"
841 .to_string(),
842 );
843 }
844
845 if results.bandwidth.status == ValidationStatus::Fail {
846 recommendations.push(format!(
847 "Network bandwidth is too low (Download: {:.2} Mbps, Upload: {:.2} Mbps). Minimum requirements are 1 Mbps download and 0.5 Mbps upload",
848 results.bandwidth.download_mbps, results.bandwidth.upload_mbps
849 ));
850 } else if results.bandwidth.status == ValidationStatus::Warning {
851 recommendations.push(format!(
852 "Network bandwidth is below recommended levels (Download: {:.2} Mbps). For optimal performance, {:.2} Mbps is recommended",
853 results.bandwidth.download_mbps, self.config.bandwidth_threshold_mbps
854 ));
855 }
856
857 if results.latency.status == ValidationStatus::Fail {
858 recommendations.push(
859 "Network latency could not be measured, your network connection may be unstable"
860 .to_string(),
861 );
862 } else if results.latency.status == ValidationStatus::Warning {
863 recommendations.push(format!(
864 "Network latency is high (Average: {} ms). For optimal performance, latency should be below {} ms",
865 results.latency.average_ms, self.config.latency_threshold_ms
866 ));
867 }
868
869 if !results.ports.closed_ports.is_empty() {
870 recommendations.push(format!(
871 "The following required ports are closed or blocked: {}. Consider opening these ports in your firewall",
872 results.ports.closed_ports.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", ")
873 ));
874 }
875
876 if let Some(ssl_result) = &results.ssl {
877 if ssl_result.status == ValidationStatus::Fail {
878 recommendations.push("SSL certificate validation failed for all endpoints. Check your system's certificate store".to_string());
879 } else if ssl_result.status == ValidationStatus::Warning {
880 recommendations.push("Some SSL certificates could not be validated. Your system may be missing necessary root certificates".to_string());
881 }
882 }
883
884 if let Some(firewall_result) = &results.firewall {
885 if firewall_result.blocks_bitcoin {
886 recommendations.push("Your firewall appears to be blocking Bitcoin network traffic. Configure your firewall to allow ports 8333 (mainnet) and 18333 (testnet)".to_string());
887 } else if !firewall_result.blocks_required_ports.is_empty() {
888 recommendations.push(format!(
889 "Your firewall is blocking these required ports: {}. Configure your firewall to allow these ports",
890 firewall_result.blocks_required_ports.iter().map(|p| p.to_string()).collect::<Vec<_>>().join(", ")
891 ));
892 }
893 if firewall_result.blocks_required_ports.contains(&174) {
894 recommendations.push(
895 "PSBT (BIP-174) port 174 is blocked. Required for partial transactions"
896 .to_string(),
897 );
898 }
899 }
900
901 if let Some(route_result) = &results.route {
902 if route_result.status == ValidationStatus::Fail {
903 recommendations.push(
904 "Network route tracing failed. Your network may be blocking ICMP traffic"
905 .to_string(),
906 );
907 } else if route_result.status == ValidationStatus::Warning {
908 recommendations.push(
909 "Network routes contain problematic hops, which may cause connection issues"
910 .to_string(),
911 );
912 }
913 }
914
915 recommendations
916 }
917
918 fn determine_overall_status(&self, results: &ValidationResults) -> ValidationStatus {
919 if results.connectivity.status == ValidationStatus::Fail
920 || results.dns.status == ValidationStatus::Fail
921 || results.bandwidth.status == ValidationStatus::Fail
922 {
923 return ValidationStatus::Fail;
924 }
925
926 if let Some(firewall_result) = results.firewall {
927 if firewall_result.blocks_bitcoin {
928 return ValidationStatus::Fail;
929 }
930 }
931
932 let has_warnings = results.connectivity.status == ValidationStatus::Warning
933 || results.dns.status == ValidationStatus::Warning
934 || results.bandwidth.status == ValidationStatus::Warning
935 || results.latency.status == ValidationStatus::Warning
936 || results.ports.status == ValidationStatus::Warning;
937
938 let optional_warnings = results
939 .ssl
940 .as_ref()
941 .map_or(false, |r| r.status == ValidationStatus::Warning)
942 || results
943 .firewall
944 .as_ref()
945 .map_or(false, |r| r.status == ValidationStatus::Warning)
946 || results
947 .route
948 .as_ref()
949 .map_or(false, |r| r.status == ValidationStatus::Warning);
950
951 if has_warnings || optional_warnings {
952 ValidationStatus::Warning
953 } else {
954 ValidationStatus::Pass
955 }
956 }
957
958 async fn validate_taproot_port(&self, _port: u16) -> bool {
959 let taproot_check = Command::new("bitcoin-cli")
960 .args(["getnetworkinfo"])
961 .output();
962
963 match taproot_check {
964 Ok(output) => {
965 let network_info = String::from_utf8_lossy(&output.stdout);
966 network_info.contains("\"taproot_active\": true")
967 }
968 Err(_) => false,
969 }
970 }
971}