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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
//! Configuration for peering.

use std::{
    collections::HashSet,
    io::{self, ErrorKind},
    path::Path,
    time::Duration,
};

use reth_net_banlist::BanList;
use reth_network_peers::{NodeRecord, TrustedPeer};
use tracing::info;

use crate::{BackoffKind, ReputationChangeWeights};

/// Maximum number of available slots for outbound sessions.
pub const DEFAULT_MAX_COUNT_PEERS_OUTBOUND: u32 = 100;

/// Maximum number of available slots for inbound sessions.
pub const DEFAULT_MAX_COUNT_PEERS_INBOUND: u32 = 30;

/// Maximum number of available slots for concurrent outgoing dials.
///
/// This restricts how many outbound dials can be performed concurrently.
pub const DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS: usize = 15;

/// The durations to use when a backoff should be applied to a peer.
///
/// See also [`BackoffKind`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PeerBackoffDurations {
    /// Applies to connection problems where there is a chance that they will be resolved after the
    /// short duration.
    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
    pub low: Duration,
    /// Applies to more severe connection problems where there is a lower chance that they will be
    /// resolved.
    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
    pub medium: Duration,
    /// Intended for spammers, or bad peers in general.
    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
    pub high: Duration,
    /// Maximum total backoff duration.
    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
    pub max: Duration,
}

impl PeerBackoffDurations {
    /// Returns the corresponding [`Duration`]
    pub const fn backoff(&self, kind: BackoffKind) -> Duration {
        match kind {
            BackoffKind::Low => self.low,
            BackoffKind::Medium => self.medium,
            BackoffKind::High => self.high,
        }
    }

    /// Returns the timestamp until which we should backoff.
    ///
    /// The Backoff duration is capped by the configured maximum backoff duration.
    pub fn backoff_until(&self, kind: BackoffKind, backoff_counter: u8) -> std::time::Instant {
        let backoff_time = self.backoff(kind);
        let backoff_time = backoff_time + backoff_time * backoff_counter as u32;
        let now = std::time::Instant::now();
        now + backoff_time.min(self.max)
    }

    /// Returns durations for testing.
    #[cfg(any(test, feature = "test-utils"))]
    pub const fn test() -> Self {
        Self {
            low: Duration::from_millis(200),
            medium: Duration::from_millis(200),
            high: Duration::from_millis(200),
            max: Duration::from_millis(200),
        }
    }
}

impl Default for PeerBackoffDurations {
    fn default() -> Self {
        Self {
            low: Duration::from_secs(30),
            // 3min
            medium: Duration::from_secs(60 * 3),
            // 15min
            high: Duration::from_secs(60 * 15),
            // 1h
            max: Duration::from_secs(60 * 60),
        }
    }
}

/// Tracks stats about connected nodes
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
pub struct ConnectionsConfig {
    /// Maximum allowed outbound connections.
    pub max_outbound: usize,
    /// Maximum allowed inbound connections.
    pub max_inbound: usize,
    /// Maximum allowed concurrent outbound dials.
    #[cfg_attr(feature = "serde", serde(default))]
    pub max_concurrent_outbound_dials: usize,
}

impl Default for ConnectionsConfig {
    fn default() -> Self {
        Self {
            max_outbound: DEFAULT_MAX_COUNT_PEERS_OUTBOUND as usize,
            max_inbound: DEFAULT_MAX_COUNT_PEERS_INBOUND as usize,
            max_concurrent_outbound_dials: DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS,
        }
    }
}

/// Config type for initiating a `PeersManager` instance.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct PeersConfig {
    /// How often to recheck free slots for outbound connections.
    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
    pub refill_slots_interval: Duration,
    /// Trusted nodes to connect to or accept from
    pub trusted_nodes: Vec<TrustedPeer>,
    /// Connect to or accept from trusted nodes only?
    #[cfg_attr(feature = "serde", serde(alias = "connect_trusted_nodes_only"))]
    pub trusted_nodes_only: bool,
    /// Maximum number of backoff attempts before we give up on a peer and dropping.
    ///
    /// The max time spent of a peer before it's removed from the set is determined by the
    /// configured backoff duration and the max backoff count.
    ///
    /// With a backoff counter of 5 and a backoff duration of 1h, the minimum time spent of the
    /// peer in the table is the sum of all backoffs (1h + 2h + 3h + 4h + 5h = 15h).
    ///
    /// Note: this does not apply to trusted peers.
    pub max_backoff_count: u8,
    /// Basic nodes to connect to.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub basic_nodes: HashSet<NodeRecord>,
    /// How long to ban bad peers.
    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
    pub ban_duration: Duration,
    /// Restrictions on `PeerIds` and Ips.
    #[cfg_attr(feature = "serde", serde(skip))]
    pub ban_list: BanList,
    /// Restrictions on connections.
    pub connection_info: ConnectionsConfig,
    /// How to weigh reputation changes.
    pub reputation_weights: ReputationChangeWeights,
    /// How long to backoff peers that we are failed to connect to for non-fatal reasons.
    ///
    /// The backoff duration increases with number of backoff attempts.
    pub backoff_durations: PeerBackoffDurations,
}

