Skip to main content

reth_network_types/peers/
config.rs

1//! Configuration for peering.
2
3use std::{
4    collections::HashSet,
5    io::{self, ErrorKind},
6    path::Path,
7    time::Duration,
8};
9
10use reth_net_banlist::{BanList, IpFilter};
11use reth_network_peers::{NodeRecord, TrustedPeer};
12use tracing::info;
13
14use crate::{BackoffKind, ReputationChangeWeights};
15
16/// Maximum number of available slots for outbound sessions.
17pub const DEFAULT_MAX_COUNT_PEERS_OUTBOUND: u32 = 100;
18
19/// Maximum number of available slots for inbound sessions.
20pub const DEFAULT_MAX_COUNT_PEERS_INBOUND: u32 = 30;
21
22/// Maximum number of available slots for concurrent outgoing dials.
23///
24/// This restricts how many outbound dials can be performed concurrently.
25pub const DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS: usize = 15;
26
27/// A temporary timeout for ips on incoming connection attempts.
28pub const INBOUND_IP_THROTTLE_DURATION: Duration = Duration::from_secs(30);
29
30/// The durations to use when a backoff should be applied to a peer.
31///
32/// See also [`BackoffKind`].
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
35pub struct PeerBackoffDurations {
36    /// Applies to connection problems where there is a chance that they will be resolved after the
37    /// short duration.
38    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
39    pub low: Duration,
40    /// Applies to more severe connection problems where there is a lower chance that they will be
41    /// resolved.
42    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
43    pub medium: Duration,
44    /// Intended for spammers, or bad peers in general.
45    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
46    pub high: Duration,
47    /// Maximum total backoff duration.
48    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
49    pub max: Duration,
50}
51
52impl PeerBackoffDurations {
53    /// Returns the corresponding [`Duration`]
54    pub const fn backoff(&self, kind: BackoffKind) -> Duration {
55        match kind {
56            BackoffKind::Low => self.low,
57            BackoffKind::Medium => self.medium,
58            BackoffKind::High => self.high,
59        }
60    }
61
62    /// Returns the timestamp until which we should backoff.
63    ///
64    /// The Backoff duration is capped by the configured maximum backoff duration.
65    pub fn backoff_until(&self, kind: BackoffKind, backoff_counter: u8) -> std::time::Instant {
66        let backoff_time = self.backoff(kind);
67        let backoff_time = backoff_time + backoff_time * backoff_counter as u32;
68        let now = std::time::Instant::now();
69        now + backoff_time.min(self.max)
70    }
71
72    /// Returns durations for testing.
73    #[cfg(any(test, feature = "test-utils"))]
74    pub const fn test() -> Self {
75        Self {
76            low: Duration::from_millis(200),
77            medium: Duration::from_millis(200),
78            high: Duration::from_millis(200),
79            max: Duration::from_millis(200),
80        }
81    }
82}
83
84impl Default for PeerBackoffDurations {
85    fn default() -> Self {
86        Self {
87            low: Duration::from_secs(30),
88            // 3min
89            medium: Duration::from_secs(60 * 3),
90            // 15min
91            high: Duration::from_secs(60 * 15),
92            // 1h
93            max: Duration::from_secs(60 * 60),
94        }
95    }
96}
97
98/// Tracks stats about connected nodes
99#[derive(Debug, Clone, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize), serde(default))]
101pub struct ConnectionsConfig {
102    /// Maximum allowed outbound connections.
103    pub max_outbound: usize,
104    /// Maximum allowed inbound connections.
105    pub max_inbound: usize,
106    /// Maximum allowed concurrent outbound dials.
107    #[cfg_attr(feature = "serde", serde(default))]
108    pub max_concurrent_outbound_dials: usize,
109}
110
111impl Default for ConnectionsConfig {
112    fn default() -> Self {
113        Self {
114            max_outbound: DEFAULT_MAX_COUNT_PEERS_OUTBOUND as usize,
115            max_inbound: DEFAULT_MAX_COUNT_PEERS_INBOUND as usize,
116            max_concurrent_outbound_dials: DEFAULT_MAX_COUNT_CONCURRENT_OUTBOUND_DIALS,
117        }
118    }
119}
120
121/// Config type for initiating a `PeersManager` instance.
122#[derive(Debug, Clone, PartialEq, Eq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124#[cfg_attr(feature = "serde", serde(default))]
125pub struct PeersConfig {
126    /// How often to recheck free slots for outbound connections.
127    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
128    pub refill_slots_interval: Duration,
129    /// Trusted nodes to connect to or accept from
130    pub trusted_nodes: Vec<TrustedPeer>,
131    /// Connect to or accept from trusted nodes only?
132    #[cfg_attr(feature = "serde", serde(alias = "connect_trusted_nodes_only"))]
133    pub trusted_nodes_only: bool,
134    /// Interval to update trusted nodes DNS resolution
135    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
136    pub trusted_nodes_resolution_interval: Duration,
137    /// Maximum number of backoff attempts before we give up on a peer and dropping.
138    ///
139    /// The max time spent of a peer before it's removed from the set is determined by the
140    /// configured backoff duration and the max backoff count.
141    ///
142    /// With a backoff counter of 5 and a backoff duration of 1h, the minimum time spent of the
143    /// peer in the table is the sum of all backoffs (1h + 2h + 3h + 4h + 5h = 15h).
144    ///
145    /// Note: this does not apply to trusted peers.
146    pub max_backoff_count: u8,
147    /// Basic nodes to connect to.
148    #[cfg_attr(feature = "serde", serde(skip))]
149    pub basic_nodes: HashSet<NodeRecord>,
150    /// How long to ban bad peers.
151    #[cfg_attr(feature = "serde", serde(with = "humantime_serde"))]
152    pub ban_duration: Duration,
153    /// Restrictions on `PeerIds` and Ips.
154    #[cfg_attr(feature = "serde", serde(skip))]
155    pub ban_list: BanList,
156    /// Restrictions on connections.
157    pub connection_info: ConnectionsConfig,
158    /// How to weigh reputation changes.
159    pub reputation_weights: ReputationChangeWeights,
160    /// How long to backoff peers that we are failed to connect to for non-fatal reasons.
161    ///
162    /// The backoff duration increases with number of backoff attempts.
163    pub backoff_durations: PeerBackoffDurations,
164    /// How long to temporarily ban ips on incoming connection attempts.
165    ///
166    /// This acts as an IP based rate limit.
167    #[cfg_attr(feature = "serde", serde(default, with = "humantime_serde"))]
168    pub incoming_ip_throttle_duration: Duration,
169    /// IP address filter for restricting network connections to specific IP ranges.
170    ///
171    /// Similar to geth's --netrestrict flag. If configured, only connections to/from
172    /// IPs within the specified CIDR ranges will be allowed.
173    #[cfg_attr(feature = "serde", serde(skip))]
174    pub ip_filter: IpFilter,
175    /// If true, discovered peers without a confirmed ENR [`ForkId`](alloy_eip2124::ForkId)
176    /// (EIP-868) will not be added to the peer set until their fork ID is verified.
177    ///
178    /// This filters out peers from other networks that pollute the discovery table.
179    pub enforce_enr_fork_id: bool,
180}
181
182impl Default for PeersConfig {
183    fn default() -> Self {
184        Self {
185            refill_slots_interval: Duration::from_millis(5_000),
186            connection_info: Default::default(),
187            reputation_weights: Default::default(),
188            ban_list: Default::default(),
189            // Ban peers for 12h
190            ban_duration: Duration::from_secs(60 * 60 * 12),
191            backoff_durations: Default::default(),
192            trusted_nodes: Default::default(),
193            trusted_nodes_only: false,
194            trusted_nodes_resolution_interval: Duration::from_secs(60 * 60),
195            basic_nodes: Default::default(),
196            max_backoff_count: 5,
197            incoming_ip_throttle_duration: INBOUND_IP_THROTTLE_DURATION,
198            ip_filter: IpFilter::default(),
199            enforce_enr_fork_id: false,
200        }
201    }
202}
203
204impl PeersConfig {
205    /// A set of `peer_ids` and ip addr that we want to never connect to
206    pub fn with_ban_list(mut self, ban_list: BanList) -> Self {
207        self.ban_list = ban_list;
208        self
209    }
210
211    /// Configure how long to ban bad peers
212    pub const fn with_ban_duration(mut self, ban_duration: Duration) -> Self {
213        self.ban_duration = ban_duration;
214        self
215    }
216
217    /// Configure how long to refill outbound slots
218    pub const fn with_refill_slots_interval(mut self, interval: Duration) -> Self {
219        self.refill_slots_interval = interval;
220        self
221    }
222
223    /// Maximum allowed outbound connections.
224    pub const fn with_max_outbound(mut self, max_outbound: usize) -> Self {
225        self.connection_info.max_outbound = max_outbound;
226        self
227    }
228
229    /// Maximum allowed inbound connections with optional update.
230    pub const fn with_max_inbound_opt(mut self, max_inbound: Option<usize>) -> Self {
231        if let Some(max_inbound) = max_inbound {
232            self.connection_info.max_inbound = max_inbound;
233        }
234        self
235    }
236
237    /// Maximum allowed outbound connections with optional update.
238    pub const fn with_max_outbound_opt(mut self, max_outbound: Option<usize>) -> Self {
239        if let Some(max_outbound) = max_outbound {
240            self.connection_info.max_outbound = max_outbound;
241        }
242        self
243    }
244
245    /// Maximum allowed inbound connections.
246    pub const fn with_max_inbound(mut self, max_inbound: usize) -> Self {
247        self.connection_info.max_inbound = max_inbound;
248        self
249    }
250
251    /// Maximum allowed concurrent outbound dials.
252    pub const fn with_max_concurrent_dials(mut self, max_concurrent_outbound_dials: usize) -> Self {
253        self.connection_info.max_concurrent_outbound_dials = max_concurrent_outbound_dials;
254        self
255    }
256
257    /// Nodes to always connect to.
258    pub fn with_trusted_nodes(mut self, nodes: Vec<TrustedPeer>) -> Self {
259        self.trusted_nodes = nodes;
260        self
261    }
262
263    /// Connect only to trusted nodes.
264    pub const fn with_trusted_nodes_only(mut self, trusted_only: bool) -> Self {
265        self.trusted_nodes_only = trusted_only;
266        self
267    }
268
269    /// Nodes available at launch.
270    pub fn with_basic_nodes(mut self, nodes: HashSet<NodeRecord>) -> Self {
271        self.basic_nodes = nodes;
272        self
273    }
274
275    /// Configures the max allowed backoff count.
276    pub const fn with_max_backoff_count(mut self, max_backoff_count: u8) -> Self {
277        self.max_backoff_count = max_backoff_count;
278        self
279    }
280
281    /// Configures how to weigh reputation changes.
282    pub const fn with_reputation_weights(
283        mut self,
284        reputation_weights: ReputationChangeWeights,
285    ) -> Self {
286        self.reputation_weights = reputation_weights;
287        self
288    }
289
290    /// Configures how long to backoff peers that are we failed to connect to for non-fatal reasons
291    pub const fn with_backoff_durations(mut self, backoff_durations: PeerBackoffDurations) -> Self {
292        self.backoff_durations = backoff_durations;
293        self
294    }
295
296    /// Returns the maximum number of peers, inbound and outbound.
297    pub const fn max_peers(&self) -> usize {
298        self.connection_info.max_outbound + self.connection_info.max_inbound
299    }
300
301    /// Read from file nodes available at launch. Ignored if None.
302    pub fn with_basic_nodes_from_file(
303        self,
304        optional_file: Option<impl AsRef<Path>>,
305    ) -> Result<Self, io::Error> {
306        let Some(file_path) = optional_file else { return Ok(self) };
307        let reader = match std::fs::File::open(file_path.as_ref()) {
308            Ok(file) => io::BufReader::new(file),
309            Err(e) if e.kind() == ErrorKind::NotFound => return Ok(self),
310            Err(e) => Err(e)?,
311        };
312        info!(target: "net::peers", file = %file_path.as_ref().display(), "Loading saved peers");
313        let nodes: HashSet<NodeRecord> = serde_json::from_reader(reader)?;
314        Ok(self.with_basic_nodes(nodes))
315    }
316
317    /// Configure the IP filter for restricting network connections to specific IP ranges.
318    pub fn with_ip_filter(mut self, ip_filter: IpFilter) -> Self {
319        self.ip_filter = ip_filter;
320        self
321    }
322
323    /// If set, discovered peers without a confirmed ENR [`ForkId`](alloy_eip2124::ForkId) will not
324    /// be added to the peer set until their fork ID is verified via EIP-868.
325    pub const fn with_enforce_enr_fork_id(mut self, enforce: bool) -> Self {
326        self.enforce_enr_fork_id = enforce;
327        self
328    }
329
330    /// Returns settings for testing
331    #[cfg(any(test, feature = "test-utils"))]
332    pub fn test() -> Self {
333        Self {
334            refill_slots_interval: Duration::from_millis(100),
335            backoff_durations: PeerBackoffDurations::test(),
336            ban_duration: Duration::from_millis(200),
337            ..Default::default()
338        }
339    }
340}