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