Skip to main content

reth_network_types/peers/
config.rs

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