anya_core/network/
validation.rs

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/// Network validation configuration
12#[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/// Network validation result
45#[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
60/// Group validation results into a single struct to avoid too many arguments
61pub(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// Various result structs for different aspects of network validation
81#[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
141/// Network validator
142pub struct NetworkValidator {
143    config: NetworkValidationConfig,
144}
145
146impl NetworkValidator {
147    pub fn new(config: NetworkValidationConfig) -> Self {
148        Self { config }
149    }
150
151    /// Run comprehensive network validation
152    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        // Run DNS, bandwidth, latency, and ports checks concurrently for better performance
192        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}