1mod config;
9
10mod handle;
13
14use std::{
15 collections::{HashMap, HashSet},
16 iter,
17 num::{NonZeroU32, NonZeroUsize},
18 pin::pin,
19 sync::Arc,
20 time::{Duration, Instant},
21};
22
23use alloy::primitives::U256;
24use bimap::BiMap;
25use futures::{SinkExt, StreamExt, channel::mpsc};
26use hotshot_types::{
27 constants::KAD_DEFAULT_REPUB_INTERVAL_SEC, traits::node_implementation::NodeType,
28};
29use libp2p::{
30 Multiaddr, StreamProtocol, Swarm, SwarmBuilder,
31 core::{transport::ListenerId, upgrade::Version::V1Lazy},
32 gossipsub::{
33 Behaviour as Gossipsub, ConfigBuilder as GossipsubConfigBuilder, Event as GossipEvent,
34 Message as GossipsubMessage, MessageAuthenticity, MessageId, Topic, ValidationMode,
35 },
36 identify::{
37 Behaviour as IdentifyBehaviour, Config as IdentifyConfig, Event as IdentifyEvent,
38 Info as IdentifyInfo,
39 },
40 identity::Keypair,
41 kad::{Behaviour, Config, Mode, Record, store::MemoryStore},
42 request_response::{
43 Behaviour as RequestResponse, Config as Libp2pRequestResponseConfig, ProtocolSupport,
44 },
45 swarm::SwarmEvent,
46};
47use libp2p_identity::PeerId;
48use parking_lot::Mutex;
49use rand::{prelude::SliceRandom, thread_rng};
50use tokio::{
51 select, spawn,
52 sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded_channel},
53 task::JoinHandle,
54 time::sleep,
55};
56use tracing::{Instrument, debug, error, info, info_span, instrument, warn};
57
58pub use self::{
59 config::{
60 DEFAULT_REPLICATION_FACTOR, GossipConfig, NetworkNodeConfig, NetworkNodeConfigBuilder,
61 NetworkNodeConfigBuilderError, RequestResponseConfig,
62 },
63 handle::{NetworkNodeHandle, NetworkNodeReceiver, spawn_network_node},
64};
65use super::{
66 BoxedTransport, ClientRequest, NetworkDef, NetworkError, NetworkEvent, NetworkEventInternal,
67 behaviours::dht::{
68 bootstrap::{DHTBootstrapTask, InputEvent},
69 store::{
70 persistent::{DhtPersistentStorage, PersistentStore},
71 validated::ValidatedStore,
72 },
73 },
74 cbor::Cbor,
75 gen_transport,
76};
77use crate::network::{
78 behaviours::{
79 dht::{DHTBehaviour, DHTProgress, KadPutQuery},
80 direct_message::{DMBehaviour, DMRequest},
81 exponential_backoff::ExponentialBackoff,
82 },
83 log_summary::LogEvent,
84};
85
86pub type SwarmTaskHandle = JoinHandle<Result<(), NetworkError>>;
88
89pub const MAX_GOSSIP_MSG_SIZE: usize = 2_000_000_000;
91
92pub const ESTABLISHED_LIMIT: NonZeroU32 = NonZeroU32::new(ESTABLISHED_LIMIT_UNWR).unwrap();
94pub const ESTABLISHED_LIMIT_UNWR: u32 = 10;
96
97const NO_INBOUND_CONNECTION_GRACE_PERIOD: Duration = Duration::from_secs(300);
99
100pub(crate) fn mainnet_gossipsub_prefix() -> Option<&'static str> {
105 None
106}
107pub(crate) fn mainnet_kad_protocol() -> StreamProtocol {
108 StreamProtocol::new("/ipfs/kad/1.0.0")
109}
110pub(crate) fn mainnet_direct_message_protocol() -> StreamProtocol {
111 StreamProtocol::new("/HotShot/direct_message/1.0")
112}
113pub(crate) fn mainnet_identify_protocol() -> &'static str {
114 "HotShot/identify/1.0"
115}
116
117pub(crate) fn gossipsub_prefix(discriminator: Option<U256>) -> Option<String> {
120 match discriminator {
121 None => mainnet_gossipsub_prefix().map(String::from),
122 Some(d) => Some(format!("/HotShot/gossipsub/1.0/{d:#x}")),
123 }
124}
125
126pub(crate) fn kad_protocol(discriminator: Option<U256>) -> Result<StreamProtocol, NetworkError> {
128 match discriminator {
129 None => Ok(mainnet_kad_protocol()),
130 Some(d) => StreamProtocol::try_from_owned(format!("/ipfs/kad/1.0.0/{d:#x}"))
131 .map_err(|err| NetworkError::ConfigError(format!("invalid kademlia protocol: {err}"))),
132 }
133}
134
135pub(crate) fn identify_protocol(discriminator: Option<U256>) -> String {
137 match discriminator {
138 None => mainnet_identify_protocol().to_string(),
139 Some(d) => format!("HotShot/identify/1.0/{d:#x}"),
140 }
141}
142
143pub(crate) fn direct_message_protocol(
145 discriminator: Option<U256>,
146) -> Result<StreamProtocol, NetworkError> {
147 match discriminator {
148 None => Ok(mainnet_direct_message_protocol()),
149 Some(d) => StreamProtocol::try_from_owned(format!("/HotShot/direct_message/1.0/{d:#x}"))
150 .map_err(|err| {
151 NetworkError::ConfigError(format!("invalid direct_message protocol: {err}"))
152 }),
153 }
154}
155
156fn should_keep_peer(expected: &StreamProtocol, peer_protocols: &[StreamProtocol]) -> bool {
158 peer_protocols.contains(expected)
159}
160
161fn resolve_put_quorum(
162 quorum_override: Option<NonZeroUsize>,
163 replication_factor: NonZeroUsize,
164) -> NonZeroUsize {
165 quorum_override.unwrap_or_else(|| {
166 NonZeroUsize::new(replication_factor.get() / 2)
167 .expect("replication factor should be bigger than 0")
168 })
169}
170
171#[derive(derive_more::Debug)]
173pub struct NetworkNode<T: NodeType, D: DhtPersistentStorage> {
174 peer_id: PeerId,
176 #[debug(skip)]
178 swarm: Swarm<NetworkDef<T::SignatureKey, D>>,
179 kademlia_record_ttl: Duration,
181 consensus_key_to_pid_map: Arc<Mutex<BiMap<T::SignatureKey, PeerId>>>,
183 listener_id: Option<ListenerId>,
185 direct_message_state: DMBehaviour,
187 dht_handler: DHTBehaviour<T::SignatureKey, D>,
189 resend_tx: Option<UnboundedSender<ClientRequest>>,
191 saw_inbound_connection: bool,
196 expected_kad_protocol: StreamProtocol,
198 same_network_peers: HashSet<PeerId>,
201 dht_put_quorum: Option<NonZeroUsize>,
202}
203
204impl<T: NodeType, D: DhtPersistentStorage> NetworkNode<T, D> {
205 pub fn num_connected(&self) -> usize {
207 self.swarm.connected_peers().count()
208 }
209
210 pub fn connected_pids(&self) -> HashSet<PeerId> {
212 self.swarm.connected_peers().copied().collect()
213 }
214
215 #[instrument(skip(self))]
219 pub async fn start_listen(
220 &mut self,
221 listen_addr: Multiaddr,
222 ) -> Result<Multiaddr, NetworkError> {
223 self.listener_id = Some(self.swarm.listen_on(listen_addr).map_err(|err| {
224 NetworkError::ListenError(format!("failed to listen for Libp2p: {err}"))
225 })?);
226 let addr = loop {
227 if let Some(SwarmEvent::NewListenAddr { address, .. }) = self.swarm.next().await {
228 break address;
229 }
230 };
231 info!("Libp2p listening on {addr:?}");
232 Ok(addr)
233 }
234
235 #[instrument(skip(self))]
240 pub fn add_known_peers(&mut self, known_peers: &[(PeerId, Multiaddr)]) {
241 debug!("Adding {} known peers", known_peers.len());
242 let behaviour = self.swarm.behaviour_mut();
243 let mut bs_nodes = HashMap::<PeerId, HashSet<Multiaddr>>::new();
244 let mut shuffled = known_peers.iter().collect::<Vec<_>>();
245 shuffled.shuffle(&mut thread_rng());
246 for (peer_id, addr) in shuffled {
247 if *peer_id != self.peer_id {
248 behaviour.dht.add_address(peer_id, addr.clone());
249 bs_nodes.insert(*peer_id, iter::once(addr.clone()).collect());
250 }
251 }
252 }
253
254 #[allow(clippy::too_many_lines)]
268 pub async fn new(
269 config: NetworkNodeConfig,
270 dht_persistent_storage: D,
271 consensus_key_to_pid_map: Arc<Mutex<BiMap<T::SignatureKey, PeerId>>>,
272 ) -> Result<Self, NetworkError> {
273 let keypair = config
275 .keypair
276 .clone()
277 .unwrap_or_else(Keypair::generate_ed25519);
278
279 let peer_id = PeerId::from(keypair.public());
281
282 let transport: BoxedTransport = gen_transport::<T>(
284 keypair.clone(),
285 config.auth_message.clone(),
286 Arc::clone(&consensus_key_to_pid_map),
287 )
288 .await?;
289
290 let kademlia_record_republication_interval = config
292 .republication_interval
293 .unwrap_or(Duration::from_secs(KAD_DEFAULT_REPUB_INTERVAL_SEC));
294
295 let kademlia_ttl = config
297 .ttl
298 .unwrap_or(16 * kademlia_record_republication_interval);
299
300 let expected_kad_protocol = kad_protocol(config.network_discriminator)?;
301
302 let mut swarm: Swarm<NetworkDef<T::SignatureKey, D>> = {
304 let message_id_fn = |message: &GossipsubMessage| {
306 let hash = blake3::hash(&message.data);
307 MessageId::from(hash.as_bytes().to_vec())
308 };
309
310 let mut gossipsub_builder = GossipsubConfigBuilder::default();
312 if let Some(prefix) = gossipsub_prefix(config.network_discriminator) {
313 gossipsub_builder.protocol_id_prefix(prefix);
314 }
315 let gossipsub_config = gossipsub_builder
316 .message_id_fn(message_id_fn) .validation_mode(ValidationMode::Strict) .heartbeat_interval(config.gossip_config.heartbeat_interval) .history_gossip(config.gossip_config.history_gossip) .history_length(config.gossip_config.history_length) .mesh_n(config.gossip_config.mesh_n) .mesh_n_high(config.gossip_config.mesh_n_high) .mesh_n_low(config.gossip_config.mesh_n_low) .mesh_outbound_min(config.gossip_config.mesh_outbound_min) .max_transmit_size(config.gossip_config.max_transmit_size) .max_ihave_length(config.gossip_config.max_ihave_length) .max_ihave_messages(config.gossip_config.max_ihave_messages) .published_message_ids_cache_time(
329 config.gossip_config.published_message_ids_cache_time,
330 ) .iwant_followup_time(config.gossip_config.iwant_followup_time) .max_messages_per_rpc(config.gossip_config.max_messages_per_rpc) .gossip_retransimission(config.gossip_config.gossip_retransmission) .flood_publish(config.gossip_config.flood_publish) .duplicate_cache_time(config.gossip_config.duplicate_cache_time) .fanout_ttl(config.gossip_config.fanout_ttl) .heartbeat_initial_delay(config.gossip_config.heartbeat_initial_delay) .gossip_factor(config.gossip_config.gossip_factor) .gossip_lazy(config.gossip_config.gossip_lazy) .build()
341 .map_err(|err| {
342 NetworkError::ConfigError(format!("error building gossipsub config: {err:?}"))
343 })?;
344
345 let gossipsub: Gossipsub = Gossipsub::new(
347 MessageAuthenticity::Signed(keypair.clone()),
348 gossipsub_config,
349 )
350 .map_err(|err| {
351 NetworkError::ConfigError(format!("error building gossipsub behaviour: {err:?}"))
352 })?;
353
354 let identify_cfg = IdentifyConfig::new(
359 identify_protocol(config.network_discriminator),
360 keypair.public(),
361 );
362 let identify = IdentifyBehaviour::new(identify_cfg);
363
364 let mut kconfig = Config::new(expected_kad_protocol.clone());
366 kconfig
367 .set_parallelism(NonZeroUsize::new(5).unwrap())
368 .set_provider_publication_interval(Some(kademlia_record_republication_interval))
369 .set_publication_interval(Some(kademlia_record_republication_interval))
370 .set_record_ttl(Some(kademlia_ttl));
371
372 #[allow(clippy::panic)]
374 if let Some(factor) = config.replication_factor {
375 kconfig.set_replication_factor(factor);
376 } else {
377 panic!("Replication factor not set");
378 }
379
380 let mut kadem = Behaviour::with_config(
382 peer_id,
383 PersistentStore::new(
384 ValidatedStore::new(MemoryStore::new(peer_id)),
385 dht_persistent_storage,
386 5,
387 )
388 .await,
389 kconfig,
390 );
391 kadem.set_mode(Some(Mode::Server));
392
393 let rrconfig = Libp2pRequestResponseConfig::default();
394
395 let cbor = Cbor::new(
397 config.request_response_config.request_size_maximum,
398 config.request_response_config.response_size_maximum,
399 );
400
401 let direct_message: super::cbor::Behaviour<Vec<u8>, Vec<u8>> =
402 RequestResponse::with_codec(
403 cbor,
404 [(
405 direct_message_protocol(config.network_discriminator)?,
406 ProtocolSupport::Full,
407 )],
408 rrconfig.clone(),
409 );
410
411 let network = NetworkDef::new(gossipsub, kadem, identify, direct_message);
412
413 let swarm = SwarmBuilder::with_existing_identity(keypair.clone());
415 let swarm = swarm.with_tokio();
416
417 swarm
418 .with_other_transport(|_| transport)
419 .unwrap()
420 .with_behaviour(|_| network)
421 .unwrap()
422 .with_swarm_config(|cfg| {
423 cfg.with_idle_connection_timeout(Duration::from_secs(10))
424 .with_substream_upgrade_protocol_override(V1Lazy)
425 })
426 .build()
427 };
428 for (peer, addr) in &config.to_connect_addrs {
429 if peer != swarm.local_peer_id() {
430 swarm.behaviour_mut().add_address(peer, addr.clone());
431 swarm.add_peer_address(*peer, addr.clone());
432 }
433 }
434
435 for addr in &config.announce_addresses {
436 info!("Adding announce address {addr}");
437 swarm.add_external_address(addr.clone());
438 }
439
440 Ok(Self {
441 peer_id,
442 swarm,
443 kademlia_record_ttl: kademlia_ttl,
444 consensus_key_to_pid_map,
445 listener_id: None,
446 direct_message_state: DMBehaviour::default(),
447 dht_handler: DHTBehaviour::new(
448 peer_id,
449 config
450 .replication_factor
451 .unwrap_or(NonZeroUsize::new(4).unwrap()),
452 ),
453 resend_tx: None,
454 saw_inbound_connection: false,
455 expected_kad_protocol,
456 same_network_peers: HashSet::new(),
457 dht_put_quorum: config.dht_put_quorum,
458 })
459 }
460
461 fn on_identify_received(&mut self, peer_id: PeerId, info: IdentifyInfo) {
463 if should_keep_peer(&self.expected_kad_protocol, &info.protocols) {
464 self.same_network_peers.insert(peer_id);
465 let behaviour = self.swarm.behaviour_mut();
466 for addr in info.listen_addrs.iter().collect::<HashSet<_>>() {
468 behaviour.dht.add_address(&peer_id, addr.clone());
469 }
470 } else {
471 debug!(
472 "Dropping peer {peer_id}: its kad protocol does not match ours ({})",
473 self.expected_kad_protocol
474 );
475 self.same_network_peers.remove(&peer_id);
476 self.swarm.behaviour_mut().dht.remove_peer(&peer_id);
477 let _ = self.swarm.disconnect_peer_id(peer_id);
478 }
479 }
480
481 pub fn put_record(&mut self, mut query: KadPutQuery) {
486 let mut record = Record::new(query.key.clone(), query.value.clone());
488
489 record.expires = Some(Instant::now() + self.kademlia_record_ttl);
491
492 let quorum = resolve_put_quorum(self.dht_put_quorum, self.dht_handler.replication_factor());
493 match self
494 .swarm
495 .behaviour_mut()
496 .dht
497 .put_record(record, libp2p::kad::Quorum::N(quorum))
498 {
499 Err(e) => {
500 query.progress = DHTProgress::NotStarted;
502 query.backoff.start_next(false);
503 error!("Error publishing to DHT: {e:?} for peer {:?}", self.peer_id);
504 },
505 Ok(qid) => {
506 debug!("Published record to DHT with qid {qid:?}");
507 let query = KadPutQuery {
508 progress: DHTProgress::InProgress(qid),
509 ..query
510 };
511 self.dht_handler.put_record(qid, query);
512 },
513 }
514 }
515
516 #[instrument(skip(self))]
525 async fn handle_client_requests(
526 &mut self,
527 msg: Option<ClientRequest>,
528 ) -> Result<bool, NetworkError> {
529 let behaviour = self.swarm.behaviour_mut();
530 match msg {
531 Some(msg) => {
532 match msg {
533 ClientRequest::BeginBootstrap => {
534 debug!("Beginning Libp2p bootstrap");
535 let _ = self.swarm.behaviour_mut().dht.bootstrap();
536 },
537 ClientRequest::LookupPeer(pid, chan) => {
538 let id = self.swarm.behaviour_mut().dht.get_closest_peers(pid);
539 self.dht_handler
540 .in_progress_get_closest_peers
541 .insert(id, chan);
542 },
543 ClientRequest::GetRoutingTable(chan) => {
544 self.dht_handler
545 .print_routing_table(&mut self.swarm.behaviour_mut().dht);
546 if chan.send(()).is_err() {
547 warn!("Tried to notify client but client not tracking anymore");
548 }
549 },
550 ClientRequest::PutDHT { key, value, notify } => {
551 let query = KadPutQuery {
552 progress: DHTProgress::NotStarted,
553 notify,
554 key,
555 value,
556 backoff: ExponentialBackoff::default(),
557 };
558 self.put_record(query);
559 },
560 ClientRequest::GetConnectedPeerNum(s) => {
561 if s.send(self.num_connected()).is_err() {
562 error!("error sending peer number to client");
563 }
564 },
565 ClientRequest::GetConnectedPeers(s) => {
566 if s.send(self.connected_pids()).is_err() {
567 error!("error sending peer set to client");
568 }
569 },
570 ClientRequest::GetKadRoutingPeers(s) => {
571 let peers: HashSet<PeerId> = self
572 .swarm
573 .behaviour_mut()
574 .dht
575 .kbuckets()
576 .flat_map(|b| {
577 b.iter().map(|e| *e.node.key.preimage()).collect::<Vec<_>>()
578 })
579 .collect();
580 if s.send(peers).is_err() {
581 error!("error sending kad routing peers to client");
582 }
583 },
584 ClientRequest::GetDHT {
585 key,
586 notify,
587 retry_count,
588 } => {
589 self.dht_handler.get_record(
590 key,
591 notify,
592 ExponentialBackoff::default(),
593 retry_count,
594 &mut self.swarm.behaviour_mut().dht,
595 );
596 },
597 ClientRequest::IgnorePeers(_peers) => {
598 },
600 ClientRequest::Shutdown => {
601 if let Some(listener_id) = self.listener_id {
602 self.swarm.remove_listener(listener_id);
603 }
604
605 return Ok(true);
606 },
607 ClientRequest::GossipMsg(topic, contents) => {
608 behaviour.publish_gossip(Topic::new(topic.clone()), contents.clone());
609 },
610 ClientRequest::Subscribe(t, chan) => {
611 behaviour.subscribe_gossip(&t);
612 if let Some(chan) = chan
613 && chan.send(()).is_err()
614 {
615 error!("finished subscribing but response channel dropped");
616 }
617 },
618 ClientRequest::Unsubscribe(t, chan) => {
619 behaviour.unsubscribe_gossip(&t);
620 if let Some(chan) = chan
621 && chan.send(()).is_err()
622 {
623 error!("finished unsubscribing but response channel dropped");
624 }
625 },
626 ClientRequest::DirectRequest {
627 pid,
628 contents,
629 retry_count,
630 } => {
631 debug!("Sending direct request to {pid:?}");
632 let id = behaviour.add_direct_request(pid, contents.clone());
633 let req = DMRequest {
634 peer_id: pid,
635 data: contents,
636 backoff: ExponentialBackoff::default(),
637 retry_count,
638 };
639 self.direct_message_state.add_direct_request(req, id);
640 },
641 ClientRequest::DirectResponse(chan, msg) => {
642 behaviour.add_direct_response(chan, msg);
643 },
644 ClientRequest::AddKnownPeers(peers) => {
645 self.add_known_peers(&peers);
646 },
647 ClientRequest::Prune(pid) => {
648 if self.swarm.disconnect_peer_id(pid).is_err() {
649 warn!("Could not disconnect from {pid:?}");
650 }
651 },
652 }
653 },
654 None => {
655 error!("Error receiving msg in main behaviour loop: channel closed");
656 },
657 }
658 Ok(false)
659 }
660
661 #[allow(clippy::type_complexity)]
663 #[instrument(skip(self))]
664 async fn handle_swarm_events(
665 &mut self,
666 event: SwarmEvent<NetworkEventInternal>,
667 send_to_client: &UnboundedSender<NetworkEvent>,
668 ) -> Result<(), NetworkError> {
669 debug!("Swarm event observed {:?}", event);
671
672 #[allow(deprecated)]
673 match event {
674 SwarmEvent::ConnectionEstablished {
675 connection_id: _,
676 peer_id,
677 endpoint,
678 num_established,
679 concurrent_dial_errors,
680 established_in: _established_in,
681 } => {
682 if num_established > ESTABLISHED_LIMIT {
683 error!(
684 "Num concurrent connections to a single peer exceeding \
685 {ESTABLISHED_LIMIT:?} at {num_established:?}!"
686 );
687 } else {
688 debug!(
689 "Connection established with {peer_id:?} at {endpoint:?} with \
690 {concurrent_dial_errors:?} concurrent dial errors"
691 );
692 }
693
694 if endpoint.is_listener() {
695 self.saw_inbound_connection = true;
696 }
697
698 send_to_client
700 .send(NetworkEvent::ConnectedPeersUpdate(self.num_connected()))
701 .map_err(|err| NetworkError::ChannelSendError(err.to_string()))?;
702 },
703 SwarmEvent::ConnectionClosed {
704 connection_id: _,
705 peer_id,
706 endpoint,
707 num_established,
708 cause,
709 } => {
710 if num_established > ESTABLISHED_LIMIT_UNWR {
711 error!(
712 "Num concurrent connections to a single peer exceeding \
713 {ESTABLISHED_LIMIT:?} at {num_established:?}!"
714 );
715 } else {
716 debug!("Connection closed with {peer_id:?} at {endpoint:?} due to {cause:?}");
717 }
718
719 if num_established == 0 {
722 self.consensus_key_to_pid_map
723 .lock()
724 .remove_by_right(&peer_id);
725 self.same_network_peers.remove(&peer_id);
726 }
727
728 send_to_client
730 .send(NetworkEvent::ConnectedPeersUpdate(self.num_connected()))
731 .map_err(|err| NetworkError::ChannelSendError(err.to_string()))?;
732 },
733 SwarmEvent::Dialing {
734 peer_id,
735 connection_id: _,
736 } => {
737 debug!("Attempting to dial {peer_id:?}");
738 },
739 SwarmEvent::ListenerClosed {
740 listener_id: _,
741 addresses: _,
742 reason: _,
743 }
744 | SwarmEvent::NewListenAddr {
745 listener_id: _,
746 address: _,
747 }
748 | SwarmEvent::ExpiredListenAddr {
749 listener_id: _,
750 address: _,
751 }
752 | SwarmEvent::NewExternalAddrCandidate { .. }
753 | SwarmEvent::ExternalAddrExpired { .. }
754 | SwarmEvent::IncomingConnection {
755 connection_id: _,
756 local_addr: _,
757 send_back_addr: _,
758 } => {},
759 SwarmEvent::Behaviour(b) => {
760 let maybe_event = match b {
761 NetworkEventInternal::DHTEvent(e) => self
762 .dht_handler
763 .dht_handle_event(e, self.swarm.behaviour_mut().dht.store_mut()),
764 NetworkEventInternal::IdentifyEvent(e) => {
765 if let IdentifyEvent::Received {
766 peer_id,
767 info,
768 connection_id: _,
769 } = *e
770 {
771 self.on_identify_received(peer_id, info);
772 }
773 None
774 },
775 NetworkEventInternal::GossipEvent(e) => match *e {
776 GossipEvent::Message {
777 propagation_source: _peer_id,
778 message_id: _id,
779 message,
780 } => Some(NetworkEvent::GossipMsg(message.data)),
781 GossipEvent::Subscribed { peer_id, topic } => {
782 debug!("Peer {peer_id:?} subscribed to topic {topic:?}");
783 None
784 },
785 GossipEvent::Unsubscribed { peer_id, topic } => {
786 debug!("Peer {peer_id:?} unsubscribed from topic {topic:?}");
787 None
788 },
789 GossipEvent::GossipsubNotSupported { peer_id } => {
790 LogEvent::GossipsubNotSupported.record();
791 debug!("Peer {peer_id:?} does not support gossipsub");
792 None
793 },
794 GossipEvent::SlowPeer {
795 peer_id,
796 failed_messages: _,
797 } => {
798 LogEvent::GossipsubSlowPeer.record();
799 debug!("Peer {peer_id:?} is slow");
800 None
801 },
802 },
803 NetworkEventInternal::DMEvent(e) => self
804 .direct_message_state
805 .handle_dm_event(e, self.resend_tx.clone()),
806 };
807
808 if let Some(event) = maybe_event {
809 send_to_client
811 .send(event)
812 .map_err(|err| NetworkError::ChannelSendError(err.to_string()))?;
813 }
814 },
815 SwarmEvent::OutgoingConnectionError {
816 connection_id: _,
817 peer_id,
818 error,
819 } => {
820 LogEvent::DialFailure.record();
821 debug!("Outgoing connection error to {peer_id:?}: {error:?}");
822 },
823 SwarmEvent::IncomingConnectionError {
824 connection_id: _,
825 local_addr: _,
826 send_back_addr: _,
827 error,
828 peer_id: _,
829 } => {
830 LogEvent::IncomingConnError.record();
831 debug!("Incoming connection error: {error:?}");
832 },
833 SwarmEvent::ListenerError {
834 listener_id: _,
835 error,
836 } => {
837 LogEvent::ListenerError.record();
838 debug!("Listener error: {error:?}");
839 },
840 SwarmEvent::ExternalAddrConfirmed { address } => {
841 let my_id = *self.swarm.local_peer_id();
842 self.swarm
843 .behaviour_mut()
844 .dht
845 .add_address(&my_id, address.clone());
846 },
847 SwarmEvent::NewExternalAddrOfPeer { peer_id, address } => {
848 if self.same_network_peers.contains(&peer_id) {
850 self.swarm
851 .behaviour_mut()
852 .dht
853 .add_address(&peer_id, address.clone());
854 }
855 },
856 _ => {
857 debug!("Unhandled swarm event {event:?}");
858 },
859 }
860 Ok(())
861 }
862
863 pub fn spawn_listeners(
869 mut self,
870 ) -> Result<
871 (
872 UnboundedSender<ClientRequest>,
873 UnboundedReceiver<NetworkEvent>,
874 SwarmTaskHandle,
875 ),
876 NetworkError,
877 > {
878 let (s_input, mut s_output) = unbounded_channel::<ClientRequest>();
879 let (r_input, r_output) = unbounded_channel::<NetworkEvent>();
880 let (mut bootstrap_tx, bootstrap_rx) = mpsc::channel(100);
881 self.resend_tx = Some(s_input.clone());
882 self.dht_handler.set_bootstrap_sender(bootstrap_tx.clone());
883
884 DHTBootstrapTask::run(bootstrap_rx, s_input.clone());
885 let task = spawn(
888 async move {
889 let mut inbound_check = pin!(sleep(NO_INBOUND_CONNECTION_GRACE_PERIOD));
890 let mut inbound_check_pending = true;
891 loop {
892 select! {
893 event = self.swarm.next() => {
894 debug!("peerid {:?}\t\thandling maybe event {:?}", self.peer_id, event);
895 if let Some(event) = event {
896 debug!("peerid {:?}\t\thandling event {:?}", self.peer_id, event);
897 self.handle_swarm_events(event, &r_input).await?;
898 }
899 },
900 msg = s_output.recv() => {
901 debug!("peerid {:?}\t\thandling msg {:?}", self.peer_id, msg);
902 let shutdown = self.handle_client_requests(msg).await?;
903 if shutdown {
904 let _ = bootstrap_tx.send(InputEvent::ShutdownBootstrap).await;
905 break
906 }
907 },
908 () = &mut inbound_check, if inbound_check_pending => {
909 inbound_check_pending = false;
910 self.warn_if_unreachable();
911 }
912 }
913 }
914 drop(self.swarm);
915 Ok::<(), NetworkError>(())
916 }
917 .instrument(info_span!("Libp2p NetworkBehaviour Handler")),
918 );
919 Ok((s_input, r_output, task))
920 }
921
922 pub fn peer_id(&self) -> PeerId {
924 self.peer_id
925 }
926
927 fn warn_if_unreachable(&self) {
936 if self.listener_id.is_none() || self.saw_inbound_connection {
937 return;
938 }
939 if self.num_connected() == 0 {
940 error!(
941 "No connections at all within {NO_INBOUND_CONNECTION_GRACE_PERIOD:?} of startup. \
942 This node could not reach any peer. Check the bootstrap node configuration and \
943 that outbound UDP traffic is allowed (firewall/NAT/security group)."
944 );
945 } else {
946 error!(
947 "No peer has connected to us within {NO_INBOUND_CONNECTION_GRACE_PERIOD:?} of \
948 startup. This node is likely not publicly reachable: peers cannot direct-message \
949 us, so leader views may fail and we may accumulate missed slots. Verify \
950 --libp2p-advertise-address (env ESPRESSO_NODE_LIBP2P_ADVERTISE_ADDRESS) is set \
951 to a publicly reachable host:port, and ensure inbound UDP at that port is open \
952 from the public internet (firewall/NAT/security group)."
953 );
954 }
955 }
956}
957
958#[cfg(test)]
959mod tests {
960 use std::{collections::HashSet, num::NonZeroUsize, sync::Arc, time::Duration};
961
962 use hotshot_example_types::node_types::TestTypes;
963 use parking_lot::Mutex;
964
965 use super::{
966 U256, direct_message_protocol, gossipsub_prefix, identify_protocol, kad_protocol,
967 resolve_put_quorum, should_keep_peer,
968 };
969 use crate::network::{
970 NetworkNodeConfigBuilder, behaviours::dht::store::persistent::DhtNoPersistence,
971 node::handle::spawn_network_node,
972 };
973
974 async fn spawn_node(
977 discriminator: Option<U256>,
978 connect_to: HashSet<(libp2p_identity::PeerId, libp2p::Multiaddr)>,
979 id: usize,
980 ) -> crate::network::node::handle::NetworkNodeHandle<TestTypes> {
981 use bimap::BiMap;
982
983 let config = NetworkNodeConfigBuilder::default()
984 .network_discriminator(discriminator)
985 .to_connect_addrs(connect_to)
986 .build()
987 .expect("config build");
988
989 let key_map = Arc::new(Mutex::new(BiMap::default()));
990 let (mut receiver, handle) = spawn_network_node::<TestTypes, DhtNoPersistence>(
991 config,
992 DhtNoPersistence,
993 key_map,
994 id,
995 )
996 .await
997 .expect("spawn node");
998
999 tokio::spawn(async move {
1001 loop {
1002 if receiver.recv().await.is_err() {
1003 break;
1004 }
1005 }
1006 });
1007
1008 handle
1009 }
1010
1011 fn snapshot_for(discriminator: Option<U256>) -> String {
1012 format!(
1013 "gossipsub_prefix: {:?}\nkad: {}\ndirect_message: {}\nidentify: {}",
1014 gossipsub_prefix(discriminator),
1015 kad_protocol(discriminator).unwrap(),
1016 direct_message_protocol(discriminator).unwrap(),
1017 identify_protocol(discriminator),
1018 )
1019 }
1020
1021 #[test]
1022 fn mainnet_libp2p_protocol_identifiers() {
1023 insta::assert_snapshot!("mainnet_libp2p_protocol_identifiers", snapshot_for(None));
1024 }
1025
1026 #[test]
1027 fn decaf_libp2p_protocol_identifiers() {
1028 insta::assert_snapshot!(
1029 "decaf_libp2p_protocol_identifiers",
1030 snapshot_for(Some(U256::from(0xdecafu64)))
1031 );
1032 }
1033
1034 #[test]
1035 fn should_keep_peer_empty_protocols() {
1036 let expected = kad_protocol(None).unwrap();
1037 assert!(!should_keep_peer(&expected, &[]));
1038 }
1039
1040 #[test]
1041 fn should_keep_peer_discriminator_matrix() {
1042 let discriminators = [None, Some(U256::from(1u64)), Some(U256::from(2u64))];
1043 for ours in discriminators {
1044 for theirs in discriminators {
1045 let expected = kad_protocol(ours).unwrap();
1046 let peer_protocols = [
1047 direct_message_protocol(theirs).unwrap(),
1048 kad_protocol(theirs).unwrap(),
1049 ];
1050 assert_eq!(
1051 should_keep_peer(&expected, &peer_protocols),
1052 ours == theirs,
1053 "ours={ours:?} theirs={theirs:?}",
1054 );
1055 }
1056 }
1057 }
1058
1059 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1062 async fn evicts_cross_network_peer_from_routing_table() {
1063 tokio::time::timeout(Duration::from_secs(90), async {
1064 let a = spawn_node(Some(U256::from(1u64)), HashSet::new(), 0).await;
1065
1066 let hub = HashSet::from([(a.peer_id(), a.listen_addr())]);
1067 let b = spawn_node(Some(U256::from(1u64)), hub.clone(), 1).await;
1068 let x = spawn_node(Some(U256::from(2u64)), hub, 2).await;
1069
1070 b.begin_bootstrap().expect("begin_bootstrap b");
1071 x.begin_bootstrap().expect("begin_bootstrap x");
1072
1073 let b_pid = b.peer_id();
1074 let x_pid = x.peer_id();
1075
1076 let deadline = tokio::time::Instant::now() + Duration::from_secs(30);
1077 let mut consecutive = 0usize;
1078 loop {
1079 assert!(
1080 tokio::time::Instant::now() < deadline,
1081 "steady state not reached",
1082 );
1083 tokio::time::sleep(Duration::from_secs(1)).await;
1084 let kad_peers = a.kad_routing_peers().await.expect("kad_routing_peers");
1085 if kad_peers.contains(&b_pid) && !kad_peers.contains(&x_pid) {
1086 consecutive += 1;
1087 if consecutive >= 3 {
1088 break;
1089 }
1090 } else {
1091 consecutive = 0;
1092 }
1093 }
1094
1095 x.shutdown().await.expect("shutdown x");
1096 b.shutdown().await.expect("shutdown b");
1097 a.shutdown().await.expect("shutdown a");
1098 })
1099 .await
1100 .expect("test timed out");
1101 }
1102
1103 #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
1106 async fn drops_cross_network_peer() {
1107 tokio::time::timeout(Duration::from_secs(90), async {
1108 let a = spawn_node(Some(U256::from(1u64)), HashSet::new(), 0).await;
1109
1110 let hub = HashSet::from([(a.peer_id(), a.listen_addr())]);
1111 let b = spawn_node(Some(U256::from(1u64)), hub.clone(), 1).await;
1112 let x = spawn_node(Some(U256::from(2u64)), hub, 2).await;
1113
1114 b.begin_bootstrap().expect("begin_bootstrap b");
1115 x.begin_bootstrap().expect("begin_bootstrap x");
1116
1117 let b_pid = b.peer_id();
1118 let expected = HashSet::from([b_pid]);
1119
1120 let deadline = tokio::time::Instant::now() + Duration::from_secs(30);
1121 let mut consecutive = 0usize;
1123 loop {
1124 assert!(
1125 tokio::time::Instant::now() < deadline,
1126 "steady state not reached; a.connected_pids()={:?}",
1127 a.connected_pids().await,
1128 );
1129 tokio::time::sleep(Duration::from_secs(1)).await;
1130 let a_pids = a.connected_pids().await.expect("connected_pids");
1131 let x_conn = x.num_connected().await.expect("num_connected");
1132 if a_pids == expected && x_conn == 0 {
1133 consecutive += 1;
1134 if consecutive >= 3 {
1135 break;
1136 }
1137 } else {
1138 consecutive = 0;
1139 }
1140 }
1141
1142 x.shutdown().await.expect("shutdown x");
1143 b.shutdown().await.expect("shutdown b");
1144 a.shutdown().await.expect("shutdown a");
1145 })
1146 .await
1147 .expect("test timed out");
1148 }
1149
1150 #[test]
1151 fn put_quorum_override() {
1152 let nz = |n| NonZeroUsize::new(n).unwrap();
1153
1154 assert_eq!(resolve_put_quorum(None, nz(20)), nz(10));
1156 assert_eq!(resolve_put_quorum(None, nz(2)), nz(1));
1157
1158 assert_eq!(resolve_put_quorum(Some(nz(1)), nz(20)), nz(1));
1160 assert_eq!(resolve_put_quorum(Some(nz(7)), nz(20)), nz(7));
1162 assert_eq!(resolve_put_quorum(Some(nz(50)), nz(20)), nz(50));
1164 }
1165}