peer_addr.rs (7784B)
1 use std::{ 2 io::ErrorKind, 3 net::{IpAddr, SocketAddr}, 4 time::Duration, 5 }; 6 7 use anyhow::{Context, Result}; 8 9 use crate::app::GossipEnvelope; 10 11 pub(super) fn next_reconnect_delay(current: Duration, max_delay: Duration) -> Duration { 12 (current * 2).min(max_delay) 13 } 14 15 pub(super) fn peer_needs_snapshot(peer_height: u64, envelopes: &[GossipEnvelope]) -> bool { 16 envelopes 17 .iter() 18 .filter_map(|envelope| match envelope { 19 GossipEnvelope::Block(block) => Some(block.height), 20 GossipEnvelope::Inventory { blocks, .. } => { 21 blocks.iter().map(|block| block.height).min() 22 } 23 _ => None, 24 }) 25 .min() 26 .is_some_and(|first_block_height| peer_height + 1 < first_block_height) 27 } 28 29 pub(super) fn reachable_advertised_addr( 30 advertised_addr: SocketAddr, 31 remote_addr: SocketAddr, 32 ) -> SocketAddr { 33 let mut reachable_addr = advertised_addr; 34 if reachable_addr.ip().is_unspecified() { 35 reachable_addr.set_ip(remote_addr.ip()); 36 } 37 reachable_addr 38 } 39 40 pub(super) fn normalize_advertised_peer(address: &str, remote_addr: SocketAddr) -> Result<String> { 41 let advertised_addr = address 42 .parse::<SocketAddr>() 43 .with_context(|| format!("invalid announced peer address {address}"))?; 44 Ok(reachable_advertised_addr(advertised_addr, remote_addr).to_string()) 45 } 46 47 pub(super) fn peer_list_address_is_discoverable( 48 address: &str, 49 remote_addr: SocketAddr, 50 ) -> Result<bool> { 51 let candidate = address 52 .parse::<SocketAddr>() 53 .with_context(|| format!("invalid peer-list address {address}"))?; 54 Ok(socket_addr_is_discoverable(candidate, remote_addr)) 55 } 56 57 pub(super) fn advertised_peer_is_discoverable( 58 address: &str, 59 remote_addr: SocketAddr, 60 ) -> Result<bool> { 61 let candidate = address 62 .parse::<SocketAddr>() 63 .with_context(|| format!("invalid announced peer address {address}"))?; 64 Ok(socket_addr_is_discoverable(candidate, remote_addr)) 65 } 66 67 fn socket_addr_is_discoverable(candidate: SocketAddr, remote_addr: SocketAddr) -> bool { 68 if candidate.ip().is_loopback() { 69 return remote_addr.ip().is_loopback(); 70 } 71 ip_is_publicly_discoverable(candidate.ip()) 72 } 73 74 fn ip_is_publicly_discoverable(ip: IpAddr) -> bool { 75 match ip { 76 IpAddr::V4(ip) => { 77 let [a, b, c, d] = ip.octets(); 78 !(a == 0 79 || a == 10 80 || a == 127 81 || (a == 100 && (64..=127).contains(&b)) 82 || (a == 169 && b == 254) 83 || (a == 172 && (16..=31).contains(&b)) 84 || (a == 192 && b == 168) 85 || (a == 192 && b == 0 && c == 2) 86 || (a == 198 && b == 51 && c == 100) 87 || (a == 203 && b == 0 && c == 113) 88 || a >= 224 89 || [a, b, c, d] == [255, 255, 255, 255]) 90 } 91 IpAddr::V6(ip) => { 92 let segments = ip.segments(); 93 !(ip.is_unspecified() 94 || ip.is_loopback() 95 || (segments[0] & 0xfe00) == 0xfc00 96 || (segments[0] & 0xffc0) == 0xfe80 97 || (segments[0] & 0xff00) == 0xff00) 98 } 99 } 100 } 101 102 pub(super) fn is_self_peer_address_for( 103 address: &str, 104 listen_addr: SocketAddr, 105 advertised_addr: Option<SocketAddr>, 106 ) -> bool { 107 address.parse::<SocketAddr>().is_ok_and(|candidate| { 108 is_self_socket_addr(candidate, listen_addr) 109 || advertised_addr.is_some_and(|addr| is_self_socket_addr(candidate, addr)) 110 }) 111 } 112 113 fn is_self_socket_addr(candidate: SocketAddr, listen_addr: SocketAddr) -> bool { 114 if candidate == listen_addr { 115 return true; 116 } 117 if candidate.port() != listen_addr.port() { 118 return false; 119 } 120 121 let candidate_ip = candidate.ip(); 122 let listen_ip = listen_addr.ip(); 123 if listen_ip.is_unspecified() { 124 return candidate_ip.is_unspecified() || candidate_ip.is_loopback(); 125 } 126 if candidate_ip.is_unspecified() { 127 return listen_ip.is_loopback(); 128 } 129 false 130 } 131 132 pub(super) fn is_quiet_disconnect(error: &anyhow::Error) -> bool { 133 error.chain().any(|cause| { 134 cause.downcast_ref::<std::io::Error>().is_some_and(|error| { 135 matches!( 136 error.kind(), 137 ErrorKind::ConnectionReset 138 | ErrorKind::BrokenPipe 139 | ErrorKind::UnexpectedEof 140 | ErrorKind::ConnectionAborted 141 ) 142 }) 143 }) 144 } 145 146 pub(super) fn is_possible_fork_error(error: &anyhow::Error) -> bool { 147 let message = format!("{error:#}"); 148 message.contains("does not extend local tip") 149 || message.contains("conflicts with local chain") 150 || message.contains("expected block height") 151 } 152 153 pub(super) fn inbound_error_counts_as_misbehavior(message: &str) -> bool { 154 !message.contains("block timestamp is too far in the future") 155 && !message.contains("block timestamp is before finalizer rank") 156 } 157 158 #[cfg(test)] 159 mod tests { 160 use std::net::SocketAddr; 161 162 use crate::{ 163 app::GossipEnvelope, 164 domain::{Block, FinalizerMode, RevealBundleSection}, 165 }; 166 167 use super::{is_self_peer_address_for, peer_needs_snapshot, reachable_advertised_addr}; 168 169 #[test] 170 fn unspecified_announced_ip_uses_remote_ip_with_announced_port() { 171 let advertised: SocketAddr = "0.0.0.0:9445".parse().unwrap(); 172 let remote: SocketAddr = "203.0.113.10:52144".parse().unwrap(); 173 174 assert_eq!( 175 reachable_advertised_addr(advertised, remote).to_string(), 176 "203.0.113.10:9445" 177 ); 178 } 179 180 #[test] 181 fn explicit_announced_ip_is_kept() { 182 let advertised: SocketAddr = "127.0.0.1:9445".parse().unwrap(); 183 let remote: SocketAddr = "127.0.0.1:52144".parse().unwrap(); 184 185 assert_eq!( 186 reachable_advertised_addr(advertised, remote).to_string(), 187 "127.0.0.1:9445" 188 ); 189 } 190 191 #[test] 192 fn loopback_peer_on_unspecified_listen_port_is_self() { 193 let listen_addr: SocketAddr = "0.0.0.0:9545".parse().unwrap(); 194 195 assert!(is_self_peer_address_for( 196 "127.0.0.1:9545", 197 listen_addr, 198 Some(listen_addr) 199 )); 200 assert!(is_self_peer_address_for( 201 "0.0.0.0:9545", 202 listen_addr, 203 Some(listen_addr) 204 )); 205 assert!(!is_self_peer_address_for( 206 "127.0.0.1:9546", 207 listen_addr, 208 Some(listen_addr) 209 )); 210 assert!(!is_self_peer_address_for( 211 "203.0.113.10:9545", 212 listen_addr, 213 Some(listen_addr) 214 )); 215 } 216 217 #[test] 218 fn peer_needs_snapshot_when_block_gossip_skips_a_height() { 219 let block = Block { 220 height: 10, 221 prev_hash: "prev".to_string(), 222 timestamp_ms: 1, 223 miner: "miner".to_string(), 224 finalizer_mode: FinalizerMode::Ticket, 225 finalizer_rank: 0, 226 reward: 100, 227 vdf_rounds: 1, 228 vdf_output: "vdf".to_string(), 229 leader_proof: None, 230 blinded_transactions: Vec::new(), 231 reveal_bundle_section: RevealBundleSection::default(), 232 transactions: Vec::new(), 233 hash: "hash".to_string(), 234 }; 235 236 assert!(peer_needs_snapshot( 237 8, 238 &[GossipEnvelope::Block(block.clone())] 239 )); 240 assert!(!peer_needs_snapshot(9, &[GossipEnvelope::Block(block)])); 241 assert!(!peer_needs_snapshot( 242 8, 243 &[GossipEnvelope::PeerAnnouncement { 244 address: "127.0.0.1:9444".to_string(), 245 node_id: Some("peer-node".to_string()), 246 }] 247 )); 248 } 249 }