1use crate::{
4 cache::LruMap,
5 error::{NetworkError, ServiceKind},
6};
7use enr::Enr;
8use futures::{FutureExt, StreamExt};
9use reth_discv4::{DiscoveryUpdate, Discv4, Discv4Config};
10use reth_discv5::{DiscoveredPeer, Discv5};
11use reth_dns_discovery::{
12 DnsDiscoveryConfig, DnsDiscoveryHandle, DnsDiscoveryService, DnsNodeRecordUpdate, DnsResolver,
13};
14use reth_ethereum_forks::{EnrForkIdEntry, ForkId};
15use reth_net_nat::{NatResolver, ResolveNatInterval};
16use reth_network_api::{DiscoveredEvent, DiscoveryEvent};
17use reth_network_peers::{NodeRecord, PeerId};
18use reth_network_types::PeerAddr;
19use secp256k1::SecretKey;
20use socket2::{Domain, Protocol, Socket, Type};
21use std::{
22 collections::VecDeque,
23 net::{IpAddr, SocketAddr},
24 pin::Pin,
25 sync::Arc,
26 task::{ready, Context, Poll},
27 time::Duration,
28};
29use tokio::{net::UdpSocket, sync::mpsc, task::JoinHandle};
30use tokio_stream::{wrappers::ReceiverStream, Stream};
31use tracing::{debug, trace};
32
33pub const DEFAULT_MAX_CAPACITY_DISCOVERED_PEERS_CACHE: u32 = 10_000;
37
38const RESOLVE_EXTERNAL_IP_INTERVAL: Duration = Duration::from_secs(60 * 5);
42
43#[derive(Debug)]
48pub struct Discovery {
49 discovered_nodes: LruMap<PeerId, PeerAddr>,
53 local_enr: NodeRecord,
55 discv4: Option<Discv4>,
57 discv4_updates: Option<ReceiverStream<DiscoveryUpdate>>,
59 _discv4_service: Option<JoinHandle<()>>,
61 discv5: Option<Discv5>,
63 discv5_updates: Option<ReceiverStream<discv5::Event>>,
65 _discv5_forwarder: Option<JoinHandle<()>>,
68 _dns_discovery: Option<DnsDiscoveryHandle>,
70 dns_discovery_updates: Option<ReceiverStream<DnsNodeRecordUpdate>>,
72 _dns_disc_service: Option<JoinHandle<()>>,
74 queued_events: VecDeque<DiscoveryEvent>,
76 discovery_listeners: Vec<mpsc::UnboundedSender<DiscoveryEvent>>,
78 nat_resolver: Option<ResolveNatInterval>,
80}
81
82impl Discovery {
83 pub async fn new(
91 tcp_addr: SocketAddr,
92 discovery_v4_addr: SocketAddr,
93 sk: SecretKey,
94 discv4_config: Option<Discv4Config>,
95 mut discv5_config: Option<reth_discv5::Config>, dns_discovery_config: Option<DnsDiscoveryConfig>,
97 nat: Option<NatResolver>,
98 ) -> Result<Self, NetworkError> {
99 let local_enr =
101 NodeRecord::from_secret_key(discovery_v4_addr, &sk).with_tcp_port(tcp_addr.port());
102
103 let bind_socket = async |addr: SocketAddr| {
107 let result = match addr {
108 SocketAddr::V4(_) => UdpSocket::bind(addr).await,
109 SocketAddr::V6(_) => (|| {
110 let socket = Socket::new(Domain::IPV6, Type::DGRAM, Some(Protocol::UDP))?;
111 socket.set_only_v6(true)?;
112 socket.set_nonblocking(true)?;
113 socket.bind(&addr.into())?;
114 UdpSocket::from_std(socket.into())
115 })(),
116 };
117 result
118 .map(Arc::new)
119 .map_err(|err| NetworkError::from_io_error(err, ServiceKind::Discovery(addr)))
120 };
121
122 let (discv4, discv4_updates, _discv4_service, discv4_ingress, shared_sockets) =
130 if let Some(mut config) = discv4_config {
131 if let Some(discv5_config) = &mut discv5_config &&
132 discv5_config.has_matching_socket(discovery_v4_addr)
133 {
134 let socket = bind_socket(discovery_v4_addr).await?;
135 let shared_port = socket
136 .local_addr()
137 .map_err(|err| {
138 NetworkError::from_io_error(
139 err,
140 ServiceKind::Discovery(discovery_v4_addr),
141 )
142 })?
143 .port();
144
145 let listen_config = &discv5_config.discv5_config_mut().listen_config;
146 let secondary_addr = reth_discv5::config::sibling_addr(
147 listen_config,
148 discovery_v4_addr,
149 shared_port,
150 );
151 let secondary_socket = match secondary_addr {
152 Some(addr) => Some(bind_socket(addr).await?),
153 None => None,
154 };
155
156 if secondary_socket.is_some() {
157 let (tcp4, tcp6) = if tcp_addr.is_ipv4() {
159 (tcp_addr.port(), 0)
160 } else {
161 (0, tcp_addr.port())
162 };
163 config.add_eip868_pair("tcp", tcp4);
164 config.add_eip868_pair("tcp6", tcp6);
165 }
166
167 let (discv4, mut discv4_service, ingress) = Discv4::bind_shared(
168 socket.clone(),
169 secondary_socket.clone(),
170 local_enr,
171 sk,
172 config,
173 )
174 .map_err(|err| {
175 NetworkError::from_io_error(err, ServiceKind::Discovery(discovery_v4_addr))
176 })?;
177
178 let discv4_updates = discv4_service.update_stream();
179 let discv4_service = discv4_service.spawn();
180 debug!(target:"net", ?discovery_v4_addr, "started discovery v4 (shared port)");
181 (
182 Some(discv4),
183 Some(discv4_updates),
184 Some(discv4_service),
185 Some(ingress),
186 Some((socket, secondary_socket)),
187 )
188 } else {
189 let (discv4, mut discv4_service) =
190 Discv4::bind(discovery_v4_addr, local_enr, sk, config).await.map_err(
191 |err| {
192 NetworkError::from_io_error(
193 err,
194 ServiceKind::Discovery(discovery_v4_addr),
195 )
196 },
197 )?;
198 let discv4_updates = discv4_service.update_stream();
199 let discv4_service = discv4_service.spawn();
201
202 debug!(target:"net", ?discovery_v4_addr, "started discovery v4");
203
204 (Some(discv4), Some(discv4_updates), Some(discv4_service), None, None)
205 }
206 } else {
207 (None, None, None, None, None)
208 };
209
210 let nat_resolver = if discv4.is_none() &&
212 let Some(config) = &mut discv5_config
213 {
214 let config = config.discv5_config_mut();
215 nat.filter(|nat| match nat {
216 NatResolver::None => false,
217 NatResolver::ExternalIp(_) | NatResolver::ExternalAddr(_) => true,
219 _ => config.enr_update && config.auto_nat_listen_duration.is_none(),
222 })
223 .map(|nat| ResolveNatInterval::interval(nat, RESOLVE_EXTERNAL_IP_INTERVAL))
224 } else {
225 None
226 };
227
228 let (discv5, discv5_updates) = if let Some(mut config) = discv5_config {
230 set_bound_rlpx_port_if_unset(&mut config, tcp_addr.port());
232
233 if let Some((primary, secondary)) = shared_sockets {
234 let (ipv4, ipv6) = if discovery_v4_addr.is_ipv4() {
235 (Some(primary), secondary)
236 } else {
237 (secondary, Some(primary))
238 };
239 config.discv5_config_mut().listen_config =
240 discv5::ListenConfig::FromSockets { ipv4, ipv6 };
241 }
242
243 let (discv5, discv5_updates) = Discv5::start(&sk, config).await?;
244 debug!(target:"net", discovery_v5_enr=?discv5.local_enr(), "started discovery v5");
245 (Some(discv5), Some(discv5_updates))
246 } else {
247 (None, None)
248 };
249
250 let (discv5_updates, _discv5_forwarder) = match (discv4_ingress, discv5_updates) {
255 (Some(mut ingress), Some(mut updates)) => {
256 let (tx, rx) = mpsc::channel(updates.max_capacity());
257 let handle = tokio::spawn(
258 async move {
259 while let Some(event) = updates.recv().await {
260 if let discv5::Event::UnrecognizedFrame(frame) = &event {
261 ingress.handle_packet(&frame.packet, frame.src_address).await;
262 continue;
263 }
264 if tx.send(event).await.is_err() {
265 break;
266 }
267 }
268 }
269 .boxed(),
270 );
271 (Some(ReceiverStream::new(rx)), Some(handle))
272 }
273 (_, updates) => (updates.map(ReceiverStream::new), None),
274 };
275
276 let (_dns_discovery, dns_discovery_updates, _dns_disc_service) =
278 if let Some(dns_config) = dns_discovery_config {
279 let (mut service, dns_disc) = DnsDiscoveryService::new_pair(
280 Arc::new(DnsResolver::from_system_conf()?),
281 dns_config,
282 );
283 let dns_discovery_updates = service.node_record_stream();
284 let dns_disc_service = service.spawn();
285 (Some(dns_disc), Some(dns_discovery_updates), Some(dns_disc_service))
286 } else {
287 (None, None, None)
288 };
289
290 Ok(Self {
291 discovery_listeners: Default::default(),
292 local_enr,
293 discv4,
294 discv4_updates,
295 _discv4_service,
296 discv5,
297 discv5_updates,
298 _discv5_forwarder,
299 discovered_nodes: LruMap::new(DEFAULT_MAX_CAPACITY_DISCOVERED_PEERS_CACHE),
300 queued_events: Default::default(),
301 _dns_disc_service,
302 _dns_discovery,
303 dns_discovery_updates,
304 nat_resolver,
305 })
306 }
307
308 pub(crate) fn add_listener(&mut self, tx: mpsc::UnboundedSender<DiscoveryEvent>) {
310 self.discovery_listeners.push(tx);
311 }
312
313 #[inline]
315 fn notify_listeners(&mut self, event: &DiscoveryEvent) {
316 self.discovery_listeners.retain_mut(|listener| listener.send(event.clone()).is_ok());
317 }
318
319 pub(crate) fn update_fork_id(&self, fork_id: ForkId) {
321 if let Some(discv4) = &self.discv4 {
322 discv4.set_eip868_rlp(b"eth".to_vec(), EnrForkIdEntry::from(fork_id))
324 }
325 if let Some(discv5) = &self.discv5 {
326 discv5
327 .encode_and_set_eip868_in_local_enr(b"eth".to_vec(), EnrForkIdEntry::from(fork_id))
328 }
329 }
330
331 pub(crate) fn ban_ip(&self, ip: IpAddr) {
333 if let Some(discv4) = &self.discv4 {
334 discv4.ban_ip(ip)
335 }
336 if let Some(discv5) = &self.discv5 {
337 discv5.ban_ip(ip)
338 }
339 }
340
341 pub(crate) fn ban(&self, peer_id: PeerId, ip: IpAddr) {
343 if let Some(discv4) = &self.discv4 {
344 discv4.ban(peer_id, ip)
345 }
346 if let Some(discv5) = &self.discv5 {
347 discv5.ban(peer_id, ip)
348 }
349 }
350
351 pub fn discv4(&self) -> Option<Discv4> {
353 self.discv4.clone()
354 }
355
356 pub(crate) const fn local_id(&self) -> PeerId {
358 self.local_enr.id }
360
361 pub(crate) fn add_discv4_node(&self, node: NodeRecord) {
363 if let Some(discv4) = &self.discv4 {
364 discv4.add_node(node);
365 }
366 }
367
368 pub fn discv5(&self) -> Option<Discv5> {
370 self.discv5.clone()
371 }
372
373 pub(crate) fn add_discv5_node(&self, enr: Enr<SecretKey>) -> Result<(), NetworkError> {
375 if let Some(discv5) = &self.discv5 {
376 discv5.add_node(enr).map_err(NetworkError::Discv5Error)?;
377 }
378
379 Ok(())
380 }
381
382 fn on_node_record_update(&mut self, record: NodeRecord, fork_id: Option<ForkId>) {
384 let peer_id = record.id;
385 let tcp_addr = record.tcp_addr();
386 if tcp_addr.port() == 0 {
387 return
389 }
390 let udp_addr = record.udp_addr();
391 let addr = PeerAddr::new(tcp_addr, Some(udp_addr));
392 _ =
393 self.discovered_nodes.get_or_insert(peer_id, || {
394 self.queued_events.push_back(DiscoveryEvent::NewNode(
395 DiscoveredEvent::EventQueued { peer_id, addr, fork_id },
396 ));
397
398 addr
399 })
400 }
401
402 fn on_discv4_update(&mut self, update: DiscoveryUpdate) {
403 match update {
404 DiscoveryUpdate::Added(record) | DiscoveryUpdate::DiscoveredAtCapacity(record) => {
405 self.on_node_record_update(record, None);
406 }
407 DiscoveryUpdate::EnrForkId(node, fork_id) => {
408 self.queued_events.push_back(DiscoveryEvent::EnrForkId(node, fork_id))
409 }
410 DiscoveryUpdate::Removed(peer_id) => {
411 self.discovered_nodes.remove(&peer_id);
412 }
413 DiscoveryUpdate::Batch(updates) => {
414 for update in updates {
415 self.on_discv4_update(update);
416 }
417 }
418 }
419 }
420
421 pub(crate) fn poll(&mut self, cx: &mut Context<'_>) -> Poll<DiscoveryEvent> {
422 loop {
423 if let Some(event) = self.queued_events.pop_front() {
425 self.notify_listeners(&event);
426 return Poll::Ready(event)
427 }
428
429 while let Some(Poll::Ready(Some(update))) =
431 self.discv4_updates.as_mut().map(|updates| updates.poll_next_unpin(cx))
432 {
433 self.on_discv4_update(update)
434 }
435
436 while let Some(Poll::Ready(Some(update))) =
438 self.discv5_updates.as_mut().map(|updates| updates.poll_next_unpin(cx))
439 {
440 if let Some(discv5) = self.discv5.as_mut() &&
441 let Some(DiscoveredPeer { node_record, fork_id }) =
442 discv5.on_discv5_update(update)
443 {
444 self.on_node_record_update(node_record, fork_id);
445 }
446 }
447
448 while let Some(Poll::Ready(Some(update))) =
450 self.dns_discovery_updates.as_mut().map(|updates| updates.poll_next_unpin(cx))
451 {
452 self.add_discv4_node(update.node_record);
453 if let Err(err) = self.add_discv5_node(update.enr) {
454 trace!(target: "net::discovery",
455 %err,
456 "failed adding node discovered by dns to discv5"
457 );
458 }
459 self.on_node_record_update(update.node_record, update.fork_id);
460 }
461
462 while let Some(Poll::Ready(ip)) =
463 self.nat_resolver.as_mut().map(|resolver| resolver.poll_tick(cx))
464 {
465 if let Some(ip) = ip {
466 self.on_external_ip(ip);
467 }
468 }
469
470 if self.queued_events.is_empty() {
471 return Poll::Pending
472 }
473 }
474 }
475
476 fn on_external_ip(&self, external_ip: IpAddr) {
481 let Some(discv5) = &self.discv5 else { return };
482 let enr = discv5.local_enr();
483 let result = match external_ip {
486 IpAddr::V4(ip) if enr.udp4().is_some() && enr.ip4() != Some(ip) => {
487 discv5.with_discv5(|discv5| discv5.enr_insert("ip", &ip))
488 }
489 IpAddr::V6(ip) if enr.udp6().is_some() && enr.ip6() != Some(ip) => {
490 discv5.with_discv5(|discv5| discv5.enr_insert("ip6", &ip))
491 }
492 _ => return,
493 };
494 match result {
495 Ok(_) => debug!(target: "net::discovery", ?external_ip, "Updated external IP"),
496 Err(err) => {
497 debug!(target: "net::discovery", ?external_ip, %err, "Failed to update external IP");
498 }
499 }
500 }
501}
502
503const fn set_bound_rlpx_port_if_unset(config: &mut reth_discv5::Config, port: u16) {
504 if config.rlpx_socket().port() == 0 {
505 config.set_rlpx_port(port);
506 }
507}
508
509impl Drop for Discovery {
510 fn drop(&mut self) {
511 if let Some(discv4) = &self.discv4 {
512 discv4.terminate();
513 }
514 if let Some(handle) = self._discv4_service.take() {
515 handle.abort();
516 }
517 if let Some(handle) = self._discv5_forwarder.take() {
518 handle.abort();
519 }
520 if let Some(handle) = self._dns_disc_service.take() {
521 handle.abort();
522 }
523 }
524}
525
526impl Stream for Discovery {
527 type Item = DiscoveryEvent;
528
529 fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
530 Poll::Ready(Some(ready!(self.get_mut().poll(cx))))
531 }
532}
533
534#[cfg(test)]
535impl Discovery {
536 pub(crate) fn noop() -> Self {
540 let (_discovery_listeners, _): (mpsc::UnboundedSender<DiscoveryEvent>, _) =
541 mpsc::unbounded_channel();
542
543 Self {
544 discovered_nodes: LruMap::new(0),
545 local_enr: NodeRecord {
546 address: IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
547 tcp_port: 0,
548 udp_port: 0,
549 id: PeerId::random(),
550 },
551 discv4: Default::default(),
552 discv4_updates: Default::default(),
553 _discv4_service: Default::default(),
554 _discv5_forwarder: None,
555 discv5: None,
556 discv5_updates: None,
557 queued_events: Default::default(),
558 _dns_discovery: None,
559 dns_discovery_updates: None,
560 _dns_disc_service: None,
561 discovery_listeners: Default::default(),
562 nat_resolver: None,
563 }
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570 use std::{
571 net::{Ipv4Addr, Ipv6Addr, SocketAddrV4},
572 time::Duration,
573 };
574
575 #[tokio::test(flavor = "multi_thread")]
576 async fn test_discovery_setup() {
577 let (secret_key, _) = secp256k1::generate_keypair(&mut rand::rng());
578 let discovery_addr = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0));
579 let _discovery = Discovery::new(
580 discovery_addr,
581 discovery_addr,
582 secret_key,
583 Default::default(),
584 None,
585 Default::default(),
586 None,
587 )
588 .await
589 .unwrap();
590 }
591
592 use reth_discv4::Discv4ConfigBuilder;
593 use reth_discv5::{enr::EnrCombinedKeyWrapper, enr_to_discv4_id};
594 use tracing::trace;
595
596 async fn start_discovery_node(udp_port_discv4: u16, udp_port_discv5: u16) -> Discovery {
597 let secret_key = SecretKey::new(&mut rand::rng());
598
599 let discv4_addr = format!("127.0.0.1:{udp_port_discv4}").parse().unwrap();
600 let discv5_addr: SocketAddr = format!("127.0.0.1:{udp_port_discv5}").parse().unwrap();
601
602 let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
604
605 let discv5_listen_config = discv5::ListenConfig::from(discv5_addr);
606 let discv5_config = reth_discv5::Config::builder(discv5_addr)
607 .discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
608 .build();
609
610 Discovery::new(
611 discv4_addr,
612 discv4_addr,
613 secret_key,
614 Some(discv4_config),
615 Some(discv5_config),
616 None,
617 None,
618 )
619 .await
620 .expect("should build discv5 with discv4 downgrade")
621 }
622
623 async fn start_discv5_only(config: reth_discv5::Config, nat: Option<NatResolver>) -> Discovery {
624 let secret_key = SecretKey::new(&mut rand::rng());
625 Discovery::new(
626 "127.0.0.1:30303".parse().unwrap(),
627 "127.0.0.1:0".parse().unwrap(),
628 secret_key,
629 None,
630 Some(config),
631 None,
632 nat,
633 )
634 .await
635 .expect("should start discv5")
636 }
637
638 #[tokio::test]
639 async fn discv5_only_advertises_resolved_external_ip() {
640 let socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap());
641 let udp_port = socket.local_addr().unwrap().port();
642 let external_ip = "203.0.113.7".parse().unwrap();
643 let mut config = reth_discv5::Config::builder("127.0.0.1:30309".parse().unwrap()).build();
645 let discv5_config = config.discv5_config_mut();
647 discv5_config.listen_config =
648 discv5::ListenConfig::FromSockets { ipv4: Some(socket), ipv6: None };
649 discv5_config.enr_update = false;
651 let mut discovery =
652 start_discv5_only(config, Some(NatResolver::ExternalIp(external_ip))).await;
653 let discv5 = discovery.discv5().unwrap();
654 assert!(discv5.local_enr().ip4().is_none());
655
656 tokio::time::timeout(
657 Duration::from_secs(5),
658 futures::future::poll_fn(|cx| {
659 let _ = discovery.poll(cx);
660 if discv5.node_record().is_some_and(|record| record.address == external_ip) {
661 Poll::Ready(())
662 } else {
663 Poll::Pending
664 }
665 }),
666 )
667 .await
668 .expect("external IP should be advertised");
669
670 let record = discv5.node_record().unwrap();
671 assert_eq!(record.address, external_ip);
672 assert_eq!(record.udp_port, udp_port);
673 assert_eq!(record.tcp_port, 30309);
674
675 let seq = discv5.local_enr().seq();
676 discovery.on_external_ip(external_ip);
677 assert_eq!(discv5.local_enr().seq(), seq);
678 discovery.on_external_ip("2001:db8::7".parse().unwrap());
679 assert_eq!(discv5.local_enr().seq(), seq);
680
681 discv5.with_discv5(|discv5| {
683 discv5.update_local_enr_socket((Ipv4Addr::LOCALHOST, 30310).into(), false);
684 });
685 discovery.on_external_ip(external_ip);
686 assert_eq!(discv5.node_record().unwrap(), NodeRecord { udp_port: 30310, ..record });
687 }
688
689 #[tokio::test]
690 async fn discv5_only_respects_enr_update_policy() {
691 for (enr_update, auto_nat_listen_duration) in
692 [(false, None), (true, Some(Duration::from_secs(300)))]
693 {
694 let mut config = reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 0).into()).build();
695 let discv5_config = config.discv5_config_mut();
696 discv5_config.listen_config =
697 discv5::ListenConfig::Ipv4 { ip: Ipv4Addr::LOCALHOST, port: 0 };
698 discv5_config.enr_update = enr_update;
699 discv5_config.auto_nat_listen_duration = auto_nat_listen_duration;
701 let discovery = start_discv5_only(config, Some(NatResolver::Any)).await;
702
703 assert!(discovery.nat_resolver.is_none());
705 }
706 }
707
708 #[test]
709 fn discv5_enr_advertises_bound_rlpx_port() {
710 let secret_key = SecretKey::new(&mut rand::rng());
711
712 let bound_rlpx_port = 30307;
713 let discv5_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
714 let mut discv5_config = reth_discv5::Config::builder((Ipv4Addr::LOCALHOST, 0).into())
715 .discv5_config(discv5::ConfigBuilder::new(discv5_addr.into()).build())
716 .build();
717
718 set_bound_rlpx_port_if_unset(&mut discv5_config, bound_rlpx_port);
719
720 let (enr, _, _, _) = reth_discv5::build_local_enr(&secret_key, &discv5_config);
721 assert_eq!(enr.tcp4(), Some(bound_rlpx_port));
722 }
723
724 #[test]
725 fn discv5_enr_preserves_configured_rlpx_port() {
726 let secret_key = SecretKey::new(&mut rand::rng());
727
728 let advertised_addr: SocketAddr = "127.0.0.1:30308".parse().unwrap();
729 let discv5_addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
730 let mut discv5_config = reth_discv5::Config::builder(advertised_addr)
731 .discv5_config(discv5::ConfigBuilder::new(discv5_addr.into()).build())
732 .build();
733
734 set_bound_rlpx_port_if_unset(&mut discv5_config, 30307);
735
736 let (enr, _, _, _) = reth_discv5::build_local_enr(&secret_key, &discv5_config);
737 assert_eq!(enr.tcp4(), Some(advertised_addr.port()));
738 }
739
740 #[tokio::test(flavor = "multi_thread")]
741 async fn discv5_and_discv4_same_pk() {
742 reth_tracing::init_test_tracing();
743
744 let mut node_1 = start_discovery_node(40014, 40015).await;
746 let discv4_enr_1 = node_1.discv4.as_ref().unwrap().node_record();
747 let discv5_enr_node_1 =
748 node_1.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
749 let discv4_id_1 = discv4_enr_1.id;
750 let discv5_id_1 = discv5_enr_node_1.node_id();
751
752 let mut node_2 = start_discovery_node(40024, 40025).await;
753 let discv4_enr_2 = node_2.discv4.as_ref().unwrap().node_record();
754 let discv5_enr_node_2 =
755 node_2.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
756 let discv4_id_2 = discv4_enr_2.id;
757 let discv5_id_2 = discv5_enr_node_2.node_id();
758
759 trace!(target: "net::discovery::tests",
760 node_1_node_id=format!("{:#}", discv5_id_1),
761 node_2_node_id=format!("{:#}", discv5_id_2),
762 "started nodes"
763 );
764
765 assert_eq!(discv4_id_1, enr_to_discv4_id(&discv5_enr_node_1).unwrap());
769 assert_eq!(discv4_id_2, enr_to_discv4_id(&discv5_enr_node_2).unwrap());
770
771 node_1.add_discv4_node(discv4_enr_2);
773
774 let event_node_1 = node_1.next().await.unwrap();
776 let event_node_2 = node_2.next().await.unwrap();
777
778 assert_eq!(
779 DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
780 peer_id: discv4_id_2,
781 addr: PeerAddr::new(discv4_enr_2.tcp_addr(), Some(discv4_enr_2.udp_addr())),
782 fork_id: None
783 }),
784 event_node_1
785 );
786 assert_eq!(
787 DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
788 peer_id: discv4_id_1,
789 addr: PeerAddr::new(discv4_enr_1.tcp_addr(), Some(discv4_enr_1.udp_addr())),
790 fork_id: None
791 }),
792 event_node_2
793 );
794
795 assert_eq!(1, node_1.discovered_nodes.len());
796 assert_eq!(1, node_2.discovered_nodes.len());
797
798 node_1.add_discv5_node(EnrCombinedKeyWrapper(discv5_enr_node_2.clone()).into()).unwrap();
800 assert!(node_1
802 .discv5
803 .as_ref()
804 .unwrap()
805 .with_discv5(|discv5| discv5.table_entries_id().contains(&discv5_id_2)));
806
807 node_1
809 .discv5
810 .as_ref()
811 .unwrap()
812 .with_discv5(|discv5| discv5.send_ping(discv5_enr_node_2.clone()))
813 .await
814 .unwrap();
815
816 assert_eq!(1, node_1.discovered_nodes.len());
819 assert_eq!(1, node_2.discovered_nodes.len());
820 }
821
822 async fn start_shared_port_node(port: u16) -> Discovery {
824 let secret_key = SecretKey::new(&mut rand::rng());
825 let disc_addr: SocketAddr = format!("127.0.0.1:{port}").parse().unwrap();
826 let tcp_addr: SocketAddr = "127.0.0.1:30303".parse().unwrap();
829
830 let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
831
832 let discv5_listen_config = discv5::ListenConfig::from(disc_addr);
833 let discv5_config = reth_discv5::Config::builder(tcp_addr)
834 .discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
835 .build();
836
837 Discovery::new(
839 tcp_addr,
840 disc_addr,
841 secret_key,
842 Some(discv4_config),
843 Some(discv5_config),
844 None,
845 None,
846 )
847 .await
848 .expect("should start with shared port")
849 }
850
851 #[tokio::test(flavor = "multi_thread")]
852 async fn test_shared_port_setup() {
853 reth_tracing::init_test_tracing();
854
855 let node = start_shared_port_node(0).await;
857
858 assert!(node.discv4.is_some(), "discv4 should be running");
860 assert!(node.discv5.is_some(), "discv5 should be running");
861 }
862
863 #[tokio::test(flavor = "multi_thread")]
864 async fn test_shared_port_discv5_discovery() {
865 reth_tracing::init_test_tracing();
866
867 let mut node_1 = start_shared_port_node(0).await;
868 let mut node_2 = start_shared_port_node(0).await;
869
870 let discv5_enr_1 = node_1.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
871 let discv5_enr_2 = node_2.discv5.as_ref().unwrap().with_discv5(|discv5| discv5.local_enr());
872
873 let peer_id_1 = enr_to_discv4_id(&discv5_enr_1).unwrap();
874 let peer_id_2 = enr_to_discv4_id(&discv5_enr_2).unwrap();
875
876 node_1.add_discv5_node(EnrCombinedKeyWrapper(discv5_enr_2.clone()).into()).unwrap();
880 node_1
881 .discv5
882 .as_ref()
883 .unwrap()
884 .with_discv5(|discv5| discv5.send_ping(discv5_enr_2))
885 .await
886 .unwrap();
887
888 let mut event_1 = None;
891 let mut event_2 = None;
892 let timeout = tokio::time::sleep(std::time::Duration::from_secs(5));
893 tokio::pin!(timeout);
894 loop {
895 tokio::select! {
896 ev = node_1.next(), if event_1.is_none() => {
897 event_1 = ev;
898 }
899 ev = node_2.next(), if event_2.is_none() => {
900 event_2 = ev;
901 }
902 _ = &mut timeout => {
903 panic!("timed out waiting for discv5 discovery events");
904 }
905 }
906 if event_1.is_some() && event_2.is_some() {
907 break;
908 }
909 }
910
911 assert!(matches!(
912 event_1.unwrap(),
913 DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, .. })
914 if peer_id == peer_id_2
915 ));
916 assert!(matches!(
917 event_2.unwrap(),
918 DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued { peer_id, .. })
919 if peer_id == peer_id_1
920 ));
921 }
922
923 #[tokio::test(flavor = "multi_thread")]
924 async fn test_shared_port_discv4_discovery() {
925 reth_tracing::init_test_tracing();
926
927 let mut node_1 = start_shared_port_node(0).await;
928 let mut node_2 = start_shared_port_node(0).await;
929
930 let enr_1 = node_1.discv4.as_ref().unwrap().node_record();
931 let enr_2 = node_2.discv4.as_ref().unwrap().node_record();
932
933 node_1.add_discv4_node(enr_2);
935
936 let event_1 = node_1.next().await.unwrap();
938 let event_2 = node_2.next().await.unwrap();
939
940 assert_eq!(
941 DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
942 peer_id: enr_2.id,
943 addr: PeerAddr::new(enr_2.tcp_addr(), Some(enr_2.udp_addr())),
944 fork_id: None
945 }),
946 event_1
947 );
948 assert_eq!(
949 DiscoveryEvent::NewNode(DiscoveredEvent::EventQueued {
950 peer_id: enr_1.id,
951 addr: PeerAddr::new(enr_1.tcp_addr(), Some(enr_1.udp_addr())),
952 fork_id: None
953 }),
954 event_2
955 );
956 }
957
958 #[tokio::test(flavor = "multi_thread")]
962 async fn test_shared_port_dual_stack() {
963 reth_tracing::init_test_tracing();
964
965 let probe = UdpSocket::bind("0.0.0.0:0").await.expect("probe bind");
967 let port = probe.local_addr().unwrap().port();
968 drop(probe);
969
970 let secret_key = SecretKey::new(&mut rand::rng());
971 let v4_addr: SocketAddr = format!("0.0.0.0:{port}").parse().unwrap();
972 let tcp_addr: SocketAddr = "0.0.0.0:30303".parse().unwrap();
973
974 let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
975
976 let discv5_listen_config = discv5::ListenConfig::DualStack {
977 ipv4: std::net::Ipv4Addr::UNSPECIFIED,
978 ipv4_port: port,
979 ipv6: std::net::Ipv6Addr::UNSPECIFIED,
980 ipv6_port: port,
981 };
982 let discv5_config = reth_discv5::Config::builder(tcp_addr)
983 .discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
984 .build();
985
986 Discovery::new(
987 tcp_addr,
988 v4_addr,
989 secret_key,
990 Some(discv4_config),
991 Some(discv5_config),
992 None,
993 None,
994 )
995 .await
996 .expect("discovery should start with shared port + dual-stack");
997 }
998
999 #[tokio::test(flavor = "multi_thread")]
1002 async fn test_shared_port_dual_stack_answers_opposite_family_discv4() {
1003 reth_tracing::init_test_tracing();
1004
1005 let Ok(probe6) = UdpSocket::bind("[::1]:0").await else {
1006 return;
1008 };
1009 drop(probe6);
1010
1011 let ips = [IpAddr::V4(Ipv4Addr::LOCALHOST), IpAddr::V6(Ipv6Addr::LOCALHOST)];
1012 for primary_ip in ips {
1013 for tcp_ip in ips {
1014 assert_shared_port_dual_stack(primary_ip, tcp_ip).await;
1015 }
1016 }
1017 }
1018
1019 async fn assert_shared_port_dual_stack(primary_ip: IpAddr, tcp_ip: IpAddr) {
1020 let secondary_ip = if primary_ip.is_ipv4() {
1021 IpAddr::V6(Ipv6Addr::LOCALHOST)
1022 } else {
1023 IpAddr::V4(Ipv4Addr::LOCALHOST)
1024 };
1025 let probe = UdpSocket::bind((primary_ip, 0)).await.expect("probe bind");
1026 let port = probe.local_addr().unwrap().port();
1027 drop(probe);
1028
1029 let secret_key = SecretKey::new(&mut rand::rng());
1030 let node_id = reth_network_peers::pk2id(&secret_key.public_key());
1031 let discovery_addr = SocketAddr::new(primary_ip, port);
1032 let tcp_addr = SocketAddr::new(tcp_ip, 30303);
1033
1034 let discv4_config = Discv4ConfigBuilder::default().external_ip_resolver(None).build();
1035 let other_port = if port == u16::MAX { port - 1 } else { port + 1 };
1037 let discv5_listen_config = discv5::ListenConfig::DualStack {
1038 ipv4: Ipv4Addr::LOCALHOST,
1039 ipv4_port: if primary_ip.is_ipv4() { port } else { other_port },
1040 ipv6: Ipv6Addr::LOCALHOST,
1041 ipv6_port: if primary_ip.is_ipv6() { port } else { other_port },
1042 };
1043 let discv5_config = reth_discv5::Config::builder(tcp_addr)
1044 .discv5_config(discv5::ConfigBuilder::new(discv5_listen_config).build())
1045 .build();
1046
1047 let _node = Discovery::new(
1048 tcp_addr,
1049 discovery_addr,
1050 secret_key,
1051 Some(discv4_config),
1052 Some(discv5_config),
1053 None,
1054 None,
1055 )
1056 .await
1057 .expect("discovery should start with shared port + dual-stack");
1058
1059 let probe_sk = SecretKey::new(&mut rand::rng());
1061 let secondary_node =
1062 NodeRecord { address: secondary_ip, udp_port: port, tcp_port: port, id: node_id };
1063 let probe_config = Discv4ConfigBuilder::default()
1064 .external_ip_resolver(None)
1065 .add_boot_node(secondary_node)
1066 .build();
1067 let probe_addr = SocketAddr::new(secondary_ip, 0);
1068 let probe_enr = NodeRecord::from_secret_key(probe_addr, &probe_sk);
1069 let (_probe, mut probe_service) =
1070 Discv4::bind(probe_addr, probe_enr, probe_sk, probe_config).await.unwrap();
1071 let mut updates = probe_service.update_stream();
1072 probe_service.spawn();
1073
1074 let added = tokio::time::timeout(Duration::from_secs(5), async {
1076 while let Some(update) = updates.next().await {
1077 if let DiscoveryUpdate::Added(record) = update &&
1078 record.id == node_id
1079 {
1080 return true
1081 }
1082 }
1083 false
1084 })
1085 .await
1086 .unwrap_or(false);
1087 assert!(added, "node should answer discv4 over its secondary family");
1088
1089 for peer_ip in [primary_ip, secondary_ip] {
1090 let node = NodeRecord { address: peer_ip, ..secondary_node };
1091 let socket = UdpSocket::bind((peer_ip, 0)).await.unwrap();
1092 let secret_key = SecretKey::new(&mut rand::rng());
1093 let record = NodeRecord::from_secret_key(socket.local_addr().unwrap(), &secret_key);
1094 let ping = reth_discv4::proto::Message::Ping(reth_discv4::proto::Ping {
1095 from: record.into(),
1096 to: node.into(),
1097 expire: u32::MAX.into(),
1098 enr_sq: None,
1099 });
1100 let (packet, _) = ping.encode(&secret_key);
1101 socket.send_to(&packet, node.udp_addr()).await.unwrap();
1102 let ping = tokio::time::timeout(Duration::from_secs(5), async {
1103 let mut buf = [0u8; 1280];
1104 loop {
1105 let (len, _) = socket.recv_from(&mut buf).await.unwrap();
1106 if let reth_discv4::proto::Message::Ping(ping) =
1107 reth_discv4::proto::Message::decode(&buf[..len]).unwrap().msg
1108 {
1109 break ping
1110 }
1111 }
1112 })
1113 .await
1114 .expect("node should ping the peer");
1115 assert_eq!(
1116 ping.from.tcp_port,
1117 if peer_ip == tcp_ip { tcp_addr.port() } else { 0 },
1118 "primary discovery IP: {primary_ip}, TCP IP: {tcp_ip}, peer IP: {peer_ip}"
1119 );
1120 }
1121 }
1122}