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