zebra_network/peer/
priority.rs

1//! Prioritizing outbound peer connections based on peer attributes.
2
3use std::net::SocketAddr;
4
5use zebra_chain::parameters::Network;
6
7use crate::PeerSocketAddr;
8
9use AttributePreference::*;
10
11/// A level of preference for a peer attribute.
12///
13/// Invalid peer attributes are represented as errors.
14///
15/// Outbound peer connections are initiated in the sorted [order](std::cmp::Ord)
16/// of this type.
17///
18/// The derived order depends on the order of the variants in the enum.
19/// The variants are sorted in the order they are listed.
20#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
21pub enum AttributePreference {
22    /// This peer is more likely to be a valid Zcash network peer.
23    ///
24    /// # Correctness
25    ///
26    /// This variant must be declared as the first enum variant,
27    /// so that `Preferred` peers sort before `Acceptable` peers.
28    Preferred,
29
30    /// This peer is possibly a valid Zcash network peer.
31    Acceptable,
32}
33
34/// A level of preference for a peer.
35///
36/// Outbound peer connections are initiated in the sorted [order](std::cmp::Ord)
37/// of this type.
38///
39/// The derived order depends on the order of the fields in the struct.
40/// The first field determines the overall order, then later fields sort equal first field values.
41#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
42pub struct PeerPreference {
43    /// Is the peer using the canonical Zcash port for the configured [`Network`]?
44    canonical_port: AttributePreference,
45}
46
47impl AttributePreference {
48    /// Returns `Preferred` if `is_preferred` is true.
49    pub fn preferred_from(is_preferred: bool) -> Self {
50        if is_preferred {
51            Preferred
52        } else {
53            Acceptable
54        }
55    }
56
57    /// Returns `true` for `Preferred` attributes.
58    #[allow(dead_code)]
59    pub fn is_preferred(&self) -> bool {
60        match self {
61            Preferred => true,
62            Acceptable => false,
63        }
64    }
65}
66
67impl PeerPreference {
68    /// Return a preference for the peer at `peer_addr` on `network`.
69    ///
70    /// Use the [`PeerPreference`] [`Ord`] implementation to sort preferred peers first.
71    pub fn new(
72        peer_addr: impl Into<PeerSocketAddr>,
73        network: impl Into<Option<Network>>,
74    ) -> Result<PeerPreference, &'static str> {
75        let peer_addr = peer_addr.into();
76
77        address_is_valid_for_outbound_connections(peer_addr, network)?;
78
79        // This check only prefers the configured network, because
80        // address_is_valid_for_outbound_connections() rejects the port used by the other network.
81        let canonical_port =
82            AttributePreference::preferred_from([8232, 18232].contains(&peer_addr.port()));
83
84        Ok(PeerPreference { canonical_port })
85    }
86}
87
88/// Is the [`PeerSocketAddr`] we have for this peer valid for outbound
89/// connections?
90///
91/// Since the addresses in the address book are unique, this check can be
92/// used to permanently reject entire [`MetaAddr`]s.
93///
94/// [`MetaAddr`]: crate::meta_addr::MetaAddr
95pub fn address_is_valid_for_outbound_connections(
96    peer_addr: PeerSocketAddr,
97    network: impl Into<Option<Network>>,
98) -> Result<(), &'static str> {
99    // TODO: make private IP addresses an error unless a debug config is set (#3117)
100
101    if peer_addr.ip().is_unspecified() {
102        return Err("invalid peer IP address: unspecified addresses can not be used for outbound connections");
103    }
104
105    // 0 is an invalid outbound port.
106    if peer_addr.port() == 0 {
107        return Err(
108            "invalid peer port: unspecified ports can not be used for outbound connections",
109        );
110    }
111
112    address_is_valid_for_inbound_listeners(*peer_addr, network)
113}
114
115/// Is the supplied [`SocketAddr`] valid for inbound listeners on `network`?
116///
117/// This is used to check Zebra's configured Zcash listener port.
118pub fn address_is_valid_for_inbound_listeners(
119    listener_addr: SocketAddr,
120    network: impl Into<Option<Network>>,
121) -> Result<(), &'static str> {
122    // TODO: make private IP addresses an error unless a debug config is set (#3117)
123
124    // Ignore ports used by potentially compatible nodes: misconfigured Zcash ports.
125    if let Some(network) = network.into() {
126        if listener_addr.port() == network.default_port() {
127            return Ok(());
128        }
129
130        if listener_addr.port() == 8232 {
131            return Err(
132                "invalid peer port: port is for Mainnet, but this node is configured for Testnet",
133            );
134        } else if listener_addr.port() == 18232 {
135            return Err(
136                "invalid peer port: port is for Testnet, but this node is configured for Mainnet",
137            );
138        }
139    }
140
141    // Ignore ports used by potentially compatible nodes: other coins and unsupported Zcash regtest.
142    if listener_addr.port() == 18344 {
143        return Err(
144            "invalid peer port: port is for Regtest, but Zebra does not support that network",
145        );
146    } else if [8033, 18033, 16125, 26125].contains(&listener_addr.port()) {
147        // These coins use the same network magic numbers as Zcash, so we have to reject them by port:
148        // - ZClassic: 8033/18033
149        //   https://github.com/ZclassicCommunity/zclassic/blob/504362bbf72400f51acdba519e12707da44138c2/src/chainparams.cpp#L130
150        // - Flux/ZelCash: 16125/26125
151        return Err("invalid peer port: port is for a non-Zcash coin");
152    }
153
154    Ok(())
155}