Skip to main content

reth_discv4/
config.rs

1//! A set of configuration parameters to tune the discovery protocol.
2//!
3//! This basis of this file has been taken from the discv5 codebase:
4//! <https://github.com/sigp/discv5>
5
6use alloy_primitives::bytes::Bytes;
7use alloy_rlp::Encodable;
8use reth_net_banlist::BanList;
9use reth_net_nat::{NatResolver, ResolveNatInterval};
10use reth_network_peers::NodeRecord;
11use std::{
12    collections::{HashMap, HashSet},
13    net::IpAddr,
14    time::Duration,
15};
16
17/// Configuration parameters that define the performance of the discovery network.
18#[derive(Clone, Debug)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
20pub struct Discv4Config {
21    /// Size of the channel buffer for outgoing messages.
22    pub udp_egress_message_buffer: usize,
23    /// Size of the channel buffer for incoming messages.
24    pub udp_ingress_message_buffer: usize,
25    /// The number of allowed consecutive failures for `FindNode` requests. Default: 5.
26    pub max_find_node_failures: u8,
27    /// The interval to use when checking for expired nodes that need to be re-pinged. Default:
28    /// 10 seconds.
29    pub ping_interval: Duration,
30    /// The duration of we consider a ping timed out.
31    pub ping_expiration: Duration,
32    /// The rate at which new random lookups should be triggered.
33    pub lookup_interval: Duration,
34    /// The duration of we consider a `FindNode` request timed out.
35    pub request_timeout: Duration,
36    /// The duration after which we consider an enr request timed out.
37    pub enr_expiration: Duration,
38    /// The duration we set for neighbours responses.
39    pub neighbours_expiration: Duration,
40    /// Provides a way to ban peers and ips.
41    #[cfg_attr(feature = "serde", serde(skip))]
42    pub ban_list: BanList,
43    /// Nodes to boot from.
44    pub bootstrap_nodes: HashSet<NodeRecord>,
45    /// Whether to randomly discover new peers.
46    ///
47    /// If true, the node will automatically randomly walk the DHT in order to find new peers.
48    pub enable_dht_random_walk: bool,
49    /// Whether to automatically lookup peers.
50    pub enable_lookup: bool,
51    /// Whether to enforce EIP-868 extension.
52    pub enable_eip868: bool,
53    /// Whether to only respond with nodes that have a known EIP-868 ENR record (fork ID) in
54    /// `Neighbours` responses.
55    ///
56    /// When enabled, the node will filter out entries that have not yet been confirmed to support
57    /// EIP-868 (i.e., have no `fork_id`) when responding to `FindNode` requests. This helps
58    /// callers discover only peers that advertise their fork ID.
59    pub enforce_eip868_neighbours: bool,
60    /// Whether to respect expiration timestamps in messages.
61    pub enforce_expiration_timestamps: bool,
62    /// Additional pairs to include in The [`Enr`](enr::Enr) if EIP-868 extension is enabled <https://eips.ethereum.org/EIPS/eip-868>
63    pub additional_eip868_rlp_pairs: HashMap<Vec<u8>, Bytes>,
64    /// If configured, try to resolve public ip
65    pub external_ip_resolver: Option<NatResolver>,
66    /// External IP of the opposite family to the local [`NodeRecord`] address, advertised in the
67    /// local EIP-868 ENR next to it (`ip` + `ip6`). Serving that family requires a secondary
68    /// socket of that family (shared-port mode).
69    pub secondary_advertised_ip: Option<IpAddr>,
70    /// If configured and a `external_ip_resolver` is configured, try to resolve the external ip
71    /// using this interval.
72    pub resolve_external_ip_interval: Option<Duration>,
73    /// The duration after which we consider a bond expired.
74    pub bond_expiration: Duration,
75}
76
77impl Discv4Config {
78    /// Returns a new default builder instance
79    pub fn builder() -> Discv4ConfigBuilder {
80        Default::default()
81    }
82
83    /// Add another key value pair to include in the ENR
84    pub fn add_eip868_pair(&mut self, key: impl Into<Vec<u8>>, value: impl Encodable) -> &mut Self {
85        self.add_eip868_rlp_pair(key, Bytes::from(alloy_rlp::encode(&value)))
86    }
87
88    /// Add another key value pair to include in the ENR
89    pub fn add_eip868_rlp_pair(&mut self, key: impl Into<Vec<u8>>, rlp: Bytes) -> &mut Self {
90        self.additional_eip868_rlp_pairs.insert(key.into(), rlp);
91        self
92    }
93
94    /// Extend additional key value pairs to include in the ENR
95    pub fn extend_eip868_rlp_pairs(
96        &mut self,
97        pairs: impl IntoIterator<Item = (impl Into<Vec<u8>>, Bytes)>,
98    ) -> &mut Self {
99        for (k, v) in pairs {
100            self.add_eip868_rlp_pair(k, v);
101        }
102        self
103    }
104
105    /// Returns the corresponding [`ResolveNatInterval`], if a [`NatResolver`] and an interval was
106    /// configured
107    pub fn resolve_external_ip_interval(&self) -> Option<ResolveNatInterval> {
108        let resolver = self.external_ip_resolver.clone()?;
109        let interval = self.resolve_external_ip_interval?;
110        Some(ResolveNatInterval::interval_at(resolver, tokio::time::Instant::now(), interval))
111    }
112}
113
114impl Default for Discv4Config {
115    fn default() -> Self {
116        Self {
117            // This should be high enough to cover an entire recursive FindNode lookup which is
118            // includes sending FindNode to nodes it discovered in the rounds using the concurrency
119            // factor ALPHA
120            udp_egress_message_buffer: 1024,
121            // Every outgoing request will eventually lead to an incoming response
122            udp_ingress_message_buffer: 1024,
123            max_find_node_failures: 5,
124            ping_interval: Duration::from_secs(10),
125            // Unified expiration and timeout durations, mirrors geth's `expiration` duration
126            ping_expiration: Duration::from_secs(20),
127            bond_expiration: Duration::from_secs(60 * 60),
128            enr_expiration: Duration::from_secs(20),
129            neighbours_expiration: Duration::from_secs(20),
130            request_timeout: Duration::from_secs(20),
131
132            lookup_interval: Duration::from_secs(20),
133            ban_list: Default::default(),
134            bootstrap_nodes: Default::default(),
135            enable_dht_random_walk: true,
136            enable_lookup: true,
137            enable_eip868: true,
138            enforce_eip868_neighbours: false,
139            enforce_expiration_timestamps: true,
140            additional_eip868_rlp_pairs: Default::default(),
141            external_ip_resolver: Some(Default::default()),
142            secondary_advertised_ip: None,
143            // By default retry public IP using a 5min interval
144            resolve_external_ip_interval: Some(Duration::from_secs(60 * 5)),
145        }
146    }
147}
148
149/// Builder type for [`Discv4Config`]
150#[derive(Clone, Debug, Default)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
152pub struct Discv4ConfigBuilder {
153    config: Discv4Config,
154}
155
156impl Discv4ConfigBuilder {
157    /// Sets the channel size for incoming messages
158    pub const fn udp_ingress_message_buffer(
159        &mut self,
160        udp_ingress_message_buffer: usize,
161    ) -> &mut Self {
162        self.config.udp_ingress_message_buffer = udp_ingress_message_buffer;
163        self
164    }
165
166    /// Sets the channel size for outgoing messages
167    pub const fn udp_egress_message_buffer(
168        &mut self,
169        udp_egress_message_buffer: usize,
170    ) -> &mut Self {
171        self.config.udp_egress_message_buffer = udp_egress_message_buffer;
172        self
173    }
174
175    /// The number of allowed request failures for `findNode` requests.
176    pub const fn max_find_node_failures(&mut self, max_find_node_failures: u8) -> &mut Self {
177        self.config.max_find_node_failures = max_find_node_failures;
178        self
179    }
180
181    /// The time between pings to ensure connectivity amongst connected nodes.
182    pub const fn ping_interval(&mut self, interval: Duration) -> &mut Self {
183        self.config.ping_interval = interval;
184        self
185    }
186
187    /// Sets the timeout after which requests are considered timed out
188    pub const fn request_timeout(&mut self, duration: Duration) -> &mut Self {
189        self.config.request_timeout = duration;
190        self
191    }
192
193    /// Sets the expiration duration for pings
194    pub const fn ping_expiration(&mut self, duration: Duration) -> &mut Self {
195        self.config.ping_expiration = duration;
196        self
197    }
198
199    /// Sets the expiration duration for enr requests
200    pub const fn enr_request_expiration(&mut self, duration: Duration) -> &mut Self {
201        self.config.enr_expiration = duration;
202        self
203    }
204
205    /// Sets the expiration duration for lookup neighbor requests
206    pub const fn lookup_neighbours_expiration(&mut self, duration: Duration) -> &mut Self {
207        self.config.neighbours_expiration = duration;
208        self
209    }
210
211    /// Sets the expiration duration for a bond with a peer
212    pub const fn bond_expiration(&mut self, duration: Duration) -> &mut Self {
213        self.config.bond_expiration = duration;
214        self
215    }
216
217    /// Whether to discover random nodes in the DHT.
218    pub const fn enable_dht_random_walk(&mut self, enable_dht_random_walk: bool) -> &mut Self {
219        self.config.enable_dht_random_walk = enable_dht_random_walk;
220        self
221    }
222
223    /// Whether to automatically lookup
224    pub const fn enable_lookup(&mut self, enable_lookup: bool) -> &mut Self {
225        self.config.enable_lookup = enable_lookup;
226        self
227    }
228
229    /// Whether to enable EIP-868
230    pub const fn enable_eip868(&mut self, enable_eip868: bool) -> &mut Self {
231        self.config.enable_eip868 = enable_eip868;
232        self
233    }
234
235    /// Whether to only include nodes with a known EIP-868 ENR record (fork ID) in `Neighbours`
236    /// responses.
237    pub const fn enforce_eip868_neighbours(
238        &mut self,
239        enforce_eip868_neighbours: bool,
240    ) -> &mut Self {
241        self.config.enforce_eip868_neighbours = enforce_eip868_neighbours;
242        self
243    }
244
245    /// Whether to enforce expiration timestamps in messages.
246    pub const fn enforce_expiration_timestamps(
247        &mut self,
248        enforce_expiration_timestamps: bool,
249    ) -> &mut Self {
250        self.config.enforce_expiration_timestamps = enforce_expiration_timestamps;
251        self
252    }
253
254    /// Add another key value pair to include in the ENR
255    pub fn add_eip868_pair(&mut self, key: impl Into<Vec<u8>>, value: impl Encodable) -> &mut Self {
256        self.add_eip868_rlp_pair(key, Bytes::from(alloy_rlp::encode(&value)))
257    }
258
259    /// Add another key value pair to include in the ENR
260    pub fn add_eip868_rlp_pair(&mut self, key: impl Into<Vec<u8>>, rlp: Bytes) -> &mut Self {
261        self.config.additional_eip868_rlp_pairs.insert(key.into(), rlp);
262        self
263    }
264
265    /// Extend additional key value pairs to include in the ENR
266    pub fn extend_eip868_rlp_pairs(
267        &mut self,
268        pairs: impl IntoIterator<Item = (impl Into<Vec<u8>>, Bytes)>,
269    ) -> &mut Self {
270        for (k, v) in pairs {
271            self.add_eip868_rlp_pair(k, v);
272        }
273        self
274    }
275
276    /// A set of lists that can ban IP's or `PeerIds` from the server. See
277    /// [`BanList`].
278    pub fn ban_list(&mut self, ban_list: BanList) -> &mut Self {
279        self.config.ban_list = ban_list;
280        self
281    }
282
283    /// Sets the lookup interval duration.
284    pub const fn lookup_interval(&mut self, lookup_interval: Duration) -> &mut Self {
285        self.config.lookup_interval = lookup_interval;
286        self
287    }
288
289    /// Adds a boot node
290    pub fn add_boot_node(&mut self, node: NodeRecord) -> &mut Self {
291        self.config.bootstrap_nodes.insert(node);
292        self
293    }
294
295    /// Adds multiple boot nodes
296    pub fn add_boot_nodes(&mut self, nodes: impl IntoIterator<Item = NodeRecord>) -> &mut Self {
297        self.config.bootstrap_nodes.extend(nodes);
298        self
299    }
300
301    /// Configures if and how the external IP of the node should be resolved.
302    pub fn external_ip_resolver(&mut self, external_ip_resolver: Option<NatResolver>) -> &mut Self {
303        self.config.external_ip_resolver = external_ip_resolver;
304        self
305    }
306
307    /// Sets the secondary-family external IP advertised in the local EIP-868 ENR.
308    pub const fn secondary_advertised_ip(&mut self, ip: IpAddr) -> &mut Self {
309        self.config.secondary_advertised_ip = Some(ip);
310        self
311    }
312
313    /// Sets the interval at which the external IP is to be resolved.
314    pub const fn resolve_external_ip_interval(
315        &mut self,
316        resolve_external_ip_interval: Option<Duration>,
317    ) -> &mut Self {
318        self.config.resolve_external_ip_interval = resolve_external_ip_interval;
319        self
320    }
321
322    /// Returns the configured [`Discv4Config`]
323    pub fn build(&self) -> Discv4Config {
324        self.config.clone()
325    }
326}
327
328#[cfg(test)]
329mod tests {
330    use super::*;
331
332    #[test]
333    fn test_config_builder() {
334        let mut builder = Discv4Config::builder();
335        let _ = builder
336            .enable_lookup(true)
337            .enable_dht_random_walk(true)
338            .add_boot_nodes(HashSet::new())
339            .lookup_interval(Duration::from_secs(3))
340            .enable_lookup(true)
341            .build();
342    }
343
344    #[tokio::test]
345    async fn test_resolve_external_ip_interval_uses_interval_at() {
346        use reth_net_nat::NatResolver;
347        use std::net::{IpAddr, Ipv4Addr};
348
349        let ip_addr = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
350
351        // Create a config with external IP resolver
352        let mut builder = Discv4Config::builder();
353        builder.external_ip_resolver(Some(NatResolver::ExternalIp(ip_addr)));
354        builder.resolve_external_ip_interval(Some(Duration::from_secs(60 * 5)));
355        let config = builder.build();
356
357        // Get the ResolveNatInterval
358        let mut interval = config.resolve_external_ip_interval().expect("should have interval");
359
360        // Test that first tick returns immediately (interval_at behavior)
361        let ip = interval.tick().await;
362        assert_eq!(ip, Some(ip_addr));
363    }
364}