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