1use std::{
4 collections::HashSet,
5 fmt::Debug,
6 net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
7};
8
9use alloy_primitives::Bytes;
10use derive_more::Display;
11use discv5::{
12 multiaddr::{Multiaddr, Protocol},
13 ListenConfig,
14};
15use reth_ethereum_forks::{EnrForkIdEntry, ForkId};
16use reth_network_peers::NodeRecord;
17use tracing::warn;
18
19use crate::{enr::discv4_id_to_multiaddr_id, filter::MustNotIncludeKeys, NetworkStackId};
20
21pub const DEFAULT_DISCOVERY_V5_ADDR: Ipv4Addr = Ipv4Addr::UNSPECIFIED;
25
26pub const DEFAULT_DISCOVERY_V5_ADDR_IPV6: Ipv6Addr = Ipv6Addr::UNSPECIFIED;
30
31pub const DEFAULT_DISCOVERY_V5_PORT: u16 = 9200;
35
36pub const DEFAULT_DISCOVERY_V5_LISTEN_CONFIG: ListenConfig =
40 ListenConfig::Ipv4 { ip: DEFAULT_DISCOVERY_V5_ADDR, port: DEFAULT_DISCOVERY_V5_PORT };
41
42pub const DEFAULT_SECONDS_LOOKUP_INTERVAL: u64 = 20;
46
47pub const DEFAULT_COUNT_BOOTSTRAP_LOOKUPS: u64 = 200;
52
53pub const DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL: u64 = 5;
57
58#[derive(Debug)]
60pub struct ConfigBuilder {
61 discv5_config: Option<discv5::Config>,
63 bootstrap_nodes: HashSet<BootNode>,
65 fork: Option<(&'static [u8], ForkId)>,
70 tcp_socket: SocketAddr,
75 other_enr_kv_pairs: Vec<(&'static [u8], Bytes)>,
78 lookup_interval: Option<u64>,
80 bootstrap_lookup_interval: Option<u64>,
83 bootstrap_lookup_countdown: Option<u64>,
85 discovered_peer_filter: Option<MustNotIncludeKeys>,
88}
89
90impl ConfigBuilder {
91 pub fn new_from(discv5_config: Config) -> Self {
93 let Config {
94 discv5_config,
95 bootstrap_nodes,
96 fork,
97 tcp_socket,
98 other_enr_kv_pairs,
99 lookup_interval,
100 bootstrap_lookup_interval,
101 bootstrap_lookup_countdown,
102 discovered_peer_filter,
103 } = discv5_config;
104
105 Self {
106 discv5_config: Some(discv5_config),
107 bootstrap_nodes,
108 fork: fork.map(|(key, fork_id)| (key, fork_id.fork_id)),
109 tcp_socket,
110 other_enr_kv_pairs,
111 lookup_interval: Some(lookup_interval),
112 bootstrap_lookup_interval: Some(bootstrap_lookup_interval),
113 bootstrap_lookup_countdown: Some(bootstrap_lookup_countdown),
114 discovered_peer_filter: Some(discovered_peer_filter),
115 }
116 }
117
118 pub fn discv5_config(mut self, discv5_config: discv5::Config) -> Self {
120 self.discv5_config = Some(discv5_config);
121 self
122 }
123
124 pub fn add_signed_boot_nodes(mut self, nodes: impl IntoIterator<Item = discv5::Enr>) -> Self {
126 self.bootstrap_nodes.extend(nodes.into_iter().map(BootNode::Enr));
127 self
128 }
129
130 pub fn add_cl_serialized_signed_boot_nodes(mut self, enrs: &str) -> Self {
134 let bootstrap_nodes = &mut self.bootstrap_nodes;
135 for node in enrs.split(&[',']).flat_map(|record| record.trim().parse::<discv5::Enr>()) {
136 bootstrap_nodes.insert(BootNode::Enr(node));
137 }
138 self
139 }
140
141 pub fn add_unsigned_boot_nodes(mut self, enodes: impl IntoIterator<Item = NodeRecord>) -> Self {
143 for node in enodes {
144 if let Ok(node) = BootNode::from_unsigned(node) {
145 self.bootstrap_nodes.insert(node);
146 }
147 }
148
149 self
150 }
151
152 pub fn add_serialized_unsigned_boot_nodes(mut self, enodes: &[&str]) -> Self {
154 for node in enodes {
155 if let Ok(node) = node.parse() {
156 if let Ok(node) = BootNode::from_unsigned(node) {
157 self.bootstrap_nodes.insert(node);
158 }
159 }
160 }
161
162 self
163 }
164
165 pub const fn fork(mut self, fork_key: &'static [u8], fork_id: ForkId) -> Self {
168 self.fork = Some((fork_key, fork_id));
169 self
170 }
171
172 pub const fn tcp_socket(mut self, socket: SocketAddr) -> Self {
176 self.tcp_socket = socket;
177 self
178 }
179
180 pub fn add_enr_kv_pair(mut self, key: &'static [u8], value: Bytes) -> Self {
183 self.other_enr_kv_pairs.push((key, value));
184 self
185 }
186
187 pub const fn lookup_interval(mut self, seconds: u64) -> Self {
190 self.lookup_interval = Some(seconds);
191 self
192 }
193
194 pub const fn bootstrap_lookup_interval(mut self, seconds: u64) -> Self {
197 self.bootstrap_lookup_interval = Some(seconds);
198 self
199 }
200
201 pub const fn bootstrap_lookup_countdown(mut self, counts: u64) -> Self {
203 self.bootstrap_lookup_countdown = Some(counts);
204 self
205 }
206
207 pub fn must_not_include_keys(mut self, not_keys: &[&'static [u8]]) -> Self {
211 let mut filter = self.discovered_peer_filter.unwrap_or_default();
212 filter.add_disallowed_keys(not_keys);
213 self.discovered_peer_filter = Some(filter);
214 self
215 }
216
217 pub fn build(self) -> Config {
219 let Self {
220 discv5_config,
221 bootstrap_nodes,
222 fork,
223 tcp_socket,
224 other_enr_kv_pairs,
225 lookup_interval,
226 bootstrap_lookup_interval,
227 bootstrap_lookup_countdown,
228 discovered_peer_filter,
229 } = self;
230
231 let mut discv5_config = discv5_config.unwrap_or_else(|| {
232 discv5::ConfigBuilder::new(DEFAULT_DISCOVERY_V5_LISTEN_CONFIG).build()
233 });
234
235 discv5_config.listen_config =
236 amend_listen_config_wrt_rlpx(&discv5_config.listen_config, tcp_socket.ip());
237
238 let fork = fork.map(|(key, fork_id)| (key, fork_id.into()));
239
240 let lookup_interval = lookup_interval.unwrap_or(DEFAULT_SECONDS_LOOKUP_INTERVAL);
241 let bootstrap_lookup_interval =
242 bootstrap_lookup_interval.unwrap_or(DEFAULT_SECONDS_BOOTSTRAP_LOOKUP_INTERVAL);
243 let bootstrap_lookup_countdown =
244 bootstrap_lookup_countdown.unwrap_or(DEFAULT_COUNT_BOOTSTRAP_LOOKUPS);
245
246 let discovered_peer_filter = discovered_peer_filter
247 .unwrap_or_else(|| MustNotIncludeKeys::new(&[NetworkStackId::ETH2]));
248
249 Config {
250 discv5_config,
251 bootstrap_nodes,
252 fork,
253 tcp_socket,
254 other_enr_kv_pairs,
255 lookup_interval,
256 bootstrap_lookup_interval,
257 bootstrap_lookup_countdown,
258 discovered_peer_filter,
259 }
260 }
261}
262
263#[derive(Clone, Debug)]
265pub struct Config {
266 pub(super) discv5_config: discv5::Config,
269 pub(super) bootstrap_nodes: HashSet<BootNode>,
271 pub(super) fork: Option<(&'static [u8], EnrForkIdEntry)>,
274 pub(super) tcp_socket: SocketAddr,
279 pub(super) other_enr_kv_pairs: Vec<(&'static [u8], Bytes)>,
282 pub(super) lookup_interval: u64,
284 pub(super) bootstrap_lookup_interval: u64,
287 pub(super) bootstrap_lookup_countdown: u64,
289 pub(super) discovered_peer_filter: MustNotIncludeKeys,
292}
293
294impl Config {
295 pub fn builder(rlpx_tcp_socket: SocketAddr) -> ConfigBuilder {
298 ConfigBuilder {
299 discv5_config: None,
300 bootstrap_nodes: HashSet::default(),
301 fork: None,
302 tcp_socket: rlpx_tcp_socket,
303 other_enr_kv_pairs: Vec::new(),
304 lookup_interval: None,
305 bootstrap_lookup_interval: None,
306 bootstrap_lookup_countdown: None,
307 discovered_peer_filter: None,
308 }
309 }
310
311 pub fn insert_boot_node(&mut self, boot_node: BootNode) {
313 self.bootstrap_nodes.insert(boot_node);
314 }
315
316 pub fn insert_unsigned_boot_node(&mut self, node_record: NodeRecord) {
319 let _ = BootNode::from_unsigned(node_record).map(|node| self.insert_boot_node(node));
320 }
321
322 pub fn extend_unsigned_boot_nodes(
324 &mut self,
325 node_records: impl IntoIterator<Item = NodeRecord>,
326 ) {
327 for node_record in node_records {
328 self.insert_unsigned_boot_node(node_record);
329 }
330 }
331
332 pub fn discovery_socket(&self) -> SocketAddr {
336 match self.discv5_config.listen_config {
337 ListenConfig::Ipv4 { ip, port } => (ip, port).into(),
338 ListenConfig::Ipv6 { ip, port } => (ip, port).into(),
339 ListenConfig::DualStack { ipv6, ipv6_port, .. } => (ipv6, ipv6_port).into(),
340 }
341 }
342
343 pub const fn rlpx_socket(&self) -> &SocketAddr {
346 &self.tcp_socket
347 }
348}
349
350pub const fn ipv4(listen_config: &ListenConfig) -> Option<SocketAddrV4> {
352 match listen_config {
353 ListenConfig::Ipv4 { ip, port } |
354 ListenConfig::DualStack { ipv4: ip, ipv4_port: port, .. } => {
355 Some(SocketAddrV4::new(*ip, *port))
356 }
357 ListenConfig::Ipv6 { .. } => None,
358 }
359}
360
361pub const fn ipv6(listen_config: &ListenConfig) -> Option<SocketAddrV6> {
363 match listen_config {
364 ListenConfig::Ipv4 { .. } => None,
365 ListenConfig::Ipv6 { ip, port } |
366 ListenConfig::DualStack { ipv6: ip, ipv6_port: port, .. } => {
367 Some(SocketAddrV6::new(*ip, *port, 0, 0))
368 }
369 }
370}
371
372pub fn amend_listen_config_wrt_rlpx(
376 listen_config: &ListenConfig,
377 rlpx_addr: IpAddr,
378) -> ListenConfig {
379 let discv5_socket_ipv4 = ipv4(listen_config);
380 let discv5_socket_ipv6 = ipv6(listen_config);
381
382 let discv5_port_ipv4 =
383 discv5_socket_ipv4.map(|socket| socket.port()).unwrap_or(DEFAULT_DISCOVERY_V5_PORT);
384 let discv5_addr_ipv4 = discv5_socket_ipv4.map(|socket| *socket.ip());
385 let discv5_port_ipv6 =
386 discv5_socket_ipv6.map(|socket| socket.port()).unwrap_or(DEFAULT_DISCOVERY_V5_PORT);
387 let discv5_addr_ipv6 = discv5_socket_ipv6.map(|socket| *socket.ip());
388
389 let (discv5_socket_ipv4, discv5_socket_ipv6) = discv5_sockets_wrt_rlpx_addr(
390 rlpx_addr,
391 discv5_addr_ipv4,
392 discv5_port_ipv4,
393 discv5_addr_ipv6,
394 discv5_port_ipv6,
395 );
396
397 ListenConfig::from_two_sockets(discv5_socket_ipv4, discv5_socket_ipv6)
398}
399
400pub fn discv5_sockets_wrt_rlpx_addr(
403 rlpx_addr: IpAddr,
404 discv5_addr_ipv4: Option<Ipv4Addr>,
405 discv5_port_ipv4: u16,
406 discv5_addr_ipv6: Option<Ipv6Addr>,
407 discv5_port_ipv6: u16,
408) -> (Option<SocketAddrV4>, Option<SocketAddrV6>) {
409 match rlpx_addr {
410 IpAddr::V4(rlpx_addr) => {
411 let discv5_socket_ipv6 =
412 discv5_addr_ipv6.map(|ip| SocketAddrV6::new(ip, discv5_port_ipv6, 0, 0));
413
414 if let Some(discv5_addr) = discv5_addr_ipv4 {
415 if discv5_addr != rlpx_addr {
416 warn!(target: "net::discv5",
417 %discv5_addr,
418 %rlpx_addr,
419 "Overwriting discv5 IPv4 address with RLPx IPv4 address, limited to one advertised IP address per IP version"
420 );
421 }
422 }
423
424 (Some(SocketAddrV4::new(rlpx_addr, discv5_port_ipv4)), discv5_socket_ipv6)
428 }
429 IpAddr::V6(rlpx_addr) => {
430 let discv5_socket_ipv4 =
431 discv5_addr_ipv4.map(|ip| SocketAddrV4::new(ip, discv5_port_ipv4));
432
433 if let Some(discv5_addr) = discv5_addr_ipv6 {
434 if discv5_addr != rlpx_addr {
435 warn!(target: "net::discv5",
436 %discv5_addr,
437 %rlpx_addr,
438 "Overwriting discv5 IPv6 address with RLPx IPv6 address, limited to one advertised IP address per IP version"
439 );
440 }
441 }
442
443 (discv5_socket_ipv4, Some(SocketAddrV6::new(rlpx_addr, discv5_port_ipv6, 0, 0)))
447 }
448 }
449}
450
451#[derive(Clone, Debug, PartialEq, Eq, Hash, Display)]
454pub enum BootNode {
455 #[display("{_0}")]
457 Enode(Multiaddr),
458 #[display("{_0:?}")]
460 Enr(discv5::Enr),
461}
462
463impl BootNode {
464 pub fn from_unsigned(node_record: NodeRecord) -> Result<Self, secp256k1::Error> {
467 let NodeRecord { address, udp_port, id, .. } = node_record;
468 let mut multi_address = Multiaddr::empty();
469 match address {
470 IpAddr::V4(ip) => multi_address.push(Protocol::Ip4(ip)),
471 IpAddr::V6(ip) => multi_address.push(Protocol::Ip6(ip)),
472 }
473
474 multi_address.push(Protocol::Udp(udp_port));
475 let id = discv4_id_to_multiaddr_id(id)?;
476 multi_address.push(Protocol::P2p(id));
477
478 Ok(Self::Enode(multi_address))
479 }
480}
481
482#[cfg(test)]
483mod test {
484 use super::*;
485 use alloy_primitives::hex;
486 use std::net::SocketAddrV4;
487
488 const MULTI_ADDRESSES: &str = "/ip4/184.72.129.189/udp/30301/p2p/16Uiu2HAmSG2hdLwyQHQmG4bcJBgD64xnW63WMTLcrNq6KoZREfGb,/ip4/3.231.11.52/udp/30301/p2p/16Uiu2HAmMy4V8bi3XP7KDfSLQcLACSvTLroRRwEsTyFUKo8NCkkp,/ip4/54.198.153.150/udp/30301/p2p/16Uiu2HAmSVsb7MbRf1jg3Dvd6a3n5YNqKQwn1fqHCFgnbqCsFZKe,/ip4/3.220.145.177/udp/30301/p2p/16Uiu2HAm74pBDGdQ84XCZK27GRQbGFFwQ7RsSqsPwcGmCR3Cwn3B,/ip4/3.231.138.188/udp/30301/p2p/16Uiu2HAmMnTiJwgFtSVGV14ZNpwAvS1LUoF4pWWeNtURuV6C3zYB";
489 const BOOT_NODES_OP_MAINNET_AND_BASE_MAINNET: &[&str] = &[
490 "enode://ca2774c3c401325850b2477fd7d0f27911efbf79b1e8b335066516e2bd8c4c9e0ba9696a94b1cb030a88eac582305ff55e905e64fb77fe0edcd70a4e5296d3ec@34.65.175.185:30305",
491 "enode://dd751a9ef8912be1bfa7a5e34e2c3785cc5253110bd929f385e07ba7ac19929fb0e0c5d93f77827291f4da02b2232240fbc47ea7ce04c46e333e452f8656b667@34.65.107.0:30305",
492 "enode://c5d289b56a77b6a2342ca29956dfd07aadf45364dde8ab20d1dc4efd4d1bc6b4655d902501daea308f4d8950737a4e93a4dfedd17b49cd5760ffd127837ca965@34.65.202.239:30305",
493 "enode://87a32fd13bd596b2ffca97020e31aef4ddcc1bbd4b95bb633d16c1329f654f34049ed240a36b449fda5e5225d70fe40bc667f53c304b71f8e68fc9d448690b51@3.231.138.188:30301",
494 "enode://ca21ea8f176adb2e229ce2d700830c844af0ea941a1d8152a9513b966fe525e809c3a6c73a2c18a12b74ed6ec4380edf91662778fe0b79f6a591236e49e176f9@184.72.129.189:30301",
495 "enode://acf4507a211ba7c1e52cdf4eef62cdc3c32e7c9c47998954f7ba024026f9a6b2150cd3f0b734d9c78e507ab70d59ba61dfe5c45e1078c7ad0775fb251d7735a2@3.220.145.177:30301",
496 "enode://8a5a5006159bf079d06a04e5eceab2a1ce6e0f721875b2a9c96905336219dbe14203d38f70f3754686a6324f786c2f9852d8c0dd3adac2d080f4db35efc678c5@3.231.11.52:30301",
497 "enode://cdadbe835308ad3557f9a1de8db411da1a260a98f8421d62da90e71da66e55e98aaa8e90aa7ce01b408a54e4bd2253d701218081ded3dbe5efbbc7b41d7cef79@54.198.153.150:30301",
498 ];
499
500 #[test]
501 fn parse_boot_nodes() {
502 const OP_SEPOLIA_CL_BOOTNODES: &str = "enr:-J64QBwRIWAco7lv6jImSOjPU_W266lHXzpAS5YOh7WmgTyBZkgLgOwo_mxKJq3wz2XRbsoBItbv1dCyjIoNq67mFguGAYrTxM42gmlkgnY0gmlwhBLSsHKHb3BzdGFja4S0lAUAiXNlY3AyNTZrMaEDmoWSi8hcsRpQf2eJsNUx-sqv6fH4btmo2HsAzZFAKnKDdGNwgiQGg3VkcIIkBg,enr:-J64QFa3qMsONLGphfjEkeYyF6Jkil_jCuJmm7_a42ckZeUQGLVzrzstZNb1dgBp1GGx9bzImq5VxJLP-BaptZThGiWGAYrTytOvgmlkgnY0gmlwhGsV-zeHb3BzdGFja4S0lAUAiXNlY3AyNTZrMaEDahfSECTIS_cXyZ8IyNf4leANlZnrsMEWTkEYxf4GMCmDdGNwgiQGg3VkcIIkBg";
503
504 let config = Config::builder((Ipv4Addr::UNSPECIFIED, 30303).into())
505 .add_cl_serialized_signed_boot_nodes(OP_SEPOLIA_CL_BOOTNODES)
506 .build();
507
508 let socket_1 = "18.210.176.114:9222".parse::<SocketAddrV4>().unwrap();
509 let socket_2 = "107.21.251.55:9222".parse::<SocketAddrV4>().unwrap();
510
511 for node in config.bootstrap_nodes {
512 let BootNode::Enr(node) = node else { panic!() };
513 assert!(
514 socket_1 == node.udp4_socket().unwrap() && socket_1 == node.tcp4_socket().unwrap() ||
515 socket_2 == node.udp4_socket().unwrap() &&
516 socket_2 == node.tcp4_socket().unwrap()
517 );
518 assert_eq!("84b4940500", hex::encode(node.get_raw_rlp("opstack").unwrap()));
519 }
520 }
521
522 #[test]
523 fn parse_enodes() {
524 let config = Config::builder((Ipv4Addr::UNSPECIFIED, 30303).into())
525 .add_serialized_unsigned_boot_nodes(BOOT_NODES_OP_MAINNET_AND_BASE_MAINNET)
526 .build();
527
528 let bootstrap_nodes =
529 config.bootstrap_nodes.into_iter().map(|node| format!("{node}")).collect::<Vec<_>>();
530
531 for node in MULTI_ADDRESSES.split(&[',']) {
532 assert!(bootstrap_nodes.contains(&node.to_string()));
533 }
534 }
535
536 #[test]
537 fn overwrite_ipv4_addr() {
538 let rlpx_addr: Ipv4Addr = "192.168.0.1".parse().unwrap();
539
540 let listen_config = DEFAULT_DISCOVERY_V5_LISTEN_CONFIG;
541
542 let amended_config = amend_listen_config_wrt_rlpx(&listen_config, rlpx_addr.into());
543
544 let config_socket_ipv4 = ipv4(&amended_config).unwrap();
545
546 assert_eq!(*config_socket_ipv4.ip(), rlpx_addr);
547 assert_eq!(config_socket_ipv4.port(), DEFAULT_DISCOVERY_V5_PORT);
548 assert_eq!(ipv6(&amended_config), ipv6(&listen_config));
549 }
550
551 #[test]
552 fn overwrite_ipv6_addr() {
553 let rlpx_addr: Ipv6Addr = "fe80::1".parse().unwrap();
554
555 let listen_config = DEFAULT_DISCOVERY_V5_LISTEN_CONFIG;
556
557 let amended_config = amend_listen_config_wrt_rlpx(&listen_config, rlpx_addr.into());
558
559 let config_socket_ipv6 = ipv6(&amended_config).unwrap();
560
561 assert_eq!(*config_socket_ipv6.ip(), rlpx_addr);
562 assert_eq!(config_socket_ipv6.port(), DEFAULT_DISCOVERY_V5_PORT);
563 assert_eq!(ipv4(&amended_config), ipv4(&listen_config));
564 }
565}