impl Default for PeersConfig {
    fn default() -> Self {
        Self {
            refill_slots_interval: Duration::from_millis(5_000),
            connection_info: Default::default(),
            reputation_weights: Default::default(),
            ban_list: Default::default(),
            // Ban peers for 12h
            ban_duration: Duration::from_secs(60 * 60 * 12),
            backoff_durations: Default::default(),
            trusted_nodes: Default::default(),
            trusted_nodes_only: false,
            basic_nodes: Default::default(),
            max_backoff_count: 5,
        }
    }
}

impl PeersConfig {
    /// A set of `peer_ids` and ip addr that we want to never connect to
    pub fn with_ban_list(mut self, ban_list: BanList) -> Self {
        self.ban_list = ban_list;
        self
    }

    /// Configure how long to ban bad peers
    pub const fn with_ban_duration(mut self, ban_duration: Duration) -> Self {
        self.ban_duration = ban_duration;
        self
    }

    /// Maximum allowed outbound connections.
    pub const fn with_max_outbound(mut self, max_outbound: usize) -> Self {
        self.connection_info.max_outbound = max_outbound;
        self
    }

    /// Maximum allowed inbound connections with optional update.
    pub const fn with_max_inbound_opt(mut self, max_inbound: Option<usize>) -> Self {
        if let Some(max_inbound) = max_inbound {
            self.connection_info.max_inbound = max_inbound;
        }
        self
    }

    /// Maximum allowed outbound connections with optional update.
    pub const fn with_max_outbound_opt(mut self, max_outbound: Option<usize>) -> Self {
        if let Some(max_outbound) = max_outbound {
            self.connection_info.max_outbound = max_outbound;
        }
        self
    }

    /// Maximum allowed inbound connections.
    pub const fn with_max_inbound(mut self, max_inbound: usize) -> Self {
        self.connection_info.max_inbound = max_inbound;
        self
    }

    /// Maximum allowed concurrent outbound dials.
    pub const fn with_max_concurrent_dials(mut self, max_concurrent_outbound_dials: usize) -> Self {
        self.connection_info.max_concurrent_outbound_dials = max_concurrent_outbound_dials;
        self
    }

    /// Nodes to always connect to.
    pub fn with_trusted_nodes(mut self, nodes: Vec<TrustedPeer>) -> Self {
        self.trusted_nodes = nodes;
        self
    }

    /// Connect only to trusted nodes.
    pub const fn with_trusted_nodes_only(mut self, trusted_only: bool) -> Self {
        self.trusted_nodes_only = trusted_only;
        self
    }

    /// Nodes available at launch.
    pub fn with_basic_nodes(mut self, nodes: HashSet<NodeRecord>) -> Self {
        self.basic_nodes = nodes;
        self
    }

    /// Configures the max allowed backoff count.
    pub const fn with_max_backoff_count(mut self, max_backoff_count: u8) -> Self {
        self.max_backoff_count = max_backoff_count;
        self
    }

    /// Configures how to weigh reputation changes.
    pub const fn with_reputation_weights(
        mut self,
        reputation_weights: ReputationChangeWeights,
    ) -> Self {
        self.reputation_weights = reputation_weights;
        self
    }

    /// Configures how long to backoff peers that are we failed to connect to for non-fatal reasons
    pub const fn with_backoff_durations(mut self, backoff_durations: PeerBackoffDurations) -> Self {
        self.backoff_durations = backoff_durations;
        self
    }

    /// Returns the maximum number of peers, inbound and outbound.
    pub const fn max_peers(&self) -> usize {
        self.connection_info.max_outbound + self.connection_info.max_inbound
    }

    /// Read from file nodes available at launch. Ignored if None.
    pub fn with_basic_nodes_from_file(
        self,
        optional_file: Option<impl AsRef<Path>>,
    ) -> Result<Self, io::Error> {
        let Some(file_path) = optional_file else { return Ok(self) };
        let reader = match std::fs::File::open(file_path.as_ref()) {
            Ok(file) => io::BufReader::new(file),
            Err(e) if e.kind() == ErrorKind::NotFound => return Ok(self),
            Err(e) => Err(e)?,
        };
        info!(target: "net::peers", file = %file_path.as_ref().display(), "Loading saved peers");
        let nodes: HashSet<NodeRecord> = serde_json::from_reader(reader)?;
        Ok(self.with_basic_nodes(nodes))
    }

    /// Returns settings for testing
    #[cfg(any(test, feature = "test-utils"))]
    pub fn test() -> Self {
        Self {
            refill_slots_interval: Duration::from_millis(100),
            backoff_durations: PeerBackoffDurations::test(),
            ..Default::default()
        }
    }
}