iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

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 }