iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

peer_addr.rs (6596B)


      1 use std::{
      2     io::ErrorKind,
      3     net::{IpAddr, SocketAddr},
      4     time::Duration,
      5 };
      6 
      7 use anyhow::{Context, Result};
      8 
      9 use crate::domain::{ValidationError, error_has_validation};
     10 
     11 use super::SyncError;
     12 
     13 pub(super) fn next_reconnect_delay(current: Duration, max_delay: Duration) -> Duration {
     14     (current * 2).min(max_delay)
     15 }
     16 
     17 pub(super) fn reachable_advertised_addr(
     18     advertised_addr: SocketAddr,
     19     remote_addr: SocketAddr,
     20 ) -> SocketAddr {
     21     let mut reachable_addr = advertised_addr;
     22     if reachable_addr.ip().is_unspecified() {
     23         reachable_addr.set_ip(remote_addr.ip());
     24     }
     25     reachable_addr
     26 }
     27 
     28 pub(super) fn normalize_advertised_peer(address: &str, remote_addr: SocketAddr) -> Result<String> {
     29     let advertised_addr = address
     30         .parse::<SocketAddr>()
     31         .with_context(|| format!("invalid announced peer address {address}"))?;
     32     Ok(reachable_advertised_addr(advertised_addr, remote_addr).to_string())
     33 }
     34 
     35 pub(super) fn peer_list_address_is_discoverable(
     36     address: &str,
     37     remote_addr: SocketAddr,
     38 ) -> Result<bool> {
     39     let candidate = address
     40         .parse::<SocketAddr>()
     41         .with_context(|| format!("invalid peer-list address {address}"))?;
     42     Ok(socket_addr_is_discoverable(candidate, remote_addr))
     43 }
     44 
     45 pub(super) fn advertised_peer_is_discoverable(
     46     address: &str,
     47     remote_addr: SocketAddr,
     48 ) -> Result<bool> {
     49     let candidate = address
     50         .parse::<SocketAddr>()
     51         .with_context(|| format!("invalid announced peer address {address}"))?;
     52     Ok(socket_addr_is_discoverable(candidate, remote_addr))
     53 }
     54 
     55 fn socket_addr_is_discoverable(candidate: SocketAddr, remote_addr: SocketAddr) -> bool {
     56     if candidate.ip().is_loopback() {
     57         return remote_addr.ip().is_loopback();
     58     }
     59     ip_is_publicly_discoverable(candidate.ip())
     60 }
     61 
     62 fn ip_is_publicly_discoverable(ip: IpAddr) -> bool {
     63     match ip {
     64         IpAddr::V4(ip) => {
     65             let [a, b, c, d] = ip.octets();
     66             !(a == 0
     67                 || a == 10
     68                 || a == 127
     69                 || (a == 100 && (64..=127).contains(&b))
     70                 || (a == 169 && b == 254)
     71                 || (a == 172 && (16..=31).contains(&b))
     72                 || (a == 192 && b == 168)
     73                 || (a == 192 && b == 0 && c == 2)
     74                 || (a == 198 && b == 51 && c == 100)
     75                 || (a == 203 && b == 0 && c == 113)
     76                 || a >= 224
     77                 || [a, b, c, d] == [255, 255, 255, 255])
     78         }
     79         IpAddr::V6(ip) => {
     80             let segments = ip.segments();
     81             !(ip.is_unspecified()
     82                 || ip.is_loopback()
     83                 || (segments[0] & 0xfe00) == 0xfc00
     84                 || (segments[0] & 0xffc0) == 0xfe80
     85                 || (segments[0] & 0xff00) == 0xff00)
     86         }
     87     }
     88 }
     89 
     90 pub(super) fn is_self_peer_address_for(
     91     address: &str,
     92     listen_addr: SocketAddr,
     93     advertised_addr: Option<SocketAddr>,
     94 ) -> bool {
     95     address.parse::<SocketAddr>().is_ok_and(|candidate| {
     96         is_self_socket_addr(candidate, listen_addr)
     97             || advertised_addr.is_some_and(|addr| is_self_socket_addr(candidate, addr))
     98     })
     99 }
    100 
    101 fn is_self_socket_addr(candidate: SocketAddr, listen_addr: SocketAddr) -> bool {
    102     if candidate == listen_addr {
    103         return true;
    104     }
    105     if candidate.port() != listen_addr.port() {
    106         return false;
    107     }
    108 
    109     let candidate_ip = candidate.ip();
    110     let listen_ip = listen_addr.ip();
    111     if listen_ip.is_unspecified() {
    112         return candidate_ip.is_unspecified() || candidate_ip.is_loopback();
    113     }
    114     if candidate_ip.is_unspecified() {
    115         return listen_ip.is_loopback();
    116     }
    117     false
    118 }
    119 
    120 pub(super) fn is_quiet_disconnect(error: &anyhow::Error) -> bool {
    121     error.chain().any(|cause| {
    122         cause.downcast_ref::<std::io::Error>().is_some_and(|error| {
    123             matches!(
    124                 error.kind(),
    125                 ErrorKind::ConnectionReset
    126                     | ErrorKind::BrokenPipe
    127                     | ErrorKind::UnexpectedEof
    128                     | ErrorKind::ConnectionAborted
    129             )
    130         })
    131     })
    132 }
    133 
    134 pub(super) fn is_possible_fork_error(error: &anyhow::Error) -> bool {
    135     error_has_validation(error, ValidationError::requests_fork_recovery)
    136         || error.chain().any(|cause| {
    137             matches!(
    138                 cause.downcast_ref::<SyncError>(),
    139                 Some(SyncError::BlockPageHasNoCommonAncestor)
    140             )
    141         })
    142 }
    143 
    144 pub(super) fn inbound_error_counts_as_misbehavior(error: &anyhow::Error) -> bool {
    145     !is_possible_fork_error(error)
    146         && !error_has_validation(error, |kind| kind.is_fork_relative() || kind.is_temporal())
    147 }
    148 
    149 #[cfg(test)]
    150 mod tests {
    151     use anyhow::anyhow;
    152 
    153     use crate::domain::ValidationError;
    154 
    155     use super::super::SyncError;
    156 
    157     use super::{inbound_error_counts_as_misbehavior, is_possible_fork_error};
    158 
    159     #[test]
    160     fn future_and_unopened_rank_slot_errors_are_temporal_not_misbehavior() {
    161         assert!(!inbound_error_counts_as_misbehavior(&anyhow::Error::new(
    162             ValidationError::BlockTimestampTooFarInFuture
    163         )));
    164         assert!(!inbound_error_counts_as_misbehavior(&anyhow::Error::new(
    165             ValidationError::BlockBeforeFinalizerRankSlot {
    166                 rank: 1,
    167                 min_timestamp: 123,
    168             }
    169         )));
    170         assert!(inbound_error_counts_as_misbehavior(&anyhow!(
    171             "block hash is invalid"
    172         )));
    173     }
    174 
    175     #[test]
    176     fn missing_block_page_ancestor_triggers_fork_recovery_without_peer_penalty() {
    177         let error =
    178             anyhow::Error::new(SyncError::BlockPageHasNoCommonAncestor).context("block batch");
    179 
    180         assert!(is_possible_fork_error(&error));
    181         assert!(!inbound_error_counts_as_misbehavior(&error));
    182     }
    183 
    184     #[test]
    185     fn fork_scoped_gossip_errors_do_not_penalize_peers() {
    186         for kind in [
    187             ValidationError::BurnBundleParentMismatch,
    188             ValidationError::BurnAnchorOutsidePendingWindow,
    189             ValidationError::MineAnchorNotOnChain,
    190         ] {
    191             let error = anyhow::Error::new(kind);
    192             assert!(!inbound_error_counts_as_misbehavior(&error));
    193             assert!(!is_possible_fork_error(&error));
    194         }
    195 
    196         assert!(inbound_error_counts_as_misbehavior(&anyhow!(
    197             "burn bundle signature is invalid"
    198         )));
    199     }
    200 
    201     #[test]
    202     fn matching_text_without_a_typed_error_is_not_trusted() {
    203         let error = anyhow!("burn bundle parent hash is invalid");
    204 
    205         assert!(inbound_error_counts_as_misbehavior(&error));
    206         assert!(!is_possible_fork_error(&error));
    207     }
    208 }