1use crate::{
23 block_announce_validator::{
24 BlockAnnounceValidationResult, BlockAnnounceValidator as BlockAnnounceValidatorStream,
25 },
26 pending_responses::{PendingResponses, ResponseEvent},
27 service::{
28 self,
29 syncing_service::{SyncingService, ToServiceCommand},
30 },
31 strategy::{SyncingAction, SyncingStrategy},
32 types::{BadPeer, ExtendedPeerInfo, SyncEvent},
33 LOG_TARGET,
34};
35
36use codec::{Decode, DecodeAll, Encode};
37use futures::{channel::oneshot, StreamExt};
38use log::{debug, error, trace};
39use prometheus_endpoint::{
40 register, Counter, Gauge, MetricSource, Opts, PrometheusError, Registry, SourcedGauge, U64,
41};
42use schnellru::{ByLength, LruMap};
43use tokio::time::{Interval, MissedTickBehavior};
44
45use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider};
46use sc_consensus::{import_queue::ImportQueueService, IncomingBlock};
47use sc_network::{
48 config::{FullNetworkConfiguration, NotificationHandshake, ProtocolId, SetConfig},
49 peer_store::PeerStoreProvider,
50 request_responses::{OutboundFailure, RequestFailure},
51 service::{
52 traits::{Direction, NotificationConfig, NotificationEvent, ValidationResult},
53 NotificationMetrics,
54 },
55 types::ProtocolName,
56 utils::LruHashSet,
57 NetworkBackend, NotificationService, ReputationChange,
58};
59use sc_network_common::{
60 role::Roles,
61 sync::message::{BlockAnnounce, BlockAnnouncesHandshake, BlockState},
62};
63use sc_network_types::PeerId;
64use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender};
65use sp_blockchain::{Error as ClientError, HeaderMetadata};
66use sp_consensus::{block_validation::BlockAnnounceValidator, BlockOrigin};
67use sp_runtime::{
68 traits::{Block as BlockT, Header, NumberFor, Zero},
69 Justifications,
70};
71
72use std::{
73 collections::{HashMap, HashSet},
74 iter,
75 num::NonZeroUsize,
76 sync::{
77 atomic::{AtomicBool, AtomicUsize, Ordering},
78 Arc,
79 },
80};
81
82const TICK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(1100);
84
85const MAX_KNOWN_BLOCKS: usize = 1024; const MAX_BLOCK_ANNOUNCE_SIZE: u64 = 1024 * 1024;
90
91pub fn block_announces_protocol_name<Hash: AsRef<[u8]>>(
93 genesis_hash: Hash,
94 fork_id: Option<&str>,
95) -> String {
96 let genesis_hash = genesis_hash.as_ref();
97 if let Some(fork_id) = fork_id {
98 format!("/{}/{}/block-announces/1", array_bytes::bytes2hex("", genesis_hash), fork_id)
99 } else {
100 format!("/{}/block-announces/1", array_bytes::bytes2hex("", genesis_hash))
101 }
102}
103
104pub fn block_announces_legacy_protocol_name(protocol_id: &ProtocolId) -> String {
106 format!("/{}/block-announces/1", protocol_id.as_ref())
107}
108
109mod rep {
110 use sc_network::ReputationChange as Rep;
111 pub const GENESIS_MISMATCH: Rep = Rep::new_fatal("Genesis mismatch");
113 pub const BAD_BLOCK_ANNOUNCEMENT: Rep = Rep::new(-(1 << 12), "Bad block announcement");
115 pub const BAD_PROTOCOL: Rep = Rep::new_fatal("Unsupported protocol");
117 pub const REFUSED: Rep = Rep::new(-(1 << 10), "Request refused");
119 pub const TIMEOUT: Rep = Rep::new(-(1 << 10), "Request timeout");
121 pub const IO: Rep = Rep::new(-(1 << 10), "IO error during request");
123}
124
125struct Metrics {
126 peers: Gauge<U64>,
127 import_queue_blocks_submitted: Counter<U64>,
128 import_queue_justifications_submitted: Counter<U64>,
129}
130
131impl Metrics {
132 fn register(
133 r: &Registry,
134 major_syncing: Arc<AtomicBool>,
135 num_connected: Arc<AtomicUsize>,
136 ) -> Result<Self, PrometheusError> {
137 MajorSyncingGauge::register(r, major_syncing)?;
138 NumConnectedGauge::register(r, num_connected)?;
139 Ok(Self {
140 peers: {
141 let g = Gauge::new("substrate_sync_peers", "Number of peers we sync with")?;
142 register(g, r)?
143 },
144 import_queue_blocks_submitted: {
145 let c = Counter::new(
146 "substrate_sync_import_queue_blocks_submitted",
147 "Number of blocks submitted to the import queue.",
148 )?;
149 register(c, r)?
150 },
151 import_queue_justifications_submitted: {
152 let c = Counter::new(
153 "substrate_sync_import_queue_justifications_submitted",
154 "Number of justifications submitted to the import queue.",
155 )?;
156 register(c, r)?
157 },
158 })
159 }
160}
161
162#[derive(Clone)]
164pub struct MajorSyncingGauge(Arc<AtomicBool>);
165
166impl MajorSyncingGauge {
167 fn register(registry: &Registry, value: Arc<AtomicBool>) -> Result<(), PrometheusError> {
170 prometheus_endpoint::register(
171 SourcedGauge::new(
172 &Opts::new(
173 "substrate_sub_libp2p_is_major_syncing",
174 "Whether the node is performing a major sync or not.",
175 ),
176 MajorSyncingGauge(value),
177 )?,
178 registry,
179 )?;
180
181 Ok(())
182 }
183}
184
185impl MetricSource for MajorSyncingGauge {
186 type N = u64;
187
188 fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) {
189 set(&[], self.0.load(Ordering::Relaxed) as u64);
190 }
191}
192
193#[derive(Clone)]
195struct NumConnectedGauge(Arc<AtomicUsize>);
196
197impl NumConnectedGauge {
198 fn register(registry: &Registry, value: Arc<AtomicUsize>) -> Result<(), PrometheusError> {
201 prometheus_endpoint::register(
202 SourcedGauge::new(
203 &Opts::new("substrate_sub_libp2p_peers_count", "Number of connected peers"),
204 NumConnectedGauge(value),
205 )?,
206 registry,
207 )?;
208
209 Ok(())
210 }
211}
212
213impl MetricSource for NumConnectedGauge {
214 type N = u64;
215
216 fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) {
217 set(&[], self.0.load(Ordering::Relaxed) as u64);
218 }
219}
220
221#[derive(Debug)]
223pub struct Peer<B: BlockT> {
224 pub info: ExtendedPeerInfo<B>,
225 pub known_blocks: LruHashSet<B::Hash>,
227 inbound: bool,
229}
230
231pub struct SyncingEngine<B: BlockT, Client> {
232 strategy: Box<dyn SyncingStrategy<B>>,
234
235 client: Arc<Client>,
237
238 num_connected: Arc<AtomicUsize>,
240
241 is_major_syncing: Arc<AtomicBool>,
243
244 network_service: service::network::NetworkServiceHandle,
246
247 service_rx: TracingUnboundedReceiver<ToServiceCommand<B>>,
249
250 roles: Roles,
252
253 genesis_hash: B::Hash,
255
256 event_streams: Vec<TracingUnboundedSender<SyncEvent>>,
258
259 tick_timeout: Interval,
261
262 peers: HashMap<PeerId, Peer<B>>,
264
265 important_peers: HashSet<PeerId>,
268
269 default_peers_set_no_slot_connected_peers: HashSet<PeerId>,
271
272 default_peers_set_no_slot_peers: HashSet<PeerId>,
274
275 default_peers_set_num_full: usize,
278
279 default_peers_set_num_light: usize,
281
282 max_in_peers: usize,
284
285 num_in_peers: usize,
287
288 dynamic_no_slot_peers: HashSet<PeerId>,
291
292 block_announce_validator: BlockAnnounceValidatorStream<B>,
294
295 block_announce_data_cache: LruMap<B::Hash, Vec<u8>>,
297
298 boot_node_ids: HashSet<PeerId>,
300
301 block_announce_protocol_name: ProtocolName,
303
304 metrics: Option<Metrics>,
306
307 notification_service: Box<dyn NotificationService>,
309
310 peer_store_handle: Arc<dyn PeerStoreProvider>,
312
313 pending_responses: PendingResponses,
315
316 import_queue: Box<dyn ImportQueueService<B>>,
318}
319
320impl<B: BlockT, Client> SyncingEngine<B, Client>
321where
322 B: BlockT,
323 Client: HeaderBackend<B>
324 + BlockBackend<B>
325 + HeaderMetadata<B, Error = sp_blockchain::Error>
326 + ProofProvider<B>
327 + Send
328 + Sync
329 + 'static,
330{
331 pub fn new<N>(
332 roles: Roles,
333 client: Arc<Client>,
334 metrics_registry: Option<&Registry>,
335 network_metrics: NotificationMetrics,
336 net_config: &FullNetworkConfiguration<B, <B as BlockT>::Hash, N>,
337 protocol_id: ProtocolId,
338 fork_id: Option<&str>,
339 block_announce_validator: Box<dyn BlockAnnounceValidator<B> + Send>,
340 syncing_strategy: Box<dyn SyncingStrategy<B>>,
341 network_service: service::network::NetworkServiceHandle,
342 import_queue: Box<dyn ImportQueueService<B>>,
343 peer_store_handle: Arc<dyn PeerStoreProvider>,
344 ) -> Result<(Self, SyncingService<B>, N::NotificationProtocolConfig), ClientError>
345 where
346 N: NetworkBackend<B, <B as BlockT>::Hash>,
347 {
348 let cache_capacity = (net_config.network_config.default_peers_set.in_peers +
349 net_config.network_config.default_peers_set.out_peers)
350 .max(1);
351 let important_peers = {
352 let mut imp_p = HashSet::new();
353 for reserved in &net_config.network_config.default_peers_set.reserved_nodes {
354 imp_p.insert(reserved.peer_id);
355 }
356 for config in net_config.notification_protocols() {
357 let peer_ids = config.set_config().reserved_nodes.iter().map(|info| info.peer_id);
358 imp_p.extend(peer_ids);
359 }
360
361 imp_p.shrink_to_fit();
362 imp_p
363 };
364 let boot_node_ids = {
365 let mut list = HashSet::new();
366 for node in &net_config.network_config.boot_nodes {
367 list.insert(node.peer_id);
368 }
369 list.shrink_to_fit();
370 list
371 };
372 let default_peers_set_no_slot_peers = {
373 let mut no_slot_p: HashSet<PeerId> = net_config
374 .network_config
375 .default_peers_set
376 .reserved_nodes
377 .iter()
378 .map(|reserved| reserved.peer_id)
379 .collect();
380 no_slot_p.shrink_to_fit();
381 no_slot_p
382 };
383 let default_peers_set_num_full =
384 net_config.network_config.default_peers_set_num_full as usize;
385 let default_peers_set_num_light = {
386 let total = net_config.network_config.default_peers_set.out_peers +
387 net_config.network_config.default_peers_set.in_peers;
388 total.saturating_sub(net_config.network_config.default_peers_set_num_full) as usize
389 };
390
391 let info = client.info();
392
393 let (block_announce_config, notification_service) =
394 Self::get_block_announce_proto_config::<N>(
395 protocol_id,
396 fork_id,
397 roles,
398 info.best_number,
399 info.best_hash,
400 info.genesis_hash,
401 &net_config.network_config.default_peers_set,
402 network_metrics,
403 Arc::clone(&peer_store_handle),
404 );
405
406 let block_announce_protocol_name = block_announce_config.protocol_name().clone();
407 let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000);
408 let num_connected = Arc::new(AtomicUsize::new(0));
409 let is_major_syncing = Arc::new(AtomicBool::new(false));
410
411 let max_full_peers = net_config.network_config.default_peers_set_num_full;
414 let max_out_peers = net_config.network_config.default_peers_set.out_peers;
415 let max_in_peers = (max_full_peers - max_out_peers) as usize;
416
417 let tick_timeout = {
418 let mut interval = tokio::time::interval(TICK_TIMEOUT);
419 interval.set_missed_tick_behavior(MissedTickBehavior::Delay);
420 interval
421 };
422
423 Ok((
424 Self {
425 roles,
426 client,
427 strategy: syncing_strategy,
428 network_service,
429 peers: HashMap::new(),
430 block_announce_data_cache: LruMap::new(ByLength::new(cache_capacity)),
431 block_announce_protocol_name,
432 block_announce_validator: BlockAnnounceValidatorStream::new(
433 block_announce_validator,
434 ),
435 num_connected: num_connected.clone(),
436 is_major_syncing: is_major_syncing.clone(),
437 service_rx,
438 genesis_hash: info.genesis_hash,
439 important_peers,
440 default_peers_set_no_slot_connected_peers: HashSet::new(),
441 boot_node_ids,
442 default_peers_set_no_slot_peers,
443 default_peers_set_num_full,
444 default_peers_set_num_light,
445 num_in_peers: 0usize,
446 max_in_peers,
447 dynamic_no_slot_peers: HashSet::new(),
448 event_streams: Vec::new(),
449 notification_service,
450 tick_timeout,
451 peer_store_handle,
452 metrics: if let Some(r) = metrics_registry {
453 match Metrics::register(r, is_major_syncing.clone(), num_connected.clone()) {
454 Ok(metrics) => Some(metrics),
455 Err(err) => {
456 log::error!(target: LOG_TARGET, "Failed to register metrics {err:?}");
457 None
458 },
459 }
460 } else {
461 None
462 },
463 pending_responses: PendingResponses::new(),
464 import_queue,
465 },
466 SyncingService::new(tx, num_connected, is_major_syncing),
467 block_announce_config,
468 ))
469 }
470
471 fn update_peer_info(
472 &mut self,
473 peer_id: &PeerId,
474 best_hash: B::Hash,
475 best_number: NumberFor<B>,
476 ) {
477 if let Some(ref mut peer) = self.peers.get_mut(peer_id) {
478 peer.info.best_hash = best_hash;
479 peer.info.best_number = best_number;
480 }
481 }
482
483 fn process_block_announce_validation_result(
485 &mut self,
486 validation_result: BlockAnnounceValidationResult<B::Header>,
487 ) {
488 match validation_result {
489 BlockAnnounceValidationResult::Skip { peer_id: _ } => {},
490 BlockAnnounceValidationResult::Process { is_new_best, peer_id, announce } => {
491 if let Some((best_hash, best_number)) =
492 self.strategy.on_validated_block_announce(is_new_best, peer_id, &announce)
493 {
494 self.update_peer_info(&peer_id, best_hash, best_number);
495 }
496
497 if let Some(data) = announce.data {
498 if !data.is_empty() {
499 self.block_announce_data_cache.insert(announce.header.hash(), data);
500 }
501 }
502 },
503 BlockAnnounceValidationResult::Failure { peer_id, disconnect } => {
504 if disconnect {
505 log::debug!(
506 target: LOG_TARGET,
507 "Disconnecting peer {peer_id} due to block announce validation failure",
508 );
509 self.network_service
510 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
511 }
512
513 self.network_service.report_peer(peer_id, rep::BAD_BLOCK_ANNOUNCEMENT);
514 },
515 }
516 }
517
518 pub fn push_block_announce_validation(
520 &mut self,
521 peer_id: PeerId,
522 announce: BlockAnnounce<B::Header>,
523 ) {
524 let hash = announce.header.hash();
525
526 let peer = match self.peers.get_mut(&peer_id) {
527 Some(p) => p,
528 None => {
529 log::error!(
530 target: LOG_TARGET,
531 "Received block announce from disconnected peer {peer_id}",
532 );
533 debug_assert!(false);
534 return;
535 },
536 };
537 peer.known_blocks.insert(hash);
538
539 if peer.info.roles.is_full() {
540 let is_best = match announce.state.unwrap_or(BlockState::Best) {
541 BlockState::Best => true,
542 BlockState::Normal => false,
543 };
544
545 self.block_announce_validator
546 .push_block_announce_validation(peer_id, hash, announce, is_best);
547 }
548 }
549
550 pub fn announce_block(&mut self, hash: B::Hash, data: Option<Vec<u8>>) {
555 let header = match self.client.header(hash) {
556 Ok(Some(header)) => header,
557 Ok(None) => {
558 log::warn!(target: LOG_TARGET, "Trying to announce unknown block: {hash}");
559 return;
560 },
561 Err(e) => {
562 log::warn!(target: LOG_TARGET, "Error reading block header {hash}: {e}");
563 return;
564 },
565 };
566
567 if header.number().is_zero() {
569 return;
570 }
571
572 let is_best = self.client.info().best_hash == hash;
573 log::debug!(target: LOG_TARGET, "Reannouncing block {hash:?} is_best: {is_best}");
574
575 let data = data
576 .or_else(|| self.block_announce_data_cache.get(&hash).cloned())
577 .unwrap_or_default();
578
579 for (peer_id, ref mut peer) in self.peers.iter_mut() {
580 let inserted = peer.known_blocks.insert(hash);
581 if inserted {
582 log::trace!(target: LOG_TARGET, "Announcing block {hash:?} to {peer_id}");
583 let message = BlockAnnounce {
584 header: header.clone(),
585 state: if is_best { Some(BlockState::Best) } else { Some(BlockState::Normal) },
586 data: Some(data.clone()),
587 };
588
589 let _ = self.notification_service.send_sync_notification(peer_id, message.encode());
590 }
591 }
592 }
593
594 pub async fn run(mut self) {
595 loop {
596 tokio::select! {
597 _ = self.tick_timeout.tick() => {
598 },
602 command = self.service_rx.select_next_some() =>
603 self.process_service_command(command),
604 notification_event = self.notification_service.next_event() => match notification_event {
605 Some(event) => self.process_notification_event(event),
606 None => {
607 error!(
608 target: LOG_TARGET,
609 "Terminating `SyncingEngine` because `NotificationService` has terminated.",
610 );
611
612 return;
613 }
614 },
615 response_event = self.pending_responses.select_next_some() =>
616 self.process_response_event(response_event),
617 validation_result = self.block_announce_validator.select_next_some() =>
618 self.process_block_announce_validation_result(validation_result),
619 }
620
621 self.is_major_syncing.store(self.strategy.is_major_syncing(), Ordering::Relaxed);
623
624 if let Err(e) = self.process_strategy_actions() {
626 error!(
627 target: LOG_TARGET,
628 "Terminating `SyncingEngine` due to fatal error: {e:?}.",
629 );
630 return;
631 }
632 }
633 }
634
635 fn process_strategy_actions(&mut self) -> Result<(), ClientError> {
636 for action in self.strategy.actions(&self.network_service)? {
637 match action {
638 SyncingAction::StartRequest { peer_id, key, request } => {
639 if !self.peers.contains_key(&peer_id) {
640 trace!(
641 target: LOG_TARGET,
642 "Cannot start request with strategy key {key:?} to unknown peer \
643 {peer_id}",
644 );
645 debug_assert!(false);
646 continue;
647 }
648
649 self.pending_responses.insert(peer_id, key, request);
650 },
651 SyncingAction::CancelRequest { peer_id, key } => {
652 let removed = self.pending_responses.remove(peer_id, key);
653
654 trace!(
655 target: LOG_TARGET,
656 "Processed `SyncingAction::CancelRequest`, response removed: {removed}.",
657 );
658 },
659 SyncingAction::DropPeer(BadPeer(peer_id, rep)) => {
660 self.pending_responses.remove_all(&peer_id);
661 self.network_service
662 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
663 self.network_service.report_peer(peer_id, rep);
664
665 trace!(target: LOG_TARGET, "{peer_id:?} dropped: {rep:?}.");
666 },
667 SyncingAction::ImportBlocks { origin, blocks } => {
668 let count = blocks.len();
669 self.import_blocks(origin, blocks);
670
671 trace!(
672 target: LOG_TARGET,
673 "Processed `ChainSyncAction::ImportBlocks` with {count} blocks.",
674 );
675 },
676 SyncingAction::ImportJustifications { peer_id, hash, number, justifications } => {
677 self.import_justifications(peer_id, hash, number, justifications);
678
679 trace!(
680 target: LOG_TARGET,
681 "Processed `ChainSyncAction::ImportJustifications` from peer {} for block {} ({}).",
682 peer_id,
683 hash,
684 number,
685 )
686 },
687 SyncingAction::Finished => {},
689 }
690 }
691
692 Ok(())
693 }
694
695 fn apply_no_slot_set(&mut self, new_dynamic_no_slot: HashSet<PeerId>) {
698 let connected_peers = &self.peers;
699 apply_no_slot_set_inner(
700 |peer_id| {
701 connected_peers
702 .get(peer_id)
703 .map(|peer| peer.inbound && peer.info.roles.is_full())
704 },
705 &self.default_peers_set_no_slot_peers,
706 &self.dynamic_no_slot_peers,
707 &new_dynamic_no_slot,
708 &mut self.default_peers_set_no_slot_connected_peers,
709 &mut self.num_in_peers,
710 self.max_in_peers,
711 &self.network_service,
712 &self.block_announce_protocol_name,
713 );
714 self.dynamic_no_slot_peers = new_dynamic_no_slot;
715 }
716
717 fn process_service_command(&mut self, command: ToServiceCommand<B>) {
718 match command {
719 ToServiceCommand::SetSyncForkRequest(peers, hash, number) => {
720 self.strategy.set_sync_fork_request(peers, &hash, number);
721 },
722 ToServiceCommand::EventStream(tx) => {
723 for (peer_id, peer) in self.peers.iter() {
725 let _ = tx.unbounded_send(SyncEvent::PeerConnected {
726 peer_id: *peer_id,
727 roles: peer.info.roles,
728 });
729 }
730 self.event_streams.push(tx);
731 },
732 ToServiceCommand::RequestJustification(hash, number) => {
733 self.strategy.request_justification(&hash, number)
734 },
735 ToServiceCommand::ClearJustificationRequests => {
736 self.strategy.clear_justification_requests()
737 },
738 ToServiceCommand::BlocksProcessed(imported, count, results) => {
739 self.strategy.on_blocks_processed(imported, count, results);
740 },
741 ToServiceCommand::JustificationImported(peer_id, hash, number, import_result) => {
742 let success =
743 matches!(import_result, sc_consensus::JustificationImportResult::Success);
744 self.strategy.on_justification_import(hash, number, success);
745
746 match import_result {
747 sc_consensus::JustificationImportResult::OutdatedJustification => {
748 log::info!(
749 target: LOG_TARGET,
750 "๐ Outdated justification provided by {peer_id} for #{hash}",
751 );
752 },
753 sc_consensus::JustificationImportResult::Failure => {
754 log::info!(
755 target: LOG_TARGET,
756 "๐ Invalid justification provided by {peer_id} for #{hash}",
757 );
758 self.network_service
759 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
760 self.network_service.report_peer(
761 peer_id,
762 ReputationChange::new_fatal("Invalid justification"),
763 );
764 },
765 sc_consensus::JustificationImportResult::Success => {
766 log::debug!(
767 target: LOG_TARGET,
768 "Justification for block #{hash} ({number}) imported from {peer_id} successfully",
769 );
770 },
771 }
772 },
773 ToServiceCommand::AnnounceBlock(hash, data) => self.announce_block(hash, data),
774 ToServiceCommand::NewBestBlockImported(hash, number) => {
775 log::debug!(target: LOG_TARGET, "New best block imported {:?}/#{}", hash, number);
776
777 self.strategy.update_chain_info(&hash, number);
778 let _ = self.notification_service.try_set_handshake(
779 BlockAnnouncesHandshake::<B>::build(
780 self.roles,
781 number,
782 hash,
783 self.genesis_hash,
784 )
785 .encode(),
786 );
787 },
788 ToServiceCommand::Status(tx) => {
789 let _ = tx.send(self.strategy.status());
790 },
791 ToServiceCommand::NumActivePeers(tx) => {
792 let _ = tx.send(self.num_active_peers());
793 },
794 ToServiceCommand::NumDownloadedBlocks(tx) => {
795 let _ = tx.send(self.strategy.num_downloaded_blocks());
796 },
797 ToServiceCommand::NumSyncRequests(tx) => {
798 let _ = tx.send(self.strategy.num_sync_requests());
799 },
800 ToServiceCommand::PeersInfo(tx) => {
801 let peers_info =
802 self.peers.iter().map(|(peer_id, peer)| (*peer_id, peer.info)).collect();
803 let _ = tx.send(peers_info);
804 },
805 ToServiceCommand::SetNoSlotPeers(peers) => self.apply_no_slot_set(peers),
806 ToServiceCommand::OnBlockFinalized(hash, header) => {
807 self.strategy.on_block_finalized(&hash, *header.number())
808 },
809 }
810 }
811
812 fn process_notification_event(&mut self, event: NotificationEvent) {
813 match event {
814 NotificationEvent::ValidateInboundSubstream { peer, handshake, result_tx } => {
815 let validation_result = self
816 .validate_connection(&peer, handshake, Direction::Inbound)
817 .map_or(ValidationResult::Reject, |_| ValidationResult::Accept);
818
819 let _ = result_tx.send(validation_result);
820 },
821 NotificationEvent::NotificationStreamOpened { peer, handshake, direction, .. } => {
822 log::debug!(
823 target: LOG_TARGET,
824 "Substream opened for {peer}, handshake {handshake:?}"
825 );
826
827 match self.validate_connection(&peer, handshake, direction) {
828 Ok(handshake) => {
829 if self.on_sync_peer_connected(peer, &handshake, direction).is_err() {
830 log::debug!(target: LOG_TARGET, "Failed to register peer {peer}");
831 self.network_service
832 .disconnect_peer(peer, self.block_announce_protocol_name.clone());
833 }
834 },
835 Err(wrong_genesis) => {
836 log::debug!(target: LOG_TARGET, "`SyncingEngine` rejected {peer}");
837
838 if wrong_genesis {
839 self.peer_store_handle.report_peer(peer, rep::GENESIS_MISMATCH);
840 }
841
842 self.network_service
843 .disconnect_peer(peer, self.block_announce_protocol_name.clone());
844 },
845 }
846 },
847 NotificationEvent::NotificationStreamClosed { peer } => {
848 self.on_sync_peer_disconnected(peer);
849 },
850 NotificationEvent::NotificationReceived { peer, notification } => {
851 if !self.peers.contains_key(&peer) {
852 log::error!(
853 target: LOG_TARGET,
854 "received notification from {peer} who had been earlier refused by `SyncingEngine`",
855 );
856 return;
857 }
858
859 let Ok(announce) = BlockAnnounce::decode(&mut notification.as_ref()) else {
860 log::warn!(target: LOG_TARGET, "failed to decode block announce");
861 return;
862 };
863
864 self.push_block_announce_validation(peer, announce);
865 },
866 }
867 }
868
869 fn is_no_slot_peer(&self, peer_id: &PeerId) -> bool {
870 self.default_peers_set_no_slot_peers.contains(peer_id) ||
871 self.dynamic_no_slot_peers.contains(peer_id)
872 }
873
874 fn on_sync_peer_disconnected(&mut self, peer_id: PeerId) {
878 let Some(info) = self.peers.remove(&peer_id) else {
879 log::debug!(target: LOG_TARGET, "{peer_id} does not exist in `SyncingEngine`");
880 return;
881 };
882 if let Some(metrics) = &self.metrics {
883 metrics.peers.dec();
884 }
885 self.num_connected.fetch_sub(1, Ordering::AcqRel);
886
887 if self.important_peers.contains(&peer_id) {
888 log::warn!(target: LOG_TARGET, "Reserved peer {peer_id} disconnected");
889 } else {
890 log::debug!(target: LOG_TARGET, "{peer_id} disconnected");
891 }
892
893 if !self.default_peers_set_no_slot_connected_peers.remove(&peer_id) &&
894 info.inbound &&
895 info.info.roles.is_full()
896 {
897 match self.num_in_peers.checked_sub(1) {
898 Some(value) => {
899 self.num_in_peers = value;
900 },
901 None => {
902 log::error!(
903 target: LOG_TARGET,
904 "trying to disconnect an inbound node which is not counted as inbound"
905 );
906 debug_assert!(false);
907 },
908 }
909 }
910
911 self.strategy.remove_peer(&peer_id);
912 self.pending_responses.remove_all(&peer_id);
913 self.event_streams
914 .retain(|stream| stream.unbounded_send(SyncEvent::PeerDisconnected(peer_id)).is_ok());
915 }
916
917 fn validate_handshake(
919 &mut self,
920 peer_id: &PeerId,
921 handshake: Vec<u8>,
922 ) -> Result<BlockAnnouncesHandshake<B>, bool> {
923 log::trace!(target: LOG_TARGET, "Validate handshake for {peer_id}");
924
925 let handshake = <BlockAnnouncesHandshake<B> as DecodeAll>::decode_all(&mut &handshake[..])
926 .map_err(|error| {
927 log::debug!(target: LOG_TARGET, "Failed to decode handshake for {peer_id}: {error:?}");
928 false
929 })?;
930
931 if handshake.genesis_hash != self.genesis_hash {
932 if self.important_peers.contains(&peer_id) {
933 log::error!(
934 target: LOG_TARGET,
935 "Reserved peer id `{peer_id}` is on a different chain (our genesis: {} theirs: {})",
936 self.genesis_hash,
937 handshake.genesis_hash,
938 );
939 } else if self.boot_node_ids.contains(&peer_id) {
940 log::error!(
941 target: LOG_TARGET,
942 "Bootnode with peer id `{peer_id}` is on a different chain (our genesis: {} theirs: {})",
943 self.genesis_hash,
944 handshake.genesis_hash,
945 );
946 } else {
947 log::debug!(
948 target: LOG_TARGET,
949 "Peer is on different chain (our genesis: {} theirs: {})",
950 self.genesis_hash,
951 handshake.genesis_hash
952 );
953 }
954
955 return Err(true);
956 }
957
958 Ok(handshake)
959 }
960
961 fn validate_connection(
975 &mut self,
976 peer_id: &PeerId,
977 handshake: Vec<u8>,
978 direction: Direction,
979 ) -> Result<BlockAnnouncesHandshake<B>, bool> {
980 log::trace!(target: LOG_TARGET, "New peer {peer_id} {handshake:?}");
981
982 let handshake = self.validate_handshake(peer_id, handshake)?;
983
984 if self.peers.contains_key(&peer_id) {
985 log::error!(
986 target: LOG_TARGET,
987 "Called `validate_connection()` with already connected peer {peer_id}",
988 );
989 debug_assert!(false);
990 return Err(false);
991 }
992
993 let no_slot_peer = self.is_no_slot_peer(&peer_id);
994 let this_peer_reserved_slot: usize = if no_slot_peer { 1 } else { 0 };
995
996 if handshake.roles.is_full() &&
997 self.strategy.num_peers() >=
998 self.default_peers_set_num_full +
999 self.default_peers_set_no_slot_connected_peers.len() +
1000 this_peer_reserved_slot
1001 {
1002 log::debug!(
1003 target: LOG_TARGET,
1004 "Too many full nodes, rejecting {peer_id} (no_slot_peer={no_slot_peer}, num_peers={}, full_cap={}, no_slot_connected={}, this_reserved={})",
1005 self.strategy.num_peers(),
1006 self.default_peers_set_num_full,
1007 self.default_peers_set_no_slot_connected_peers.len(),
1008 this_peer_reserved_slot,
1009 );
1010 return Err(false);
1011 }
1012
1013 if !no_slot_peer &&
1015 handshake.roles.is_full() &&
1016 direction.is_inbound() &&
1017 self.num_in_peers >= self.max_in_peers
1018 {
1019 if self.num_in_peers > self.max_in_peers {
1020 log::warn!(
1021 target: LOG_TARGET,
1022 "num_in_peers ({}) exceeds max_in_peers ({}), this is a slot accounting bug ",
1023 self.num_in_peers,
1024 self.max_in_peers,
1025 );
1026 debug_assert!(false);
1027 }
1028 log::debug!(
1029 target: LOG_TARGET,
1030 "All inbound slots have been consumed, rejecting {peer_id} (no_slot_peer={no_slot_peer}, num_in_peers={}, max_in_peers={})",
1031 self.num_in_peers,
1032 self.max_in_peers,
1033 );
1034 return Err(false);
1035 }
1036
1037 if handshake.roles.is_light() &&
1042 (self.peers.len() - self.strategy.num_peers()) >= self.default_peers_set_num_light
1043 {
1044 log::debug!(target: LOG_TARGET, "Too many light nodes, rejecting {peer_id}");
1045 return Err(false);
1046 }
1047
1048 Ok(handshake)
1049 }
1050
1051 fn on_sync_peer_connected(
1057 &mut self,
1058 peer_id: PeerId,
1059 status: &BlockAnnouncesHandshake<B>,
1060 direction: Direction,
1061 ) -> Result<(), ()> {
1062 log::trace!(target: LOG_TARGET, "New peer {peer_id} {status:?}");
1063
1064 let peer = Peer {
1065 info: ExtendedPeerInfo {
1066 roles: status.roles,
1067 best_hash: status.best_hash,
1068 best_number: status.best_number,
1069 },
1070 known_blocks: LruHashSet::new(
1071 NonZeroUsize::new(MAX_KNOWN_BLOCKS).expect("Constant is nonzero"),
1072 ),
1073 inbound: direction.is_inbound(),
1074 };
1075
1076 if status.roles.is_full() {
1078 self.strategy.add_peer(peer_id, peer.info.best_hash, peer.info.best_number);
1079 }
1080
1081 log::debug!(target: LOG_TARGET, "Connected {peer_id}");
1082
1083 if self.peers.insert(peer_id, peer).is_none() {
1084 if let Some(metrics) = &self.metrics {
1085 metrics.peers.inc();
1086 }
1087 self.num_connected.fetch_add(1, Ordering::AcqRel);
1088 }
1089 self.peer_store_handle.set_peer_role(&peer_id, status.roles.into());
1090
1091 if self.is_no_slot_peer(&peer_id) {
1092 self.default_peers_set_no_slot_connected_peers.insert(peer_id);
1093 } else if direction.is_inbound() && status.roles.is_full() {
1094 self.num_in_peers += 1;
1095 }
1096
1097 self.event_streams.retain(|stream| {
1098 stream
1099 .unbounded_send(SyncEvent::PeerConnected { peer_id, roles: status.roles })
1100 .is_ok()
1101 });
1102
1103 Ok(())
1104 }
1105
1106 fn process_response_event(&mut self, response_event: ResponseEvent) {
1107 let ResponseEvent { peer_id, key, response: response_result } = response_event;
1108
1109 match response_result {
1110 Ok(Ok((response, protocol_name))) => {
1111 self.strategy.on_generic_response(&peer_id, key, protocol_name, response);
1112 },
1113 Ok(Err(e)) => {
1114 debug!(target: LOG_TARGET, "Request to peer {peer_id:?} failed: {e:?}.");
1115
1116 match e {
1117 RequestFailure::Network(OutboundFailure::Timeout) => {
1118 self.network_service.report_peer(peer_id, rep::TIMEOUT);
1119 self.network_service
1120 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1121 },
1122 RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => {
1123 self.network_service.report_peer(peer_id, rep::BAD_PROTOCOL);
1124 self.network_service
1125 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1126 },
1127 RequestFailure::Network(OutboundFailure::DialFailure) => {
1128 self.network_service
1129 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1130 },
1131 RequestFailure::Refused => {
1132 self.network_service.report_peer(peer_id, rep::REFUSED);
1133 self.network_service
1134 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1135 },
1136 RequestFailure::Network(OutboundFailure::ConnectionClosed) |
1137 RequestFailure::NotConnected => {
1138 self.network_service
1139 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1140 },
1141 RequestFailure::UnknownProtocol => {
1142 debug_assert!(false, "Block request protocol should always be known.");
1143 },
1144 RequestFailure::InvalidRequest => {
1145 debug_assert!(false, "Block request payload should always be valid.");
1146 },
1147 RequestFailure::Obsolete => {
1148 debug_assert!(
1149 false,
1150 "Can not receive `RequestFailure::Obsolete` after dropping the \
1151 response receiver.",
1152 );
1153 },
1154 RequestFailure::Network(OutboundFailure::Io(_)) => {
1155 self.network_service.report_peer(peer_id, rep::IO);
1156 self.network_service
1157 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1158 },
1159 }
1160 },
1161 Err(oneshot::Canceled) => {
1162 trace!(
1163 target: LOG_TARGET,
1164 "Request to peer {peer_id:?} failed due to oneshot being canceled.",
1165 );
1166 self.network_service
1167 .disconnect_peer(peer_id, self.block_announce_protocol_name.clone());
1168 },
1169 }
1170 }
1171
1172 fn num_active_peers(&self) -> usize {
1174 self.pending_responses.len()
1175 }
1176
1177 fn get_block_announce_proto_config<N: NetworkBackend<B, <B as BlockT>::Hash>>(
1179 protocol_id: ProtocolId,
1180 fork_id: Option<&str>,
1181 roles: Roles,
1182 best_number: NumberFor<B>,
1183 best_hash: B::Hash,
1184 genesis_hash: B::Hash,
1185 set_config: &SetConfig,
1186 metrics: NotificationMetrics,
1187 peer_store_handle: Arc<dyn PeerStoreProvider>,
1188 ) -> (N::NotificationProtocolConfig, Box<dyn NotificationService>) {
1189 let block_announces_protocol = block_announces_protocol_name(genesis_hash, fork_id);
1190
1191 N::notification_config(
1192 block_announces_protocol.into(),
1193 iter::once(block_announces_legacy_protocol_name(&protocol_id).into()).collect(),
1194 MAX_BLOCK_ANNOUNCE_SIZE,
1195 Some(NotificationHandshake::new(BlockAnnouncesHandshake::<B>::build(
1196 roles,
1197 best_number,
1198 best_hash,
1199 genesis_hash,
1200 ))),
1201 set_config.clone(),
1202 metrics,
1203 peer_store_handle,
1204 )
1205 }
1206
1207 fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>) {
1209 if let Some(metrics) = &self.metrics {
1210 metrics.import_queue_blocks_submitted.inc();
1211 }
1212
1213 self.import_queue.import_blocks(origin, blocks);
1214 }
1215
1216 fn import_justifications(
1218 &mut self,
1219 peer_id: PeerId,
1220 hash: B::Hash,
1221 number: NumberFor<B>,
1222 justifications: Justifications,
1223 ) {
1224 if let Some(metrics) = &self.metrics {
1225 metrics.import_queue_justifications_submitted.inc();
1226 }
1227
1228 self.import_queue.import_justifications(peer_id, hash, number, justifications);
1229 }
1230}
1231
1232fn apply_no_slot_set_inner(
1243 peer_inbound_full: impl Fn(&PeerId) -> Option<bool>,
1244 static_no_slot: &HashSet<PeerId>,
1245 old_dynamic_no_slot: &HashSet<PeerId>,
1246 new_dynamic_no_slot: &HashSet<PeerId>,
1247 connected_no_slot: &mut HashSet<PeerId>,
1248 num_in_peers: &mut usize,
1249 max_in_peers: usize,
1250 network_service: &service::network::NetworkServiceHandle,
1251 protocol: &ProtocolName,
1252) {
1253 let slot_impact = |peer_id: &PeerId| -> Option<bool> {
1255 if static_no_slot.contains(peer_id) {
1256 return None;
1257 }
1258 peer_inbound_full(peer_id)
1259 };
1260
1261 let mut promoted = 0;
1262 let mut demoted = 0;
1263 let mut disconnected = 0;
1264
1265 for peer_id in new_dynamic_no_slot.difference(old_dynamic_no_slot) {
1266 let Some(affects_slots) = slot_impact(peer_id) else { continue };
1267 if !connected_no_slot.insert(*peer_id) {
1269 log::error!(
1270 target: LOG_TARGET,
1271 "{peer_id} promoted to no-slot but was already in connected_no_slot",
1272 );
1273 debug_assert!(false);
1274 continue;
1275 }
1276 if affects_slots {
1277 if let Some(n) = num_in_peers.checked_sub(1) {
1278 *num_in_peers = n;
1279 } else {
1280 log::error!(
1281 target: LOG_TARGET,
1282 "num_in_peers underflow promoting {peer_id} to no-slot",
1283 );
1284 debug_assert!(false);
1285 }
1286 promoted += 1;
1287 }
1288 }
1289
1290 for peer_id in old_dynamic_no_slot.difference(new_dynamic_no_slot) {
1291 let Some(affects_slots) = slot_impact(peer_id) else { continue };
1292 if !connected_no_slot.contains(peer_id) {
1293 continue;
1294 }
1295 if affects_slots && *num_in_peers >= max_in_peers {
1296 log::debug!(
1297 target: LOG_TARGET,
1298 "Demoting {peer_id} would exceed max_in_peers ({max_in_peers}); disconnecting",
1299 );
1300 network_service.disconnect_peer(*peer_id, protocol.clone());
1301 disconnected += 1;
1302 continue;
1303 }
1304 connected_no_slot.remove(peer_id);
1305 if affects_slots {
1306 *num_in_peers += 1;
1307 demoted += 1;
1308 }
1309 }
1310
1311 log::debug!(
1312 target: LOG_TARGET,
1313 "Dynamic no-slot peer set updated: {} peers: +{} in, -{} out, {} disconnected",
1314 new_dynamic_no_slot.len(),
1315 promoted,
1316 demoted,
1317 disconnected,
1318 );
1319}
1320
1321#[cfg(test)]
1322mod tests {
1323 use super::*;
1324
1325 fn fresh_peers<const N: usize>() -> [PeerId; N] {
1326 std::array::from_fn(|_| PeerId::random())
1327 }
1328
1329 fn set_of<const N: usize>(peers: [PeerId; N]) -> HashSet<PeerId> {
1330 peers.into_iter().collect()
1331 }
1332
1333 #[track_caller]
1337 fn run_apply(
1338 connected: Vec<(PeerId, bool)>,
1339 static_no_slot: HashSet<PeerId>,
1340 old_dynamic: HashSet<PeerId>,
1341 new_dynamic: HashSet<PeerId>,
1342 initial_connected_no_slot: HashSet<PeerId>,
1343 initial_num_in_peers: usize,
1344 ) -> (HashSet<PeerId>, usize) {
1345 let (connected_no_slot, num_in, disconnects) = run_apply_with_cap(
1346 connected,
1347 static_no_slot,
1348 old_dynamic,
1349 new_dynamic,
1350 initial_connected_no_slot,
1351 initial_num_in_peers,
1352 usize::MAX,
1353 );
1354 assert!(disconnects.is_empty(), "unexpected disconnects: {disconnects:?}");
1355 (connected_no_slot, num_in)
1356 }
1357
1358 #[track_caller]
1362 fn run_apply_with_cap(
1363 connected: Vec<(PeerId, bool)>,
1364 static_no_slot: HashSet<PeerId>,
1365 old_dynamic: HashSet<PeerId>,
1366 new_dynamic: HashSet<PeerId>,
1367 initial_connected_no_slot: HashSet<PeerId>,
1368 initial_num_in_peers: usize,
1369 max_in_peers: usize,
1370 ) -> (HashSet<PeerId>, usize, Vec<PeerId>) {
1371 use crate::service::network::{NetworkServiceHandle, ToServiceCommand as NetCmd};
1372
1373 let peer_inbound_full: HashMap<PeerId, bool> = connected.into_iter().collect();
1374 let (tx, mut rx) = tracing_unbounded::<NetCmd>("test_apply_no_slot_set_disconnects", 100);
1375 let network_service = NetworkServiceHandle::new(tx);
1376 let protocol: ProtocolName = "/test/block-announces/1".into();
1377 let mut connected_no_slot = initial_connected_no_slot;
1378 let mut num_in_peers = initial_num_in_peers;
1379 apply_no_slot_set_inner(
1380 |peer_id| peer_inbound_full.get(peer_id).copied(),
1381 &static_no_slot,
1382 &old_dynamic,
1383 &new_dynamic,
1384 &mut connected_no_slot,
1385 &mut num_in_peers,
1386 max_in_peers,
1387 &network_service,
1388 &protocol,
1389 );
1390 drop(network_service);
1391
1392 let mut disconnects = Vec::new();
1393 while let Ok(cmd) = rx.try_recv() {
1394 if let NetCmd::DisconnectPeer(peer, _) = cmd {
1395 disconnects.push(peer);
1396 }
1397 }
1398 (connected_no_slot, num_in_peers, disconnects)
1399 }
1400
1401 #[test]
1402 fn apply_promotes_multiple_inbound_full_peers() {
1403 let [a, b, c, already] = fresh_peers();
1406 let (connected_no_slot, num_in) = run_apply(
1407 vec![(a, true), (b, true), (c, true), (already, true)],
1408 HashSet::new(),
1409 set_of([already]),
1410 set_of([a, b, c, already]),
1411 set_of([already]),
1412 10,
1413 );
1414 assert_eq!(connected_no_slot, set_of([a, b, c, already]));
1415 assert_eq!(num_in, 7);
1416 }
1417
1418 #[test]
1419 fn apply_demotes_multiple_inbound_full_peers() {
1420 let [a, b, c, stays] = fresh_peers();
1421 let (connected_no_slot, num_in) = run_apply(
1422 vec![(a, true), (b, true), (c, true), (stays, true)],
1423 HashSet::new(),
1424 set_of([a, b, c, stays]),
1425 set_of([stays]),
1426 set_of([a, b, c, stays]),
1427 2,
1428 );
1429 assert_eq!(connected_no_slot, set_of([stays]));
1430 assert_eq!(num_in, 5);
1431 }
1432
1433 #[test]
1434 fn apply_ignores_non_slot_consuming_peers() {
1435 let [outbound, light] = fresh_peers();
1438 let (connected_no_slot, num_in) = run_apply(
1439 vec![(outbound, false), (light, false)],
1440 HashSet::new(),
1441 HashSet::new(),
1442 set_of([outbound, light]),
1443 HashSet::new(),
1444 5,
1445 );
1446 assert_eq!(connected_no_slot, set_of([outbound, light]));
1447 assert_eq!(num_in, 5);
1448 }
1449
1450 #[test]
1451 fn apply_static_peers_stay_no_slot_when_removed_from_dynamic() {
1452 let [s1, s2, control] = fresh_peers();
1455 let (connected_no_slot, num_in) = run_apply(
1456 vec![(s1, true), (s2, true), (control, true)],
1457 set_of([s1, s2]),
1458 set_of([s1, s2, control]),
1459 HashSet::new(),
1460 set_of([s1, s2, control]),
1461 2,
1462 );
1463 assert_eq!(connected_no_slot, set_of([s1, s2]));
1464 assert_eq!(num_in, 3);
1465 }
1466
1467 #[test]
1468 fn apply_static_peers_added_to_dynamic_are_unchanged() {
1469 let [s1, s2] = fresh_peers();
1470 let (connected_no_slot, num_in) = run_apply(
1471 vec![(s1, true), (s2, true)],
1472 set_of([s1, s2]),
1473 HashSet::new(),
1474 set_of([s1, s2]),
1475 set_of([s1, s2]),
1476 4,
1477 );
1478 assert_eq!(connected_no_slot, set_of([s1, s2]));
1479 assert_eq!(num_in, 4);
1480 }
1481
1482 #[test]
1483 fn apply_unconnected_peers_in_new_set_are_ignored() {
1484 let [connected_a, connected_b] = fresh_peers();
1487 let [unconnected_a, unconnected_b] = fresh_peers();
1488 let (connected_no_slot, num_in) = run_apply(
1489 vec![(connected_a, true), (connected_b, true)],
1490 HashSet::new(),
1491 HashSet::new(),
1492 set_of([unconnected_a, unconnected_b]),
1493 HashSet::new(),
1494 3,
1495 );
1496 assert!(connected_no_slot.is_empty());
1497 assert_eq!(num_in, 3);
1498 }
1499
1500 #[test]
1501 fn apply_idempotent_same_set() {
1502 let [in_full, out_full, light] = fresh_peers();
1503 let target = set_of([in_full, out_full, light]);
1504 let (connected_no_slot, num_in) = run_apply(
1505 vec![(in_full, true), (out_full, false), (light, false)],
1506 HashSet::new(),
1507 target.clone(),
1508 target.clone(),
1509 target.clone(),
1510 2,
1511 );
1512 assert_eq!(connected_no_slot, target);
1513 assert_eq!(num_in, 2);
1514 }
1515
1516 #[test]
1517 fn apply_empty_set_clears_dynamic_only_peers() {
1518 let [in1, in2, out, light] = fresh_peers();
1519 let [static_peer] = fresh_peers();
1520 let old = set_of([in1, in2, out, light, static_peer]);
1521 let (connected_no_slot, num_in) = run_apply(
1522 vec![(in1, true), (in2, true), (out, false), (light, false), (static_peer, true)],
1523 set_of([static_peer]),
1524 old.clone(),
1525 HashSet::new(),
1526 old,
1527 0,
1528 );
1529 assert_eq!(connected_no_slot, set_of([static_peer]));
1530 assert_eq!(num_in, 2);
1531 }
1532
1533 #[test]
1534 fn apply_mixed_promote_and_demote() {
1535 let [p1, p2] = fresh_peers();
1536 let [d1, d2] = fresh_peers();
1537 let (connected_no_slot, num_in) = run_apply(
1538 vec![(p1, true), (p2, true), (d1, true), (d2, true)],
1539 HashSet::new(),
1540 set_of([d1, d2]),
1541 set_of([p1, p2]),
1542 set_of([d1, d2]),
1543 5,
1544 );
1545 assert_eq!(connected_no_slot, set_of([p1, p2]));
1546 assert_eq!(num_in, 5);
1547 }
1548
1549 #[test]
1550 fn apply_demote_at_capacity_disconnects_peer() {
1551 let [px] = fresh_peers();
1557 let (connected_no_slot, num_in, disconnects) = run_apply_with_cap(
1558 vec![(px, true)],
1559 HashSet::new(),
1560 set_of([px]),
1561 HashSet::new(),
1562 set_of([px]),
1563 8,
1564 8,
1565 );
1566 assert_eq!(connected_no_slot, set_of([px]));
1567 assert_eq!(num_in, 8);
1568 assert_eq!(disconnects, vec![px]);
1569 }
1570
1571 #[test]
1572 fn apply_demote_below_capacity_increments_normally() {
1573 let [px] = fresh_peers();
1576 let (connected_no_slot, num_in, disconnects) = run_apply_with_cap(
1577 vec![(px, true)],
1578 HashSet::new(),
1579 set_of([px]),
1580 HashSet::new(),
1581 set_of([px]),
1582 7,
1583 8,
1584 );
1585 assert!(connected_no_slot.is_empty());
1586 assert_eq!(num_in, 8);
1587 assert!(disconnects.is_empty());
1588 }
1589
1590 #[test]
1591 fn num_connected_gauge_tracks_the_shared_counter() {
1592 let registry = Registry::new();
1593 let num_connected = Arc::new(AtomicUsize::new(0));
1594 NumConnectedGauge::register(®istry, num_connected.clone()).unwrap();
1595
1596 let peers_count = || {
1597 registry
1598 .gather()
1599 .iter()
1600 .find(|family| family.get_name() == "substrate_sub_libp2p_peers_count")
1601 .map(|family| family.get_metric()[0].get_gauge().get_value())
1602 };
1603
1604 assert_eq!(peers_count(), Some(0.0));
1605
1606 num_connected.store(3, Ordering::Relaxed);
1607 assert_eq!(peers_count(), Some(3.0));
1608
1609 num_connected.store(0, Ordering::Relaxed);
1610 assert_eq!(peers_count(), Some(0.0));
1611 }
1612}