1use std::{
11 cmp::min,
12 collections::{BTreeSet, HashSet},
13 fmt::Debug,
14 net::{IpAddr, ToSocketAddrs},
15 num::NonZeroUsize,
16 sync::{
17 Arc,
18 atomic::{AtomicBool, AtomicU64, Ordering},
19 },
20 time::Duration,
21};
22#[cfg(feature = "hotshot-testing")]
23use std::{collections::HashMap, str::FromStr};
24
25use alloy::primitives::U256;
26use anyhow::{Context, anyhow};
27use async_lock::RwLock;
28use async_trait::async_trait;
29use bimap::BiMap;
30use futures::future::join_all;
31#[cfg(feature = "hotshot-testing")]
32use hotshot_libp2p_networking::network::behaviours::dht::store::persistent::DhtNoPersistence;
33pub use hotshot_libp2p_networking::network::{GossipConfig, RequestResponseConfig};
34use hotshot_libp2p_networking::{
35 network::{
36 DEFAULT_REPLICATION_FACTOR,
37 NetworkEvent::{self, DirectRequest, DirectResponse, GossipMsg},
38 NetworkNodeConfig, NetworkNodeConfigBuilder, NetworkNodeHandle, NetworkNodeReceiver,
39 behaviours::dht::{
40 record::{Namespace, RecordKey, RecordValue},
41 store::persistent::DhtPersistentStorage,
42 },
43 log_summary::LogEvent,
44 spawn_network_node,
45 transport::construct_auth_message,
46 },
47 reexport::Multiaddr,
48};
49use hotshot_types::{
50 BoxSyncFuture, boxed_sync,
51 constants::LOOK_AHEAD,
52 data::{EpochNumber, ViewNumber},
53 network::NetworkConfig,
54 traits::{
55 metrics::{Counter, Gauge, Metrics, NoMetrics},
56 network::{ConnectedNetwork, NetworkError, Topic},
57 node_implementation::NodeType,
58 signature_key::{PrivateSignatureKey, SignatureKey},
59 },
60};
61#[cfg(feature = "hotshot-testing")]
62use hotshot_types::{
63 PeerConnectInfo,
64 traits::network::{AsyncGenerator, NetworkReliability, TestableNetworkingImplementation},
65};
66use libp2p_identity::{
67 Keypair, PeerId,
68 ed25519::{self, SecretKey},
69};
70use serde::Serialize;
71use tokio::{
72 select, spawn,
73 sync::{
74 Mutex,
75 mpsc::{Receiver, Sender, channel, error::TrySendError},
76 },
77 time::sleep,
78};
79use tracing::{debug, error, info, instrument, trace, warn};
80
81use crate::{BroadcastDelay, EpochMembershipCoordinator};
82
83#[derive(Clone, Debug)]
85pub struct Libp2pMetricsValue {
86 pub num_connected_peers: Box<dyn Gauge>,
88 pub num_failed_messages: Box<dyn Counter>,
90 pub is_ready: Box<dyn Gauge>,
92}
93
94impl Libp2pMetricsValue {
95 pub fn new(metrics: &dyn Metrics) -> Self {
97 let subgroup = metrics.subgroup("libp2p".into());
99
100 Self {
102 num_connected_peers: subgroup.create_gauge("num_connected_peers".into(), None),
103 num_failed_messages: subgroup.create_counter("num_failed_messages".into(), None),
104 is_ready: subgroup.create_gauge("is_ready".into(), None),
105 }
106 }
107}
108
109impl Default for Libp2pMetricsValue {
110 fn default() -> Self {
112 Self::new(&*NoMetrics::boxed())
113 }
114}
115
116pub type BootstrapAddrs = Arc<RwLock<Vec<(PeerId, Multiaddr)>>>;
119
120pub const QC_TOPIC: &str = "global";
122
123#[derive(Serialize)]
132pub struct Empty {
133 byte: u8,
136}
137
138impl<T: NodeType> Debug for Libp2pNetwork<T> {
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 f.debug_struct("Libp2p").field("inner", &"inner").finish()
141 }
142}
143
144pub type PeerInfoVec = Arc<RwLock<Vec<(PeerId, Multiaddr)>>>;
146
147#[derive(Debug)]
149struct Libp2pNetworkInner<T: NodeType> {
150 pk: T::SignatureKey,
152 handle: Arc<NetworkNodeHandle<T>>,
154 receiver: Mutex<Receiver<Vec<u8>>>,
156 sender: Sender<Vec<u8>>,
158 node_lookup_send: Sender<Option<(ViewNumber, T::SignatureKey)>>,
160 bootstrap_addrs: PeerInfoVec,
163 is_ready: Arc<AtomicBool>,
165 dht_timeout: Duration,
167 is_bootstrapped: Arc<AtomicBool>,
169 metrics: Libp2pMetricsValue,
171 subscribed_topics: HashSet<String>,
173 latest_seen_view: Arc<AtomicU64>,
177 #[cfg(feature = "hotshot-testing")]
178 reliability_config: Option<Box<dyn NetworkReliability>>,
180 kill_switch: Sender<()>,
182}
183
184#[derive(Clone)]
187pub struct Libp2pNetwork<T: NodeType> {
188 inner: Arc<Libp2pNetworkInner<T>>,
190}
191
192#[cfg(feature = "hotshot-testing")]
193impl<T: NodeType> TestableNetworkingImplementation<T> for Libp2pNetwork<T> {
194 #[allow(clippy::panic, clippy::too_many_lines)]
204 fn generator(
205 expected_node_count: usize,
206 num_bootstrap: usize,
207 _network_id: usize,
208 da_committee_size: usize,
209 reliability_config: Option<Box<dyn NetworkReliability>>,
210 _secondary_network_delay: Duration,
211 _connect_infos: &mut HashMap<T::SignatureKey, PeerConnectInfo>,
212 ) -> AsyncGenerator<Arc<Self>> {
213 assert!(
214 da_committee_size <= expected_node_count,
215 "DA committee size must be less than or equal to total # nodes"
216 );
217 let bootstrap_addrs: PeerInfoVec = Arc::default();
218 let node_ids: Arc<RwLock<HashSet<u64>>> = Arc::default();
219
220 Box::pin({
223 move |node_id| {
224 info!(
225 "GENERATOR: Node id {:?}, is bootstrap: {:?}",
226 node_id,
227 node_id < num_bootstrap as u64
228 );
229
230 let port = std::net::UdpSocket::bind("127.0.0.1:0")
232 .expect("UDP socket should bind")
233 .local_addr()
234 .expect("UDP socket should have local addr")
235 .port();
236
237 let addr =
238 Multiaddr::from_str(&format!("/ip4/127.0.0.1/udp/{port}/quic-v1")).unwrap();
239
240 let privkey = T::SignatureKey::generated_from_seed_indexed([0u8; 32], node_id).1;
242 let pubkey = T::SignatureKey::from_private(&privkey);
243
244 let libp2p_keypair = derive_libp2p_keypair::<T::SignatureKey>(&privkey)
246 .expect("Failed to derive libp2p keypair");
247
248 let lookup_record_value = RecordValue::new_signed(
250 &RecordKey::new(Namespace::Lookup, pubkey.to_bytes()),
251 libp2p_keypair.public().to_peer_id().to_bytes(),
252 &privkey,
253 )
254 .expect("Failed to sign DHT lookup record");
255
256 let replication_factor =
258 NonZeroUsize::new((2 * expected_node_count).div_ceil(3)).unwrap();
259
260 let config = NetworkNodeConfigBuilder::default()
262 .keypair(libp2p_keypair)
263 .replication_factor(replication_factor)
264 .bind_address(Some(addr))
265 .to_connect_addrs(HashSet::default())
266 .republication_interval(None)
267 .build()
268 .expect("Failed to build network node config");
269
270 let bootstrap_addrs_ref = Arc::clone(&bootstrap_addrs);
271 let node_ids_ref = Arc::clone(&node_ids);
272 let reliability_config_dup = reliability_config.clone();
273
274 Box::pin(async move {
275 let mut write_ids = node_ids_ref.write().await;
277 if write_ids.contains(&node_id) {
278 write_ids.clear();
279 }
280 write_ids.insert(node_id);
281 drop(write_ids);
282 Arc::new(
283 match Libp2pNetwork::new(
284 Libp2pMetricsValue::default(),
285 DhtNoPersistence,
286 config,
287 pubkey.clone(),
288 lookup_record_value,
289 bootstrap_addrs_ref,
290 usize::try_from(node_id).unwrap(),
291 #[cfg(feature = "hotshot-testing")]
292 reliability_config_dup,
293 )
294 .await
295 {
296 Ok(network) => network,
297 Err(err) => {
298 panic!("Failed to create libp2p network: {err:?}");
299 },
300 },
301 )
302 })
303 }
304 })
305 }
306
307 fn in_flight_message_count(&self) -> Option<usize> {
308 None
309 }
310}
311
312pub fn derive_libp2p_keypair<K: SignatureKey>(
318 private_key: &K::PrivateKey,
319) -> anyhow::Result<Keypair> {
320 let derived_key = blake3::derive_key("libp2p key", &private_key.to_bytes());
322 let derived_key = SecretKey::try_from_bytes(derived_key)?;
323
324 Ok(ed25519::Keypair::from(derived_key).into())
326}
327
328pub fn derive_libp2p_peer_id<K: SignatureKey>(
333 private_key: &K::PrivateKey,
334) -> anyhow::Result<PeerId> {
335 let keypair = derive_libp2p_keypair::<K>(private_key)?;
337
338 Ok(PeerId::from_public_key(&keypair.public()))
340}
341
342pub fn derive_libp2p_multiaddr(addr: &String) -> anyhow::Result<Multiaddr> {
351 let (host, port) = match addr.rfind(':') {
353 Some(idx) => (&addr[..idx], &addr[idx + 1..]),
354 None => return Err(anyhow!("Invalid address format, no port supplied")),
355 };
356
357 let ip = host.parse::<IpAddr>();
359
360 let multiaddr_string = match ip {
362 Ok(IpAddr::V4(ip)) => format!("/ip4/{ip}/udp/{port}/quic-v1"),
363 Ok(IpAddr::V6(ip)) => format!("/ip6/{ip}/udp/{port}/quic-v1"),
364 Err(_) => {
365 let lookup_result = addr.to_socket_addrs();
367
368 let failed = lookup_result
370 .map(|result| result.collect::<Vec<_>>().is_empty())
371 .unwrap_or(true);
372
373 if failed {
375 warn!(
376 "Failed to resolve domain name {host}, assuming it has not yet been \
377 provisioned"
378 );
379 }
380
381 format!("/dns/{host}/udp/{port}/quic-v1")
382 },
383 };
384
385 multiaddr_string.parse().with_context(|| {
387 format!("Failed to convert Multiaddr string to Multiaddr: {multiaddr_string}")
388 })
389}
390
391impl<T: NodeType> Libp2pNetwork<T> {
392 #[allow(clippy::too_many_arguments)]
401 pub async fn from_config<D: DhtPersistentStorage>(
402 mut config: NetworkConfig<T>,
403 dht_persistent_storage: D,
404 gossip_config: GossipConfig,
405 request_response_config: RequestResponseConfig,
406 bind_address: Multiaddr,
407 announce_addresses: Vec<Multiaddr>,
408 pub_key: &T::SignatureKey,
409 priv_key: &<T::SignatureKey as SignatureKey>::PrivateKey,
410 metrics: Libp2pMetricsValue,
411 network_discriminator: Option<U256>,
412 dht_put_quorum: Option<NonZeroUsize>,
413 ) -> anyhow::Result<Self> {
414 let libp2p_config = config
416 .libp2p_config
417 .take()
418 .ok_or(anyhow!("Libp2p config not supplied"))?;
419
420 let keypair = derive_libp2p_keypair::<T::SignatureKey>(priv_key)?;
422
423 let mut config_builder = NetworkNodeConfigBuilder::default();
425
426 config_builder.gossip_config(gossip_config.clone());
428 config_builder.request_response_config(request_response_config);
429
430 let auth_message =
432 construct_auth_message(pub_key, &keypair.public().to_peer_id(), priv_key)
433 .with_context(|| "Failed to construct auth message")?;
434
435 config_builder.auth_message(Some(auth_message));
437
438 let Some(default_replication_factor) = DEFAULT_REPLICATION_FACTOR else {
440 return Err(anyhow!("Default replication factor not supplied"));
441 };
442
443 let replication_factor = NonZeroUsize::new(min(
444 default_replication_factor.get(),
445 config.config.num_nodes_with_stake.get() / 2,
446 ))
447 .with_context(|| "Failed to calculate replication factor")?;
448
449 let lookup_record_value = RecordValue::new_signed(
451 &RecordKey::new(Namespace::Lookup, pub_key.to_bytes()),
452 keypair.public().to_peer_id().to_bytes(),
454 priv_key,
455 )
456 .with_context(|| "Failed to sign DHT lookup record")?;
457
458 config_builder
459 .keypair(keypair)
460 .replication_factor(replication_factor)
461 .bind_address(Some(bind_address.clone()))
462 .announce_addresses(announce_addresses)
463 .network_discriminator(network_discriminator)
464 .dht_put_quorum(dht_put_quorum);
465
466 config_builder.to_connect_addrs(HashSet::from_iter(libp2p_config.bootstrap_nodes.clone()));
468
469 let node_config = config_builder.build()?;
471
472 let mut all_keys = BTreeSet::new();
474
475 for node in config.config.known_nodes_with_stake {
477 all_keys.insert(T::SignatureKey::public_key(&node.stake_table_entry));
478 }
479
480 Ok(Libp2pNetwork::new(
481 metrics,
482 dht_persistent_storage,
483 node_config,
484 pub_key.clone(),
485 lookup_record_value,
486 Arc::new(RwLock::new(libp2p_config.bootstrap_nodes)),
487 usize::try_from(config.node_index)?,
488 #[cfg(feature = "hotshot-testing")]
489 None,
490 )
491 .await?)
492 }
493
494 #[must_use]
496 pub fn has_peers(&self) -> bool {
497 self.inner.is_ready.load(Ordering::Relaxed)
498 }
499
500 pub async fn wait_for_peers(&self) {
502 loop {
503 if self.has_peers() {
504 break;
505 }
506 sleep(Duration::from_secs(1)).await;
507 }
508 }
509
510 #[allow(clippy::too_many_arguments)]
523 pub async fn new<D: DhtPersistentStorage>(
524 metrics: Libp2pMetricsValue,
525 dht_persistent_storage: D,
526 config: NetworkNodeConfig,
527 pk: T::SignatureKey,
528 lookup_record_value: RecordValue<T::SignatureKey>,
529 bootstrap_addrs: BootstrapAddrs,
530 id: usize,
531 #[cfg(feature = "hotshot-testing")] reliability_config: Option<Box<dyn NetworkReliability>>,
532 ) -> Result<Libp2pNetwork<T>, NetworkError> {
533 let consensus_key_to_pid_map = Arc::new(parking_lot::Mutex::new(BiMap::new()));
535
536 let (mut rx, network_handle) = spawn_network_node::<T, D>(
537 config.clone(),
538 dht_persistent_storage,
539 Arc::clone(&consensus_key_to_pid_map),
540 id,
541 )
542 .await
543 .map_err(|e| NetworkError::ConfigError(format!("failed to spawn network node: {e}")))?;
544
545 let addr = network_handle.listen_addr();
547 let pid = network_handle.peer_id();
548 bootstrap_addrs.write().await.push((pid, addr));
549
550 let subscribed_topics = HashSet::from_iter(vec![QC_TOPIC.to_string()]);
552
553 let (sender, receiver) = channel(1000);
556 let (node_lookup_send, node_lookup_recv) = channel(10);
557 let (kill_tx, kill_rx) = channel(1);
558 rx.set_kill_switch(kill_rx);
559
560 let mut result = Libp2pNetwork {
561 inner: Arc::new(Libp2pNetworkInner {
562 handle: Arc::new(network_handle),
563 receiver: Mutex::new(receiver),
564 sender: sender.clone(),
565 pk,
566 bootstrap_addrs,
567 is_ready: Arc::new(AtomicBool::new(false)),
568 dht_timeout: config.dht_timeout.unwrap_or(Duration::from_secs(120)),
570 is_bootstrapped: Arc::new(AtomicBool::new(false)),
571 metrics,
572 subscribed_topics,
573 node_lookup_send,
574 latest_seen_view: Arc::new(AtomicU64::new(0)),
578 #[cfg(feature = "hotshot-testing")]
579 reliability_config,
580 kill_switch: kill_tx,
581 }),
582 };
583
584 result.inner.metrics.is_ready.set(0);
586
587 result.handle_event_generator(sender, rx);
588 result.spawn_node_lookup(node_lookup_recv);
589 result.spawn_connect(id, lookup_record_value);
590
591 Ok(result)
592 }
593
594 #[allow(clippy::cast_sign_loss, clippy::cast_precision_loss)]
596 fn spawn_node_lookup(
597 &self,
598 mut node_lookup_recv: Receiver<Option<(ViewNumber, T::SignatureKey)>>,
599 ) {
600 let handle = Arc::clone(&self.inner.handle);
601 let dht_timeout = self.inner.dht_timeout;
602 let latest_seen_view = Arc::clone(&self.inner.latest_seen_view);
603
604 spawn(async move {
606 while let Some(Some((view_number, pk))) = node_lookup_recv.recv().await {
608 #[allow(clippy::cast_possible_truncation)]
610 const THRESHOLD: u64 = (LOOK_AHEAD as f64 * 0.8) as u64;
611
612 trace!("Performing lookup for peer {pk}");
613
614 if latest_seen_view.load(Ordering::Relaxed) + THRESHOLD <= *view_number {
616 if let Err(err) = handle.lookup_node(&pk, dht_timeout).await {
618 LogEvent::DhtLookupFailure.record();
619 debug!("Failed to perform lookup for key {pk}: {err}");
620 };
621 }
622 }
623 });
624 }
625
626 fn spawn_connect(&mut self, id: usize, lookup_record_value: RecordValue<T::SignatureKey>) {
628 let pk = self.inner.pk.clone();
629 let bootstrap_ref = Arc::clone(&self.inner.bootstrap_addrs);
630 let handle = Arc::clone(&self.inner.handle);
631 let is_bootstrapped = Arc::clone(&self.inner.is_bootstrapped);
632 let inner = Arc::clone(&self.inner);
633
634 spawn({
635 let is_ready = Arc::clone(&self.inner.is_ready);
636 async move {
637 let bs_addrs = bootstrap_ref.read().await.clone();
638
639 handle.add_known_peers(bs_addrs).unwrap();
641
642 handle.begin_bootstrap()?;
644 while !is_bootstrapped.load(Ordering::Relaxed) {
645 sleep(Duration::from_secs(1)).await;
646 handle.begin_bootstrap()?;
647 }
648
649 handle.subscribe(QC_TOPIC.to_string()).await.unwrap();
651
652 while handle
655 .put_record(
656 RecordKey::new(Namespace::Lookup, pk.to_bytes()),
657 lookup_record_value.clone(),
658 )
659 .await
660 .is_err()
661 {
662 sleep(Duration::from_secs(1)).await;
663 }
664
665 if let Err(e) = handle.wait_to_connect(1, id).await {
667 error!("Failed to connect to peers: {e:?}");
668 return Err::<(), NetworkError>(e);
669 }
670 info!("Connected to required number of peers");
671
672 is_ready.store(true, Ordering::Relaxed);
674 inner.metrics.is_ready.set(1);
675
676 Ok::<(), NetworkError>(())
677 }
678 });
679 }
680
681 fn handle_recvd_events(
683 &self,
684 msg: NetworkEvent,
685 sender: &Sender<Vec<u8>>,
686 ) -> Result<(), NetworkError> {
687 match msg {
688 GossipMsg(msg) => {
689 sender.try_send(msg).map_err(|err| {
690 NetworkError::ChannelSendError(format!("failed to send gossip message: {err}"))
691 })?;
692 },
693 DirectRequest(msg, _pid, chan) => {
694 sender.try_send(msg).map_err(|err| {
695 NetworkError::ChannelSendError(format!(
696 "failed to send direct request message: {err}"
697 ))
698 })?;
699 if self
700 .inner
701 .handle
702 .direct_response(
703 chan,
704 &bincode::serialize(&Empty { byte: 0u8 }).map_err(|e| {
705 NetworkError::FailedToSerialize(format!(
706 "failed to serialize acknowledgement: {e}"
707 ))
708 })?,
709 )
710 .is_err()
711 {
712 error!("failed to ack!");
713 };
714 },
715 DirectResponse(_msg, _) => {},
716 NetworkEvent::IsBootstrapped => {
717 error!(
718 "handle_recvd_events received `NetworkEvent::IsBootstrapped`, which should be \
719 impossible."
720 );
721 },
722 NetworkEvent::ConnectedPeersUpdate(_) => {},
723 }
724 Ok::<(), NetworkError>(())
725 }
726
727 fn handle_event_generator(&self, sender: Sender<Vec<u8>>, mut network_rx: NetworkNodeReceiver) {
730 let handle = self.clone();
731 let is_bootstrapped = Arc::clone(&self.inner.is_bootstrapped);
732 spawn(async move {
733 let Some(mut kill_switch) = network_rx.take_kill_switch() else {
734 tracing::error!(
735 "`spawn_handle` was called on a network handle that was already closed"
736 );
737 return;
738 };
739
740 loop {
741 select! {
742 msg = network_rx.recv() => {
743 let Ok(message) = msg else {
744 warn!("Network receiver shut down!");
745 return;
746 };
747
748 match message {
749 NetworkEvent::IsBootstrapped => {
750 is_bootstrapped.store(true, Ordering::Relaxed);
751 }
752 GossipMsg(_) | DirectRequest(_, _, _) | DirectResponse(_, _) => {
753 let _ = handle.handle_recvd_events(message, &sender);
754 }
755 NetworkEvent::ConnectedPeersUpdate(num_peers) => {
756 handle.inner.metrics.num_connected_peers.set(num_peers);
757 }
758 }
759 }
760
761 _kill_switch = kill_switch.recv() => {
762 warn!("Event Handler shutdown");
763 return;
764 }
765 }
766 }
767 });
768 }
769}
770
771#[async_trait]
772impl<T: NodeType> ConnectedNetwork<T::SignatureKey> for Libp2pNetwork<T> {
773 #[instrument(name = "Libp2pNetwork::ready_blocking", skip_all)]
774 async fn wait_for_ready(&self) {
775 self.wait_for_peers().await;
776 }
777
778 fn pause(&self) {
779 unimplemented!("Pausing not implemented for the Libp2p network");
780 }
781
782 fn resume(&self) {
783 unimplemented!("Resuming not implemented for the Libp2p network");
784 }
785
786 #[instrument(name = "Libp2pNetwork::shut_down", skip_all)]
787 fn shut_down<'a, 'b>(&'a self) -> BoxSyncFuture<'b, ()>
788 where
789 'a: 'b,
790 Self: 'b,
791 {
792 let closure = async move {
793 let _ = self.inner.handle.shutdown().await;
794 let _ = self.inner.node_lookup_send.send(None).await;
795 let _ = self.inner.kill_switch.send(()).await;
796 };
797 boxed_sync(closure)
798 }
799
800 #[instrument(name = "Libp2pNetwork::broadcast_message", skip_all)]
801 async fn broadcast_message(
802 &self,
803 _: ViewNumber,
804 message: Vec<u8>,
805 topic: Topic,
806 _broadcast_delay: BroadcastDelay,
807 ) -> Result<(), NetworkError> {
808 if !self.has_peers() {
810 self.inner.metrics.num_failed_messages.add(1);
811 return Err(NetworkError::NoPeersYet);
812 };
813
814 let topic = topic.to_string();
816 if self.inner.subscribed_topics.contains(&topic) {
817 self.inner.sender.try_send(message.clone()).map_err(|_| {
819 self.inner.metrics.num_failed_messages.add(1);
820 NetworkError::ShutDown
821 })?;
822 }
823
824 #[cfg(feature = "hotshot-testing")]
826 {
827 let metrics = self.inner.metrics.clone();
828 if let Some(config) = &self.inner.reliability_config {
829 let handle = Arc::clone(&self.inner.handle);
830
831 let fut = config.clone().chaos_send_msg(
832 message,
833 Arc::new(move |msg: Vec<u8>| {
834 let topic_2 = topic.clone();
835 let handle_2 = Arc::clone(&handle);
836 let metrics_2 = metrics.clone();
837 boxed_sync(async move {
838 if let Err(e) = handle_2.gossip_no_serialize(topic_2, msg) {
839 metrics_2.num_failed_messages.add(1);
840 warn!("Failed to broadcast to libp2p: {e:?}");
841 }
842 })
843 }),
844 );
845 spawn(fut);
846 return Ok(());
847 }
848 }
849
850 if let Err(e) = self.inner.handle.gossip(topic, &message) {
851 self.inner.metrics.num_failed_messages.add(1);
852 return Err(e);
853 }
854
855 Ok(())
856 }
857
858 #[instrument(name = "Libp2pNetwork::da_broadcast_message", skip_all)]
859 async fn da_broadcast_message(
860 &self,
861 view: ViewNumber,
862 message: Vec<u8>,
863 recipients: Vec<T::SignatureKey>,
864 _broadcast_delay: BroadcastDelay,
865 ) -> Result<(), NetworkError> {
866 if !self.has_peers() {
868 self.inner.metrics.num_failed_messages.add(1);
869 return Err(NetworkError::NoPeersYet);
870 };
871
872 let topic = Topic::Da.to_string();
874 if self.inner.subscribed_topics.contains(&topic) {
875 self.inner.sender.try_send(message.clone()).map_err(|_| {
876 self.inner.metrics.num_failed_messages.add(1);
877 NetworkError::ShutDown
878 })?;
879 }
880
881 let future_results = recipients
882 .into_iter()
883 .map(|r| self.direct_message(view, message.clone(), r));
884 let results = join_all(future_results).await;
885
886 let errors: Vec<_> = results.into_iter().filter_map(|r| r.err()).collect();
887
888 if errors.is_empty() {
889 Ok(())
890 } else {
891 Err(NetworkError::Multiple(errors))
892 }
893 }
894
895 #[instrument(name = "Libp2pNetwork::direct_message", skip_all)]
896 async fn direct_message(
897 &self,
898 _: ViewNumber,
899 message: Vec<u8>,
900 recipient: T::SignatureKey,
901 ) -> Result<(), NetworkError> {
902 if !self.has_peers() {
904 self.inner.metrics.num_failed_messages.add(1);
905 return Err(NetworkError::NoPeersYet);
906 };
907
908 if recipient == self.inner.pk {
910 self.inner.sender.try_send(message).map_err(|_x| {
912 self.inner.metrics.num_failed_messages.add(1);
913 NetworkError::ShutDown
914 })?;
915 return Ok(());
916 }
917
918 let pid = match self
919 .inner
920 .handle
921 .lookup_node(&recipient, Duration::from_secs(2))
922 .await
923 {
924 Ok(pid) => pid,
925 Err(err) => {
926 self.inner.metrics.num_failed_messages.add(1);
927 return Err(NetworkError::LookupError(format!(
928 "failed to look up node for direct message: {err}"
929 )));
930 },
931 };
932
933 #[cfg(feature = "hotshot-testing")]
934 {
935 let metrics = self.inner.metrics.clone();
936 if let Some(config) = &self.inner.reliability_config {
937 let handle = Arc::clone(&self.inner.handle);
938
939 let fut = config.clone().chaos_send_msg(
940 message,
941 Arc::new(move |msg: Vec<u8>| {
942 let handle_2 = Arc::clone(&handle);
943 let metrics_2 = metrics.clone();
944 boxed_sync(async move {
945 if let Err(e) = handle_2.direct_request_no_serialize(pid, msg) {
946 metrics_2.num_failed_messages.add(1);
947 warn!("Failed to broadcast to libp2p: {e:?}");
948 }
949 })
950 }),
951 );
952 spawn(fut);
953 return Ok(());
954 }
955 }
956
957 match self.inner.handle.direct_request(pid, &message) {
958 Ok(()) => Ok(()),
959 Err(e) => {
960 self.inner.metrics.num_failed_messages.add(1);
961 Err(e)
962 },
963 }
964 }
965
966 #[instrument(name = "Libp2pNetwork::recv_message", skip_all)]
971 async fn recv_message(&self) -> Result<Vec<u8>, NetworkError> {
972 let result = self
973 .inner
974 .receiver
975 .lock()
976 .await
977 .recv()
978 .await
979 .ok_or(NetworkError::ShutDown)?;
980
981 Ok(result)
982 }
983
984 #[instrument(name = "Libp2pNetwork::queue_node_lookup", skip_all)]
985 #[allow(clippy::type_complexity)]
986 fn queue_node_lookup(
987 &self,
988 view_number: ViewNumber,
989 pk: T::SignatureKey,
990 ) -> Result<(), TrySendError<Option<(ViewNumber, T::SignatureKey)>>> {
991 self.inner
992 .node_lookup_send
993 .try_send(Some((view_number, pk)))
994 }
995
996 async fn update_view<TYPES>(
1009 &self,
1010 view: ViewNumber,
1011 epoch: Option<EpochNumber>,
1012 membership_coordinator: EpochMembershipCoordinator<TYPES>,
1013 ) where
1014 TYPES: NodeType<SignatureKey = T::SignatureKey>,
1015 {
1016 let future_view = ViewNumber::new(*view) + LOOK_AHEAD;
1017 let epoch = epoch.map(|e| EpochNumber::new(*e));
1018
1019 let membership = match membership_coordinator.membership_for_epoch(epoch) {
1020 Ok(m) => m,
1021 Err(e) => {
1022 return tracing::warn!(e.message);
1023 },
1024 };
1025 let future_leader = match membership.leader(future_view) {
1026 Ok(l) => l,
1027 Err(e) => {
1028 return tracing::info!("Failed to calculate leader for view {future_view}: {e}");
1029 },
1030 };
1031
1032 let _ = self
1033 .queue_node_lookup(ViewNumber::new(*future_view), future_leader)
1034 .map_err(|err| tracing::warn!("failed to process node lookup request: {err}"));
1035 }
1036}
1037
1038#[cfg(test)]
1039mod test {
1040 mod derive_multiaddr {
1041 use std::net::Ipv6Addr;
1042
1043 use super::super::*;
1044
1045 #[test]
1047 fn test_v4_valid() {
1048 let addr = "1.1.1.1:8080".to_string();
1050 let multiaddr =
1051 derive_libp2p_multiaddr(&addr).expect("Failed to derive valid multiaddr, {}");
1052
1053 assert_eq!(multiaddr.to_string(), "/ip4/1.1.1.1/udp/8080/quic-v1");
1055 }
1056
1057 #[test]
1059 fn test_v6_valid() {
1060 let ipv6_addr = Ipv6Addr::new(1, 2, 3, 4, 5, 6, 7, 8);
1062 let addr = format!("{ipv6_addr}:8080");
1063 let multiaddr =
1064 derive_libp2p_multiaddr(&addr).expect("Failed to derive valid multiaddr, {}");
1065
1066 assert_eq!(
1068 multiaddr.to_string(),
1069 format!("/ip6/{ipv6_addr}/udp/8080/quic-v1")
1070 );
1071 }
1072
1073 #[test]
1075 fn test_no_port() {
1076 let addr = "1.1.1.1".to_string();
1078 let multiaddr = derive_libp2p_multiaddr(&addr);
1079
1080 assert!(multiaddr.is_err());
1082 }
1083
1084 #[test]
1086 fn test_fqdn_exists() {
1087 let addr = "example.com:8080".to_string();
1089 let multiaddr =
1090 derive_libp2p_multiaddr(&addr).expect("Failed to derive valid multiaddr, {}");
1091
1092 assert_eq!(multiaddr.to_string(), "/dns/example.com/udp/8080/quic-v1");
1094 }
1095
1096 #[test]
1098 fn test_fqdn_does_not_exist() {
1099 let addr = "libp2p.example.com:8080".to_string();
1101 let multiaddr =
1102 derive_libp2p_multiaddr(&addr).expect("Failed to derive valid multiaddr, {}");
1103
1104 assert_eq!(
1106 multiaddr.to_string(),
1107 "/dns/libp2p.example.com/udp/8080/quic-v1"
1108 );
1109 }
1110
1111 #[test]
1113 fn test_fqdn_no_port() {
1114 let addr = "example.com".to_string();
1116 let multiaddr = derive_libp2p_multiaddr(&addr);
1117
1118 assert!(multiaddr.is_err());
1120 }
1121 }
1122}