@@ -9686,14 +9686,45 @@ impl SimplifiedP2P {
96869686 println!("[DBG][P2P] regional_added peer={}", peer.id);
96879687 }
96889688
9689- // v10.1: Query real height from peer immediately on connect.
9690- // Don't wait 30s for first heartbeat — get height NOW via REST API.
9691- // Dedup: only query if BEST_PEER_HEIGHT is still 0 (first connect)
9692- // or significantly stale (prevents spam on repeated reconnections).
9689+ // v2.95: Query height via QUIC HealthPing (UDP, firewall-friendly)
9690+ // Falls back to HTTP if QUIC is not available yet.
96939691 let should_query = BEST_PEER_HEIGHT.load(std::sync::atomic::Ordering::Relaxed) == 0;
96949692 let health_addr = peer.addr.clone();
96959693 if should_query { tokio::spawn(async move {
96969694 let ip = health_addr.split(':').next().unwrap_or("");
9695+
9696+ // Try QUIC HealthPing first (works through firewalls)
9697+ let quic_success = {
9698+ use crate::quic_transport::QUIC_PORT_OFFSET;
9699+ let quic_port: u16 = 8001 + QUIC_PORT_OFFSET;
9700+ if let Ok(quic_addr) = format!("{}:{}", ip, quic_port).parse::<std::net::SocketAddr>() {
9701+ let transport_arc_opt = GLOBAL_QUIC_TRANSPORT.read().ok()
9702+ .and_then(|g| g.as_ref().map(|a| a.clone()));
9703+ if let Some(transport_arc) = transport_arc_opt {
9704+ let transport = transport_arc.read().await;
9705+ let ping = NetworkMessage::HealthPing {
9706+ from: GLOBAL_NODE_ID.read().map(|g| g.clone()).unwrap_or_default(),
9707+ timestamp: std::time::SystemTime::now()
9708+ .duration_since(std::time::UNIX_EPOCH)
9709+ .unwrap_or_default()
9710+ .as_secs(),
9711+ height: LOCAL_BLOCKCHAIN_HEIGHT.load(std::sync::atomic::Ordering::Relaxed),
9712+ signature: String::new(),
9713+ public_key: String::new(),
9714+ };
9715+ transport.send_message(quic_addr, &ping).await.is_ok()
9716+ } else { false }
9717+ } else { false }
9718+ };
9719+
9720+ if quic_success {
9721+ if crate::node::is_info() {
9722+ println!("[INFO][P2P] peer_height_ping sent via QUIC to {}", ip);
9723+ }
9724+ return; // Height will arrive via HealthPing response
9725+ }
9726+
9727+ // Fallback: HTTP query
96979728 let url = format!("http://{}:8001/api/v1/node/health", ip);
96989729 match HTTP_CLIENT.get(&url).send().await {
96999730 Ok(resp) => {
@@ -9702,17 +9733,13 @@ impl SimplifiedP2P {
97029733 if h > 0 {
97039734 BEST_PEER_HEIGHT.fetch_max(h, std::sync::atomic::Ordering::Relaxed);
97049735 if crate::node::is_info() {
9705- println!("[INFO][P2P] peer_height_query {}={}", ip, h);
9736+ println!("[INFO][P2P] peer_height_query {}={} via HTTP ", ip, h);
97069737 }
97079738 }
97089739 }
97099740 }
97109741 }
9711- Err(e) => {
9712- if crate::node::is_debug() {
9713- println!("[DBG][P2P] peer_height_query_fail {}={}", ip, e);
9714- }
9715- }
9742+ Err(_) => {}
97169743 }
97179744 }); } // end if should_query
97189745 }
@@ -10097,23 +10124,58 @@ impl SimplifiedP2P {
1009710124 /// Previous version (5s + check_api_readiness_static with 24s worst-case) caused
1009810125 /// cascading API deadlocks across the entire network.
1009910126 fn test_peer_connectivity_static(peer_addr: &str) -> bool {
10100- use std::net::{TcpStream, SocketAddr};
10127+ use std::net::{TcpStream, SocketAddr, UdpSocket };
1010110128 use std::time::Duration;
10102-
10129+
1010310130 let ip = peer_addr.split(':').next().unwrap_or("");
10104- let addr = format!("{}:8001", ip);
10105-
10106- if let Ok(socket_addr) = addr.parse::<SocketAddr>() {
10107- // v4.2: Single attempt, strict 2-second timeout. No retries.
10108- // For international servers: 200-500ms latency + 100-300ms jitter = 800ms max.
10109- // 2s provides sufficient margin without blocking the runtime.
10131+
10132+ // v2.95: Try QUIC (UDP) first — works even when TCP ports are blocked by firewalls.
10133+ // This is critical for node updates: TCP connections break when containers restart,
10134+ // and many hosting providers (Contabo, etc.) block non-standard TCP ports.
10135+ // UDP/QUIC is rarely blocked, so this ensures peer discovery always works.
10136+ let quic_port: u16 = 10876; // Fixed QUIC port (API port 8001 + QUIC_PORT_OFFSET 2875)
10137+ let quic_addr = format!("{}:{}", ip, quic_port);
10138+ if let Ok(quic_socket_addr) = quic_addr.parse::<SocketAddr>() {
10139+ // Check if QUIC transport already has a connection to this peer
10140+ if let Ok(guard) = GLOBAL_QUIC_TRANSPORT.read() {
10141+ if let Some(ref transport_arc) = *guard {
10142+ // Use try_read to avoid blocking on async RwLock from sync context
10143+ if let Ok(transport) = transport_arc.try_read() {
10144+ if transport.is_connected(&quic_socket_addr) {
10145+ if crate::node::is_info() {
10146+ println!("[INFO][P2P] peer_connected via=QUIC addr={}", get_privacy_id_for_addr(peer_addr));
10147+ }
10148+ return true;
10149+ }
10150+ }
10151+ }
10152+ }
10153+
10154+ // UDP probe: send a small packet to check reachability (no response needed)
10155+ // If the UDP socket can send without error, the peer's network is reachable
10156+ if let Ok(socket) = UdpSocket::bind("0.0.0.0:0") {
10157+ socket.set_write_timeout(Some(Duration::from_secs(2))).ok();
10158+ if socket.send_to(b"QNET_PROBE", quic_socket_addr).is_ok() {
10159+ // UDP send succeeded — peer network is reachable.
10160+ // The actual QUIC handshake will happen when we try to communicate.
10161+ if crate::node::is_info() {
10162+ println!("[INFO][P2P] peer_reachable via=UDP addr={}", get_privacy_id_for_addr(peer_addr));
10163+ }
10164+ return true;
10165+ }
10166+ }
10167+ }
10168+
10169+ // Fallback: TCP check on port 8001 (original behavior)
10170+ let tcp_addr = format!("{}:8001", ip);
10171+ if let Ok(socket_addr) = tcp_addr.parse::<SocketAddr>() {
1011010172 match TcpStream::connect_timeout(&socket_addr, Duration::from_secs(2)) {
10111- Ok(_) => true,
10112- Err(_) => false,
10173+ Ok(_) => return true,
10174+ Err(_) => {}
1011310175 }
10114- } else {
10115- false
1011610176 }
10177+
10178+ false
1011710179 }
1011810180
1011910181 /// Query peer metrics - now returns placeholder as metrics come from QUIC stats
@@ -11134,6 +11196,18 @@ pub enum NetworkMessage {
1113411196 sender_id: String,
1113511197 },
1113611198
11199+ /// v2.95: QUIC-based peer list exchange (replaces HTTP GET /api/v1/peers)
11200+ /// Enables peer discovery when TCP ports are blocked by firewalls
11201+ PeerListRequest {
11202+ requester_id: String,
11203+ },
11204+
11205+ /// v2.95: Response with peer list via QUIC
11206+ PeerListResponse {
11207+ peers: Vec<(String, String, u64)>, // (addr, node_id, height)
11208+ sender_id: String,
11209+ },
11210+
1113711211 /// Request consensus state for recovery
1113811212 RequestConsensusState {
1113911213 round: u64,
@@ -12307,10 +12381,52 @@ impl SimplifiedP2P {
1230712381 }
1230812382 }
1230912383
12384+ NetworkMessage::PeerListRequest { requester_id } => {
12385+ // v2.95: Handle QUIC-based peer list request (replaces HTTP /api/v1/peers)
12386+ if crate::node::is_info() {
12387+ println!("[P2P] 📥 PeerListRequest from {} via QUIC", requester_id);
12388+ }
12389+ let peers: Vec<(String, String, u64)> = self.connected_peers_lockfree.iter()
12390+ .map(|e| {
12391+ let p = e.value();
12392+ (p.addr.clone(), p.id.clone(), p.last_block_height)
12393+ })
12394+ .collect();
12395+ let response = NetworkMessage::PeerListResponse {
12396+ peers,
12397+ sender_id: self.node_id.clone(),
12398+ };
12399+ self.send_network_message(from_peer, response);
12400+ }
12401+
12402+ NetworkMessage::PeerListResponse { peers, sender_id } => {
12403+ // v2.95: Handle QUIC-based peer list response
12404+ if crate::node::is_info() {
12405+ println!("[P2P] 📡 PeerListResponse from {}: {} peers via QUIC", sender_id, peers.len());
12406+ }
12407+ // Store received peer info for peer exchange processing
12408+ // Peers will be validated and added by the peer exchange cycle
12409+ for (addr, peer_id, height) in &peers {
12410+ if peer_id != &self.node_id && !addr.is_empty() {
12411+ if let Ok(mut peer_info) = Self::parse_peer_address_static(addr) {
12412+ peer_info.id = peer_id.clone();
12413+ peer_info.last_block_height = *height;
12414+ // Only add if not already known
12415+ if !self.connected_peers_lockfree.contains_key(addr) {
12416+ let ip = addr.split(':').next().unwrap_or("");
12417+ if is_genesis_node_ip(ip) || !std::env::var("QNET_BOOTSTRAP_ID").is_ok() {
12418+ self.connected_peers_lockfree.insert(addr.clone(), peer_info);
12419+ }
12420+ }
12421+ }
12422+ }
12423+ }
12424+ }
12425+
1231012426 NetworkMessage::RequestBlocks { from_height, to_height, requester_id } => {
1231112427 // Handle block request for sync
1231212428 if crate::node::is_info() {
12313- println!("[SYNC] 📥 Received block request from {} for heights {}-{}",
12429+ println!("[SYNC] 📥 Received block request from {} for heights {}-{}",
1231412430 requester_id, from_height, to_height);
1231512431 }
1231612432 self.handle_block_request(from_peer, from_height, to_height, requester_id);
@@ -18648,19 +18764,30 @@ impl SimplifiedP2P {
1864818764 }
1864918765
1865018766 /// Request peer list from a connected node for decentralized discovery
18767+ /// v2.95: QUIC-first with HTTP fallback — works even when TCP ports are blocked
1865118768 async fn request_peer_list_from_node(node_addr: &str) -> Result<Vec<PeerInfo>, String> {
18652- use reqwest;
1865318769 use std::time::Duration;
18654-
18655- // CRITICAL FIX: Use existing working query_node_for_peers logic
18656- // Make actual HTTP request to /api/v1/peers endpoint
18770+
1865718771 let ip = node_addr.split(':').next().unwrap_or(node_addr);
18658- let endpoint = format!("http://{}:8001/api/v1/peers", ip);
18659-
18772+
1866018773 if crate::node::is_info() {
18661- println!("[P2P] 📞 Requesting peer list from {}", get_privacy_id_for_addr(& ip));
18774+ println!("[P2P] 📞 Requesting peer list from {}", get_privacy_id_for_addr(ip));
1866218775 }
18663-
18776+
18777+ // === Phase 1: Try QUIC (UDP) — bypasses TCP firewall blocks ===
18778+ let quic_result = Self::request_peer_list_via_quic(ip).await;
18779+ if let Ok(ref peers) = quic_result {
18780+ if !peers.is_empty() {
18781+ if crate::node::is_info() {
18782+ println!("[P2P] ✅ Got {} peers from {} via QUIC", peers.len(), get_privacy_id_for_addr(ip));
18783+ }
18784+ return quic_result;
18785+ }
18786+ }
18787+
18788+ // === Phase 2: Fallback to HTTP (original behavior) ===
18789+ let endpoint = format!("http://{}:8001/api/v1/peers", ip);
18790+
1866418791 let client = reqwest::Client::builder()
1866518792 .timeout(Duration::from_secs(10))
1866618793 .connect_timeout(Duration::from_secs(5))
@@ -18670,63 +18797,37 @@ impl SimplifiedP2P {
1867018797 .pool_idle_timeout(Duration::from_secs(HTTP_POOL_IDLE_TIMEOUT_SECS))
1867118798 .build()
1867218799 .map_err(|e| format!("HTTP client error: {}", e))?;
18673-
18800+
1867418801 match client.get(&endpoint).send().await {
1867518802 Ok(response) if response.status().is_success() => {
1867618803 match response.text().await {
1867718804 Ok(text) => {
18678- if crate::node::is_info() {
18679- println!("[P2P] ✅ Received peer data from {}: {} bytes", get_privacy_id_for_addr(node_addr), text.len());
18680- }
18681-
18682- // Parse JSON response from /api/v1/peers endpoint
1868318805 if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(&text) {
1868418806 if let Some(peers_array) = json_value.get("peers").and_then(|p| p.as_array()) {
1868518807 let mut peer_list = Vec::new();
18686-
1868718808 for peer_json in peers_array {
1868818809 if let Some(address) = peer_json.get("address").and_then(|a| a.as_str()) {
18689- // FIXED: Use EXISTING parse_peer_address_static method - no default values!
1869018810 let peer_addr = if address.contains(':') { address.to_string() } else { format!("{}:8001", address) };
18691-
18692- // Use static version of parse_peer_address (compatible with async context)
1869318811 if let Ok(peer_info) = Self::parse_peer_address_static(&peer_addr) {
1869418812 peer_list.push(peer_info);
1869518813 }
1869618814 }
1869718815 }
18698-
1869918816 if crate::node::is_info() {
18700- println!("[P2P] 📡 Parsed {} peers from {}", peer_list.len(), get_privacy_id_for_addr(node_addr));
18817+ println!("[P2P] 📡 Parsed {} peers from {} via HTTP ", peer_list.len(), get_privacy_id_for_addr(node_addr));
1870118818 }
1870218819 Ok(peer_list)
1870318820 } else {
18704- if crate::node::is_info() {
18705- println!("[P2P] ⚠️ No 'peers' array in response from {}", get_privacy_id_for_addr(node_addr));
18706- }
1870718821 Ok(Vec::new())
1870818822 }
1870918823 } else {
18710- if crate::node::is_info() {
18711- println!("[P2P] ⚠️ Failed to parse JSON response from {}", get_privacy_id_for_addr(node_addr));
18712- }
1871318824 Ok(Vec::new())
1871418825 }
1871518826 }
18716- Err(e) => {
18717- if crate::node::is_info() {
18718- println!("[P2P] ❌ Failed to read response from {}: {}", get_privacy_id_for_addr(node_addr), e);
18719- }
18720- Err(format!("Response read error: {}", e))
18721- }
18827+ Err(e) => Err(format!("Response read error: {}", e)),
1872218828 }
1872318829 }
18724- Ok(response) => {
18725- if crate::node::is_info() {
18726- println!("[P2P] ❌ HTTP error from {}: {}", get_privacy_id_for_addr(node_addr), response.status());
18727- }
18728- Err(format!("HTTP error: {}", response.status()))
18729- }
18830+ Ok(response) => Err(format!("HTTP error: {}", response.status())),
1873018831 Err(e) => {
1873118832 if crate::node::is_info() {
1873218833 println!("[P2P] ❌ Request failed to {}: {}", get_privacy_id_for_addr(node_addr), e);
@@ -18735,6 +18836,43 @@ impl SimplifiedP2P {
1873518836 }
1873618837 }
1873718838 }
18839+
18840+ /// v2.95: Request peer list via QUIC transport (UDP-based, firewall-friendly)
18841+ async fn request_peer_list_via_quic(ip: &str) -> Result<Vec<PeerInfo>, String> {
18842+ use crate::quic_transport::QUIC_PORT_OFFSET;
18843+ use std::net::SocketAddr;
18844+
18845+ let api_port: u16 = 8001;
18846+ let quic_port = api_port + QUIC_PORT_OFFSET;
18847+ let quic_addr: SocketAddr = format!("{}:{}", ip, quic_port)
18848+ .parse()
18849+ .map_err(|e| format!("Invalid addr: {}", e))?;
18850+
18851+ let transport_arc = {
18852+ let guard = GLOBAL_QUIC_TRANSPORT.read()
18853+ .map_err(|e| format!("QUIC lock: {}", e))?;
18854+ match &*guard {
18855+ Some(arc) => arc.clone(),
18856+ None => return Err("QUIC not initialized".to_string()),
18857+ }
18858+ };
18859+
18860+ let request = NetworkMessage::PeerListRequest {
18861+ requester_id: match GLOBAL_NODE_ID.read() {
18862+ Ok(g) => g.clone(),
18863+ Err(_) => String::new(),
18864+ },
18865+ };
18866+
18867+ let transport = transport_arc.read().await;
18868+ transport.send_message(quic_addr, &request).await
18869+ .map_err(|e| format!("QUIC send: {}", e))?;
18870+
18871+ // PeerListResponse will arrive asynchronously via message handler
18872+ // and peers will be added to connected_peers_lockfree directly.
18873+ // Return empty OK to indicate QUIC request was sent successfully.
18874+ Ok(Vec::new())
18875+ }
1873818876
1873918877 /// PRODUCTION: Get shared reputation system for consensus integration
1874018878 #[deprecated(note = "Use get_node_reputation_from_blockchain() instead")]
0 commit comments