1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! Prioritizing outbound peer connections based on peer attributes.

use std::net::SocketAddr;

use zebra_chain::parameters::Network;

use crate::PeerSocketAddr;

use AttributePreference::*;

/// A level of preference for a peer attribute.
///
/// Invalid peer attributes are represented as errors.
///
/// Outbound peer connections are initiated in the sorted [order](std::cmp::Ord)
/// of this type.
///
/// The derived order depends on the order of the variants in the enum.
/// The variants are sorted in the order they are listed.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum AttributePreference {
    /// This peer is more likely to be a valid Zcash network peer.
    ///
    /// # Correctness
    ///
    /// This variant must be declared as the first enum variant,
    /// so that `Preferred` peers sort before `Acceptable` peers.
    Preferred,

    /// This peer is possibly a valid Zcash network peer.
    Acceptable,
}

/// A level of preference for a peer.
///
/// Outbound peer connections are initiated in the sorted [order](std::cmp::Ord)
/// of this type.
///
/// The derived order depends on the order of the fields in the struct.
/// The first field determines the overall order, then later fields sort equal first field values.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct PeerPreference {
    /// Is the peer using the canonical Zcash port for the configured [`Network`]?
    canonical_port: AttributePreference,
}

impl AttributePreference {
    /// Returns `Preferred` if `is_preferred` is true.
    pub fn preferred_from(is_preferred: bool) -> Self {
        if is_preferred {
            Preferred
        } else {
            Acceptable
        }
    }

    /// Returns `true` for `Preferred` attributes.
    #[allow(dead_code)]
    pub fn is_preferred(&self) -> bool {
        match self {
            Preferred => true,
            Acceptable => false,
        }
    }
}

impl PeerPreference {
    /// Return a preference for the peer at `peer_addr` on `network`.
    ///
    /// Use the [`PeerPreference`] [`Ord`] implementation to sort preferred peers first.
    pub fn new(
        peer_addr: impl Into<PeerSocketAddr>,
        network: impl Into<Option<Network>>,
    ) -> Result<PeerPreference, &'static str> {
        let peer_addr = peer_addr.into();

        address_is_valid_for_outbound_connections(peer_addr, network)?;

        // This check only prefers the configured network, because
        // address_is_valid_for_outbound_connections() rejects the port used by the other network.
        let canonical_port =
            AttributePreference::preferred_from([8232, 18232].contains(&peer_addr.port()));

        Ok(PeerPreference { canonical_port })
    }
}

/// Is the [`PeerSocketAddr`] we have for this peer valid for outbound
/// connections?
///
/// Since the addresses in the address book are unique, this check can be
/// used to permanently reject entire [`MetaAddr`]s.
///
/// [`MetaAddr`]: crate::meta_addr::MetaAddr
pub fn address_is_valid_for_outbound_connections(
    peer_addr: PeerSocketAddr,
    network: impl Into<Option<Network>>,
) -> Result<(), &'static str> {
    // TODO: make private IP addresses an error unless a debug config is set (#3117)

    if peer_addr.ip().is_unspecified() {
        return Err("invalid peer IP address: unspecified addresses can not be used for outbound connections");
    }

    // 0 is an invalid outbound port.
    if peer_addr.port() == 0 {
        return Err(
            "invalid peer port: unspecified ports can not be used for outbound connections",
        );
    }

    address_is_valid_for_inbound_listeners(*peer_addr, network)
}

/// Is the supplied [`SocketAddr`] valid for inbound listeners on `network`?
///
/// This is used to check Zebra's configured Zcash listener port.
pub fn address_is_valid_for_inbound_listeners(
    listener_addr: SocketAddr,
    network: impl Into<Option<Network>>,
) -> Result<(), &'static str> {
    // TODO: make private IP addresses an error unless a debug config is set (#3117)

    // Ignore ports used by potentially compatible nodes: misconfigured Zcash ports.
    if let Some(network) = network.into() {
        if listener_addr.port() == network.default_port() {
            return Ok(());
        }

        if listener_addr.port() == 8232 {
            return Err(
                "invalid peer port: port is for Mainnet, but this node is configured for Testnet",
            );
        } else if listener_addr.port() == 18232 {
            return Err(
                "invalid peer port: port is for Testnet, but this node is configured for Mainnet",
            );
        }
    }

    // Ignore ports used by potentially compatible nodes: other coins and unsupported Zcash regtest.
    if listener_addr.port() == 18344 {
        return Err(
            "invalid peer port: port is for Regtest, but Zebra does not support that network",
        );
    } else if [8033, 18033, 16125, 26125].contains(&listener_addr.port()) {
        // These coins use the same network magic numbers as Zcash, so we have to reject them by port:
        // - ZClassic: 8033/18033
        //   https://github.com/ZclassicCommunity/zclassic/blob/504362bbf72400f51acdba519e12707da44138c2/src/chainparams.cpp#L130
        // - Flux/ZelCash: 16125/26125
        return Err("invalid peer port: port is for a non-Zcash coin");
    }

    Ok(())
}