smoldot_light/
network_service.rs

1// Smoldot
2// Copyright (C) 2019-2022  Parity Technologies (UK) Ltd.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// This program is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18//! Background network service.
19//!
20//! The [`NetworkService`] manages background tasks dedicated to connecting to other nodes.
21//! Importantly, its design is oriented towards the particular use case of the light client.
22//!
23//! The [`NetworkService`] spawns one background task (using [`PlatformRef::spawn_task`]) for
24//! each active connection.
25//!
26//! The objective of the [`NetworkService`] in general is to try stay connected as much as
27//! possible to the nodes of the peer-to-peer network of the chain, and maintain open substreams
28//! with them in order to send out requests (e.g. block requests) and notifications (e.g. block
29//! announces).
30//!
31//! Connectivity to the network is performed in the background as an implementation detail of
32//! the service. The public API only allows emitting requests and notifications towards the
33//! already-connected nodes.
34//!
35//! After a [`NetworkService`] is created, one can add chains using [`NetworkService::add_chain`].
36//! If all references to a [`NetworkServiceChain`] are destroyed, the chain is automatically
37//! purged.
38//!
39//! An important part of the API is the list of channel receivers of [`Event`] returned by
40//! [`NetworkServiceChain::subscribe`]. These channels inform the foreground about updates to the
41//! network connectivity.
42
43use crate::{
44    log, metrics,
45    platform::{self, PlatformRef, address_parse},
46};
47
48use alloc::{
49    borrow::ToOwned as _,
50    boxed::Box,
51    collections::{BTreeMap, VecDeque},
52    format,
53    string::{String, ToString as _},
54    sync::Arc,
55    vec::{self, Vec},
56};
57use core::{cmp, mem, num::NonZero, pin::Pin, time::Duration};
58use futures_channel::oneshot;
59use futures_lite::FutureExt as _;
60use futures_util::{StreamExt as _, future, stream};
61use hashbrown::{HashMap, HashSet};
62use rand::seq::IteratorRandom as _;
63use rand_chacha::rand_core::SeedableRng as _;
64use smoldot::{
65    header,
66    informant::{BytesDisplay, HashDisplay},
67    libp2p::{
68        connection,
69        multiaddr::{self, Multiaddr},
70        peer_id,
71    },
72    network::{basic_peering_strategy, bitswap_peering_strategy, codec, service},
73};
74
75pub use codec::{AffinityFilter, CallProofRequestConfig, Role};
76use service::SendTopicAffinityError;
77pub use service::{
78    ChainId, EncodedMerkleProof, PeerId, QueueNotificationError, SendBitswapMessageError,
79};
80
81mod tasks;
82
83/// Delay between two attempts to reopen the transactions, GrandPa and statement substreams
84/// of a gossip link. Only the first closing on a link is answered at once, every later one
85/// waits this delay.
86const SECONDARY_SUBSTREAM_RETRY_DELAY: Duration = Duration::from_millis(200);
87
88/// Configuration for a [`NetworkService`].
89pub struct Config<TPlat> {
90    /// Access to the platform's capabilities.
91    pub platform: TPlat,
92
93    /// Value sent back for the agent version when receiving an identification request.
94    pub identify_agent_version: String,
95
96    /// Capacity to allocate for the list of chains.
97    pub chains_capacity: usize,
98
99    /// Maximum number of connections that the service can open simultaneously. After this value
100    /// has been reached, a new connection can be opened after each
101    /// [`Config::connections_open_pool_restore_delay`].
102    pub connections_open_pool_size: u32,
103
104    /// Delay after which the service can open a new connection.
105    /// The delay is cumulative. If no connection has been opened for example for twice this
106    /// duration, then two connections can be opened at the same time, up to a maximum of
107    /// [`Config::connections_open_pool_size`].
108    pub connections_open_pool_restore_delay: Duration,
109}
110
111/// See [`NetworkService::add_chain`].
112///
113/// Note that this configuration is intentionally missing a field containing the bootstrap
114/// nodes of the chain. Bootstrap nodes are supposed to be added afterwards by calling
115/// [`NetworkServiceChain::discover`].
116pub struct ConfigChain {
117    /// Name of the chain, for logging purposes.
118    pub log_name: String,
119
120    /// Number of "out slots" of this chain. We establish simultaneously gossip substreams up to
121    /// this number of peers.
122    pub num_out_slots: usize,
123
124    /// Hash of the genesis block of the chain. Sent to other nodes in order to determine whether
125    /// the chains match.
126    ///
127    /// > **Note**: Be aware that this *must* be the *genesis* block, not any block known to be
128    /// >           in the chain.
129    pub genesis_block_hash: [u8; 32],
130
131    /// Number and hash of the current best block. Can later be updated with
132    /// [`NetworkServiceChain::set_local_best_block`].
133    pub best_block: (u64, [u8; 32]),
134
135    /// Optional identifier to insert into the networking protocol names. Used to differentiate
136    /// between chains with the same genesis hash.
137    pub fork_id: Option<String>,
138
139    /// Number of bytes of the block number in the networking protocol.
140    pub block_number_bytes: usize,
141
142    /// Must be `Some` if and only if the chain uses the GrandPa networking protocol. Contains the
143    /// number of the finalized block at the time of the initialization.
144    pub grandpa_protocol_finalized_block_height: Option<u64>,
145
146    /// If `true`, enables the statement store protocol.
147    pub enable_statement_protocol: bool,
148
149    /// Metrics of the chain.
150    pub metrics: Arc<metrics::ChainMetrics>,
151}
152
153pub struct NetworkService<TPlat: PlatformRef> {
154    /// Channel connected to the background service.
155    messages_tx: async_channel::Sender<ToBackground<TPlat>>,
156
157    /// See [`Config::platform`].
158    platform: TPlat,
159
160    /// Process-wide network metrics, shared with the background service.
161    metrics: Arc<metrics::NetworkMetrics>,
162}
163
164impl<TPlat: PlatformRef> NetworkService<TPlat> {
165    /// Initializes the network service with the given configuration.
166    pub fn new(config: Config<TPlat>) -> Arc<Self> {
167        let (main_messages_tx, main_messages_rx) = async_channel::bounded(4);
168
169        let metrics = Arc::new(metrics::NetworkMetrics::default());
170
171        let network = service::ChainNetwork::new(service::Config {
172            chains_capacity: config.chains_capacity,
173            connections_capacity: 32,
174            // Shortened from 8s: parallel dials hold slots until this fires.
175            handshake_timeout: Duration::from_secs(4),
176            randomness_seed: {
177                let mut seed = [0; 32];
178                config.platform.fill_random_bytes(&mut seed);
179                seed
180            },
181        });
182
183        // Spawn main task that processes the network service.
184        let (tasks_messages_tx, tasks_messages_rx) = async_channel::bounded(32);
185        let task = Box::pin(background_task(BackgroundTask {
186            randomness: rand_chacha::ChaCha20Rng::from_seed({
187                let mut seed = [0; 32];
188                config.platform.fill_random_bytes(&mut seed);
189                seed
190            }),
191            identify_agent_version: config.identify_agent_version,
192            tasks_messages_tx,
193            tasks_messages_rx: Box::pin(tasks_messages_rx),
194            peering_strategy: basic_peering_strategy::BasicPeeringStrategy::new(
195                basic_peering_strategy::Config {
196                    randomness_seed: {
197                        let mut seed = [0; 32];
198                        config.platform.fill_random_bytes(&mut seed);
199                        seed
200                    },
201                    peers_capacity: 50, // TODO: ?
202                    chains_capacity: config.chains_capacity,
203                },
204            ),
205            bitswap_peering_strategy: bitswap_peering_strategy::BitswapPeeringStrategy::new(
206                bitswap_peering_strategy::Config {
207                    randomness_seed: {
208                        let mut seed = [0; 32];
209                        config.platform.fill_random_bytes(&mut seed);
210                        seed
211                    },
212                    peers_capacity: 50, // TODO: hardcoded to the same value as `peering_strategy`.
213                },
214            ),
215            network,
216            connections_open_pool_size: config.connections_open_pool_size,
217            connections_open_pool_restore_delay: config.connections_open_pool_restore_delay,
218            num_recent_connection_opening: 0,
219            next_recent_connection_restore: None,
220            platform: config.platform.clone(),
221            metrics: metrics.clone(),
222            open_gossip_links: BTreeMap::new(),
223            chains_ever_gossip_connected: HashSet::with_capacity_and_hasher(4, Default::default()),
224            v2_statement_peers: HashMap::with_capacity_and_hasher(4, Default::default()),
225            current_affinity_filter: HashMap::with_capacity_and_hasher(4, Default::default()),
226            events_pending_send: VecDeque::with_capacity(4),
227            event_senders: either::Left(Vec::new()),
228            pending_new_subscriptions: Vec::new(),
229            bitswap_event_pending_send: None,
230            bitswap_connected_peers: 0,
231            bitswap_event_senders: either::Left(Vec::new()),
232            pending_new_bitswap_subscriptions: Vec::new(),
233            statement_event_pending_send: None,
234            statement_event_senders: either::Left(Vec::new()),
235            pending_new_statement_subscriptions: Vec::new(),
236            important_nodes: HashMap::with_capacity_and_hasher(16, Default::default()),
237            main_messages_rx: Box::pin(main_messages_rx),
238            messages_rx: stream::SelectAll::new(),
239            blocks_requests: HashMap::with_capacity_and_hasher(8, Default::default()),
240            grandpa_warp_sync_requests: HashMap::with_capacity_and_hasher(8, Default::default()),
241            storage_proof_requests: HashMap::with_capacity_and_hasher(8, Default::default()),
242            call_proof_requests: HashMap::with_capacity_and_hasher(8, Default::default()),
243            child_storage_proof_requests: HashMap::with_capacity_and_hasher(8, Default::default()),
244            chains_by_next_discovery: BTreeMap::new(),
245            secondary_reopens: BTreeMap::new(),
246        }));
247
248        config.platform.spawn_task("network-service".into(), {
249            let platform = config.platform.clone();
250            async move {
251                task.await;
252                log!(&platform, Debug, "network", "shutdown");
253            }
254        });
255
256        Arc::new(NetworkService {
257            messages_tx: main_messages_tx,
258            platform: config.platform,
259            metrics,
260        })
261    }
262
263    /// Returns the process-wide network metrics.
264    pub fn metrics(&self) -> Arc<metrics::NetworkMetrics> {
265        self.metrics.clone()
266    }
267
268    /// Adds a chain to the list of chains that the network service connects to.
269    ///
270    /// Returns an object representing the chain and that allows interacting with it. If all
271    /// references to [`NetworkServiceChain`] are destroyed, the network service automatically
272    /// purges that chain.
273    pub fn add_chain(&self, config: ConfigChain) -> Arc<NetworkServiceChain<TPlat>> {
274        let (messages_tx, messages_rx) = async_channel::bounded(32);
275        let chain_metrics = config.metrics.clone();
276
277        // TODO: this code is hacky because we don't want to make `add_chain` async at the moment, because it's not convenient for lib.rs
278        self.platform.spawn_task("add-chain-message-send".into(), {
279            let config = service::ChainConfig {
280                grandpa_protocol_config: config.grandpa_protocol_finalized_block_height.map(
281                    |commit_finalized_height| service::GrandpaState {
282                        commit_finalized_height,
283                        round_number: 1,
284                        set_id: 0,
285                    },
286                ),
287                enable_statement_protocol: config.enable_statement_protocol,
288                fork_id: config.fork_id.clone(),
289                block_number_bytes: config.block_number_bytes,
290                best_hash: config.best_block.1,
291                best_number: config.best_block.0,
292                genesis_hash: config.genesis_block_hash,
293                role: Role::Light,
294                allow_inbound_block_requests: false,
295                user_data: Chain {
296                    log_name: config.log_name,
297                    block_number_bytes: config.block_number_bytes,
298                    num_out_slots: config.num_out_slots,
299                    num_references: NonZero::<usize>::new(1).unwrap(),
300                    next_discovery_period: Duration::from_secs(2),
301                    next_discovery_when: self.platform.now(),
302                    metrics: config.metrics,
303                },
304            };
305
306            let messages_tx = self.messages_tx.clone();
307            async move {
308                let _ = messages_tx
309                    .send(ToBackground::AddChain {
310                        messages_rx,
311                        config,
312                    })
313                    .await;
314            }
315        });
316
317        Arc::new(NetworkServiceChain {
318            _keep_alive_messages_tx: self.messages_tx.clone(),
319            messages_tx,
320            metrics: chain_metrics,
321            platform: self.platform.clone(),
322        })
323    }
324}
325
326pub struct NetworkServiceChain<TPlat: PlatformRef> {
327    /// Copy of [`NetworkService::messages_tx`]. Used in order to maintain the network service
328    /// background task alive.
329    _keep_alive_messages_tx: async_channel::Sender<ToBackground<TPlat>>,
330
331    /// Channel to send messages to the background task.
332    messages_tx: async_channel::Sender<ToBackgroundChain>,
333
334    /// Metrics of the chain.
335    metrics: Arc<metrics::ChainMetrics>,
336
337    /// See [`Config::platform`].
338    platform: TPlat,
339}
340
341/// Severity of a ban. See [`NetworkServiceChain::ban_and_disconnect`].
342#[derive(Debug, Copy, Clone, PartialEq, Eq)]
343pub enum BanSeverity {
344    Low,
345    High,
346}
347
348/// Reason for banning a peer. Printed in the logs and used as the `reason` label
349/// of the `networkPeerBansTotal` metric.
350// Strum derives are explained on [`crate::metrics::MetricLabel`]; `Display`
351// additionally prints the same string in log messages.
352#[derive(
353    Debug, Copy, Clone, PartialEq, Eq, strum::Display, strum::EnumIter, strum::IntoStaticStr,
354)]
355#[strum(serialize_all = "kebab-case")]
356pub enum BanReason {
357    BadBlockAnnounce,
358    BadChildTrieRoot,
359    BadGrandpaCommit,
360    BadJustification,
361    BadMerkleProof,
362    BadWarpSyncFragment,
363    InvalidCallProof,
364    BlocksRequestFailed,
365    CallProofRequestFailed,
366    ChildStorageRequestFailed,
367    StorageRequestFailed,
368    WarpSyncRequestFailed,
369}
370
371impl<TPlat: PlatformRef> NetworkServiceChain<TPlat> {
372    /// Subscribes to the networking events that happen on the given chain.
373    ///
374    /// Calling this function returns a `Receiver` that receives events about the chain.
375    /// The new channel will immediately receive events about all the existing connections, so
376    /// that it is able to maintain a coherent view of the network.
377    ///
378    /// Note that this function is `async`, but it should return very quickly.
379    ///
380    /// The `Receiver` **must** be polled continuously. When the channel is full, the networking
381    /// connections will be back-pressured until the channel isn't full anymore.
382    ///
383    /// The `Receiver` never yields `None` unless the [`NetworkService`] crashes or is destroyed.
384    /// If `None` is yielded and the [`NetworkService`] is still alive, you should call
385    /// [`NetworkServiceChain::subscribe`] again to obtain a new `Receiver`.
386    ///
387    // TODO: consider not killing the background until the channel is destroyed, as that would be a more sensical behaviour
388    pub async fn subscribe(&self) -> async_channel::Receiver<Event> {
389        let (tx, rx) = async_channel::bounded(128);
390
391        self.messages_tx
392            .send(ToBackgroundChain::Subscribe { sender: tx })
393            .await
394            .unwrap();
395
396        rx
397    }
398
399    /// Subscribes to the Bitswap events that happen on the network. Bitswap events subscription is
400    /// separate from other network service events, because Bitswap events are big and are not
401    /// needed by the most of subscribers.
402    ///
403    /// Note that this function is `async`, but it should return very quickly.
404    ///
405    /// The `Receiver` **must** be polled continuously. When the channel is full, the networking
406    /// connections will be back-pressured until the channel isn't full anymore.
407    ///
408    /// The `Receiver` never yields `None` unless the [`NetworkService`] crashes or is destroyed.
409    /// If `None` is yielded and the [`NetworkService`] is still alive, you should call
410    /// [`NetworkServiceChain::subscribe_bitswap`] again to obtain a new `Receiver`.
411    ///
412    // TODO: the last section of the doc seem to contradict itself.
413    pub async fn subscribe_bitswap(&self) -> async_channel::Receiver<BitswapEvent> {
414        let (tx, rx) = async_channel::bounded(128);
415
416        self.messages_tx
417            .send(ToBackgroundChain::SubscribeBitswap { sender: tx })
418            .await
419            .unwrap();
420
421        rx
422    }
423
424    /// Subscribes to the statement notifications that happen on the network.
425    ///
426    /// Note that this function is `async`, but it should return very quickly.
427    ///
428    /// The `Receiver` **must** be polled continuously. When the channel is full, the networking
429    /// connections will be back-pressured until the channel isn't full anymore.
430    ///
431    /// The `Receiver` never yields `None` unless the [`NetworkService`] crashes or is destroyed.
432    /// If `None` is yielded and the [`NetworkService`] is still alive, you should call
433    /// [`NetworkServiceChain::subscribe_statements`] again to obtain a new `Receiver`.
434    pub async fn subscribe_statements(&self) -> async_channel::Receiver<StatementEvent> {
435        let (tx, rx) = async_channel::bounded(128);
436
437        self.messages_tx
438            .send(ToBackgroundChain::SubscribeStatements { sender: tx })
439            .await
440            .unwrap();
441
442        rx
443    }
444
445    /// Starts asynchronously disconnecting the given peer. A [`Event::Disconnected`] will later be
446    /// generated. Prevents a new gossip link with the same peer from being reopened for a
447    /// little while.
448    ///
449    /// `reason` is printed in the logs and counted in the metrics.
450    ///
451    /// Due to race conditions, it is possible to reconnect to the peer soon after, in case the
452    /// reconnection was already happening as the call to this function is still being processed.
453    /// If that happens another [`Event::Disconnected`] will be delivered afterwards. In other
454    /// words, this function guarantees that we will be disconnected in the future rather than
455    /// guarantees that we will disconnect.
456    pub async fn ban_and_disconnect(
457        &self,
458        peer_id: PeerId,
459        severity: BanSeverity,
460        reason: BanReason,
461    ) {
462        self.metrics.peer_bans.inc(reason);
463
464        let _ = self
465            .messages_tx
466            .send(ToBackgroundChain::DisconnectAndBan {
467                peer_id,
468                severity,
469                reason,
470            })
471            .await;
472    }
473
474    /// Sends a blocks request to the given peer.
475    // TODO: more docs
476    pub async fn blocks_request(
477        self: Arc<Self>,
478        target: PeerId,
479        config: codec::BlocksRequestConfig,
480        timeout: Duration,
481    ) -> Result<Vec<codec::BlockData>, BlocksRequestError> {
482        let when_started = self.platform.now();
483        let (tx, rx) = oneshot::channel();
484
485        self.messages_tx
486            .send(ToBackgroundChain::StartBlocksRequest {
487                target: target.clone(),
488                config,
489                timeout,
490                result: tx,
491            })
492            .await
493            .unwrap();
494
495        let result = rx.await.unwrap();
496        self.metrics
497            .blocks_requests
498            .observe(result.is_ok(), self.platform.now() - when_started);
499        result
500    }
501
502    /// Sends a grandpa warp sync request to the given peer.
503    // TODO: more docs
504    pub async fn grandpa_warp_sync_request(
505        self: Arc<Self>,
506        target: PeerId,
507        begin_hash: [u8; 32],
508        timeout: Duration,
509    ) -> Result<service::EncodedGrandpaWarpSyncResponse, WarpSyncRequestError> {
510        let when_started = self.platform.now();
511        let (tx, rx) = oneshot::channel();
512
513        self.messages_tx
514            .send(ToBackgroundChain::StartWarpSyncRequest {
515                target: target.clone(),
516                begin_hash,
517                timeout,
518                result: tx,
519            })
520            .await
521            .unwrap();
522
523        let result = rx.await.unwrap();
524        self.metrics
525            .warp_sync_requests
526            .observe(result.is_ok(), self.platform.now() - when_started);
527        result
528    }
529
530    pub async fn set_local_best_block(&self, best_hash: [u8; 32], best_number: u64) {
531        self.messages_tx
532            .send(ToBackgroundChain::SetLocalBestBlock {
533                best_hash,
534                best_number,
535            })
536            .await
537            .unwrap();
538    }
539
540    pub async fn set_local_grandpa_state(&self, grandpa_state: service::GrandpaState) {
541        self.messages_tx
542            .send(ToBackgroundChain::SetLocalGrandpaState { grandpa_state })
543            .await
544            .unwrap();
545    }
546
547    /// Sends a storage proof request to the given peer.
548    // TODO: more docs
549    pub async fn storage_proof_request(
550        self: Arc<Self>,
551        target: PeerId, // TODO: takes by value because of futures longevity issue
552        config: codec::StorageProofRequestConfig<impl Iterator<Item = impl AsRef<[u8]> + Clone>>,
553        timeout: Duration,
554    ) -> Result<service::EncodedMerkleProof, StorageProofRequestError> {
555        let when_started = self.platform.now();
556        let (tx, rx) = oneshot::channel();
557
558        self.messages_tx
559            .send(ToBackgroundChain::StartStorageProofRequest {
560                target: target.clone(),
561                config: codec::StorageProofRequestConfig {
562                    block_hash: config.block_hash,
563                    keys: config
564                        .keys
565                        .map(|key| key.as_ref().to_vec()) // TODO: to_vec() overhead
566                        .collect::<Vec<_>>()
567                        .into_iter(),
568                },
569                timeout,
570                result: tx,
571            })
572            .await
573            .unwrap();
574
575        let result = rx.await.unwrap();
576        self.metrics
577            .storage_proof_requests
578            .observe(result.is_ok(), self.platform.now() - when_started);
579        result
580    }
581
582    /// Sends a call proof request to the given peer.
583    ///
584    /// See also [`NetworkServiceChain::call_proof_request`].
585    // TODO: more docs
586    pub async fn call_proof_request(
587        self: Arc<Self>,
588        target: PeerId, // TODO: takes by value because of futures longevity issue
589        config: codec::CallProofRequestConfig<'_, impl Iterator<Item = impl AsRef<[u8]>>>,
590        timeout: Duration,
591    ) -> Result<EncodedMerkleProof, CallProofRequestError> {
592        let when_started = self.platform.now();
593        let (tx, rx) = oneshot::channel();
594
595        self.messages_tx
596            .send(ToBackgroundChain::StartCallProofRequest {
597                target: target.clone(),
598                config: codec::CallProofRequestConfig {
599                    block_hash: config.block_hash,
600                    method: config.method.into_owned().into(),
601                    parameter_vectored: config
602                        .parameter_vectored
603                        .map(|v| v.as_ref().to_vec()) // TODO: to_vec() overhead
604                        .collect::<Vec<_>>()
605                        .into_iter(),
606                },
607                timeout,
608                result: tx,
609            })
610            .await
611            .unwrap();
612
613        let result = rx.await.unwrap();
614        self.metrics
615            .call_proof_requests
616            .observe(result.is_ok(), self.platform.now() - when_started);
617        result
618    }
619
620    /// Sends a child storage proof request to the given peer.
621    pub async fn child_storage_proof_request(
622        self: Arc<Self>,
623        target: PeerId,
624        config: codec::ChildStorageProofRequestConfig<
625            impl AsRef<[u8]> + Clone,
626            impl Iterator<Item = impl AsRef<[u8]> + Clone>,
627        >,
628        timeout: Duration,
629    ) -> Result<service::EncodedMerkleProof, ChildStorageProofRequestError> {
630        let when_started = self.platform.now();
631        let (tx, rx) = oneshot::channel();
632
633        self.messages_tx
634            .send(ToBackgroundChain::StartChildStorageProofRequest {
635                target: target.clone(),
636                config: ChildStorageProofRequestConfigOwned {
637                    block_hash: config.block_hash,
638                    child_trie: config.child_trie.as_ref().to_vec(),
639                    keys: config
640                        .keys
641                        .map(|key| key.as_ref().to_vec())
642                        .collect::<Vec<_>>(),
643                },
644                timeout,
645                result: tx,
646            })
647            .await
648            .unwrap();
649
650        let result = rx.await.unwrap();
651        self.metrics
652            .child_storage_proof_requests
653            .observe(result.is_ok(), self.platform.now() - when_started);
654        result
655    }
656
657    /// Announces transaction to the peers we are connected to.
658    ///
659    /// Returns a list of peers that we have sent the transaction to. Can return an empty `Vec`
660    /// if we didn't send the transaction to any peer.
661    ///
662    /// Note that the remote doesn't confirm that it has received the transaction. Because
663    /// networking is inherently unreliable, successfully sending a transaction to a peer doesn't
664    /// necessarily mean that the remote has received it. In practice, however, the likelihood of
665    /// a transaction not being received are extremely low. This can be considered as known flaw.
666    pub async fn announce_transaction(self: Arc<Self>, transaction: &[u8]) -> Vec<PeerId> {
667        let (tx, rx) = oneshot::channel();
668
669        self.messages_tx
670            .send(ToBackgroundChain::AnnounceTransaction {
671                transaction: transaction.to_vec(), // TODO: ovheread
672                result: tx,
673            })
674            .await
675            .unwrap();
676
677        rx.await.unwrap()
678    }
679
680    /// See [`service::ChainNetwork::gossip_send_block_announce`].
681    pub async fn send_block_announce(
682        self: Arc<Self>,
683        target: &PeerId,
684        scale_encoded_header: &[u8],
685        is_best: bool,
686    ) -> Result<(), QueueNotificationError> {
687        let (tx, rx) = oneshot::channel();
688
689        self.messages_tx
690            .send(ToBackgroundChain::SendBlockAnnounce {
691                target: target.clone(),                              // TODO: overhead
692                scale_encoded_header: scale_encoded_header.to_vec(), // TODO: overhead
693                is_best,
694                result: tx,
695            })
696            .await
697            .unwrap();
698
699        rx.await.unwrap()
700    }
701
702    /// Send Bitswap message to the given peer.
703    pub async fn send_bitswap_message(
704        &self,
705        target: PeerId,
706        message: Vec<u8>,
707    ) -> Result<(), SendBitswapMessageError> {
708        let (tx, rx) = oneshot::channel();
709
710        self.messages_tx
711            .send(ToBackgroundChain::SendBitswapMessage {
712                target,
713                message,
714                result: tx,
715            })
716            .await
717            .unwrap();
718
719        rx.await.unwrap()
720    }
721
722    /// Broadcast Bitswap message to all [`service::ChainNetwork::established_bitswap_desired`]
723    /// peers.
724    ///
725    /// Returns the peers message was broadcast to or an error if the message cannot be sent
726    /// to at least one peer.
727    // TODO: better use a dedicated error type instead of reusing a lower-level
728    // `SendBitswapMessageErorr`.
729    pub async fn broadcast_bitswap_message(
730        &self,
731        message: Vec<u8>,
732    ) -> Result<Vec<PeerId>, SendBitswapMessageError> {
733        let (tx, rx) = oneshot::channel();
734
735        self.messages_tx
736            .send(ToBackgroundChain::BroadcastBitswapMessage {
737                message,
738                result: tx,
739            })
740            .await
741            .unwrap();
742
743        rx.await.unwrap()
744    }
745
746    /// Broadcast a statement notification to all gossip-connected peers.
747    pub async fn broadcast_statement(
748        self: Arc<Self>,
749        statement: Vec<u8>,
750    ) -> BroadcastStatementResult {
751        let (tx, rx) = oneshot::channel();
752
753        self.messages_tx
754            .send(ToBackgroundChain::BroadcastStatement {
755                statement,
756                result: tx,
757            })
758            .await
759            .unwrap();
760
761        rx.await.unwrap()
762    }
763
764    pub async fn update_topic_affinity(&self, filter: AffinityFilter) {
765        self.messages_tx
766            .send(ToBackgroundChain::UpdateTopicAffinity { filter })
767            .await
768            .unwrap();
769    }
770
771    /// Marks the given peers as belonging to the given chain, and adds some addresses to these
772    /// peers to the address book.
773    ///
774    /// The `important_nodes` parameter indicates whether these nodes are considered note-worthy
775    /// and should have additional logging.
776    pub async fn discover(
777        &self,
778        list: impl IntoIterator<Item = (PeerId, impl IntoIterator<Item = Multiaddr>)>,
779        important_nodes: bool,
780    ) {
781        self.messages_tx
782            .send(ToBackgroundChain::Discover {
783                // TODO: overhead
784                list: list
785                    .into_iter()
786                    .map(|(peer_id, addrs)| {
787                        (peer_id, addrs.into_iter().collect::<Vec<_>>().into_iter())
788                    })
789                    .collect::<Vec<_>>()
790                    .into_iter(),
791                important_nodes,
792            })
793            .await
794            .unwrap();
795    }
796
797    /// Returns a list of nodes (their [`PeerId`] and multiaddresses) that we know are part of
798    /// the network.
799    ///
800    /// Nodes that are discovered might disappear over time. In other words, there is no guarantee
801    /// that a node that has been added through [`NetworkServiceChain::discover`] will later be
802    /// returned by [`NetworkServiceChain::discovered_nodes`].
803    pub async fn discovered_nodes(
804        &self,
805    ) -> impl Iterator<Item = (PeerId, impl Iterator<Item = Multiaddr>)> {
806        let (tx, rx) = oneshot::channel();
807
808        self.messages_tx
809            .send(ToBackgroundChain::DiscoveredNodes { result: tx })
810            .await
811            .unwrap();
812
813        rx.await
814            .unwrap()
815            .into_iter()
816            .map(|(peer_id, addrs)| (peer_id, addrs.into_iter()))
817    }
818
819    /// Returns an iterator to the list of [`PeerId`]s that we have an established connection
820    /// with.
821    pub async fn peers_list(&self) -> impl Iterator<Item = PeerId> {
822        let (tx, rx) = oneshot::channel();
823        self.messages_tx
824            .send(ToBackgroundChain::PeersList { result: tx })
825            .await
826            .unwrap();
827        rx.await.unwrap().into_iter()
828    }
829}
830
831#[derive(Debug, Clone)]
832pub struct BroadcastStatementResult {
833    pub sent: usize,
834    pub total: usize,
835}
836
837/// Event that can happen on the network service.
838#[derive(Debug, Clone)]
839pub enum Event {
840    Connected {
841        peer_id: PeerId,
842        role: Role,
843        best_block_number: u64,
844        best_block_hash: [u8; 32],
845    },
846    Disconnected {
847        peer_id: PeerId,
848    },
849    BlockAnnounce {
850        peer_id: PeerId,
851        announce: service::EncodedBlockAnnounce,
852    },
853    GrandpaNeighborPacket {
854        peer_id: PeerId,
855        finalized_block_height: u64,
856    },
857    /// Received a GrandPa commit message from the network.
858    GrandpaCommitMessage {
859        peer_id: PeerId,
860        message: service::EncodedGrandpaCommitMessage,
861    },
862}
863
864/// Bitswap event that can be generated by the network service. Because Bitswap messages are big
865/// (up to 2 MiB) and can be delivered at high rate, we use a dedicated subscriber to not copy them
866/// to all network service subscribers.
867#[derive(Debug, Clone)]
868pub enum BitswapEvent {
869    BitswapMessage {
870        peer_id: PeerId,
871        message: service::EncodedBitswapMessage,
872    },
873}
874
875/// Statement event that can be generated by the network service.
876#[derive(Debug, Clone)]
877pub enum StatementEvent {
878    /// Received a statement notification from the network.
879    StatementsNotification {
880        peer_id: PeerId,
881        statements: Vec<([u8; 32], codec::Statement)>,
882    },
883}
884
885/// Error returned by [`NetworkServiceChain::blocks_request`].
886#[derive(Debug, derive_more::Display, derive_more::Error)]
887pub enum BlocksRequestError {
888    /// No established connection with the target.
889    NoConnection,
890    /// Error during the request.
891    #[display("{_0}")]
892    Request(service::BlocksRequestError),
893}
894
895/// Error returned by [`NetworkServiceChain::grandpa_warp_sync_request`].
896#[derive(Debug, derive_more::Display, derive_more::Error)]
897pub enum WarpSyncRequestError {
898    /// No established connection with the target.
899    NoConnection,
900    /// Error during the request.
901    #[display("{_0}")]
902    Request(service::GrandpaWarpSyncRequestError),
903}
904
905/// Error returned by [`NetworkServiceChain::storage_proof_request`].
906#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
907pub enum StorageProofRequestError {
908    /// No established connection with the target.
909    NoConnection,
910    /// Storage proof request is too large and can't be sent.
911    RequestTooLarge,
912    /// Error during the request.
913    #[display("{_0}")]
914    Request(service::StorageProofRequestError),
915}
916
917/// Error returned by [`NetworkServiceChain::call_proof_request`].
918#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
919pub enum CallProofRequestError {
920    /// No established connection with the target.
921    NoConnection,
922    /// Call proof request is too large and can't be sent.
923    RequestTooLarge,
924    /// Error during the request.
925    #[display("{_0}")]
926    Request(service::CallProofRequestError),
927}
928
929impl CallProofRequestError {
930    /// Returns `true` if this is caused by networking issues, as opposed to a consensus-related
931    /// issue.
932    pub fn is_network_problem(&self) -> bool {
933        match self {
934            CallProofRequestError::Request(err) => err.is_network_problem(),
935            CallProofRequestError::RequestTooLarge => false,
936            CallProofRequestError::NoConnection => true,
937        }
938    }
939}
940
941/// Error returned by [`NetworkServiceChain::child_storage_proof_request`].
942#[derive(Debug, derive_more::Display, derive_more::Error, Clone)]
943pub enum ChildStorageProofRequestError {
944    /// No established connection with the target.
945    NoConnection,
946    /// Child storage proof request is too large and can't be sent.
947    RequestTooLarge,
948    /// Error during the request.
949    #[display("{_0}")]
950    Request(service::StorageProofRequestError),
951}
952
953impl ChildStorageProofRequestError {
954    /// Returns `true` if this is caused by networking issues, as opposed to a consensus-related
955    /// issue.
956    pub fn is_network_problem(&self) -> bool {
957        match self {
958            ChildStorageProofRequestError::Request(err) => err.is_network_problem(),
959            ChildStorageProofRequestError::RequestTooLarge => false,
960            ChildStorageProofRequestError::NoConnection => true,
961        }
962    }
963}
964
965/// Why an address obtained through discovery was discarded. The `reason` label of
966/// the `networkDiscoveryAddressesDroppedTotal` metric.
967// Strum derives are explained on [`crate::metrics::MetricLabel`]; `Display`
968// additionally prints the same string in log messages.
969#[derive(
970    Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumIter, strum::IntoStaticStr,
971)]
972#[strum(serialize_all = "kebab-case")]
973pub(crate) enum DiscoveredAddressDropReason {
974    /// Address contains a `/p2p/` component that doesn't match the peer it was announced for.
975    PeerIdMismatch,
976    /// Platform doesn't support connecting to this type of address.
977    NotSupported,
978    /// Address failed to parse.
979    Invalid,
980}
981
982/// Owned version of [`codec::ChildStorageProofRequestConfig`] for sending across channel.
983struct ChildStorageProofRequestConfigOwned {
984    block_hash: [u8; 32],
985    child_trie: Vec<u8>,
986    keys: Vec<Vec<u8>>,
987}
988
989enum ToBackground<TPlat: PlatformRef> {
990    AddChain {
991        messages_rx: async_channel::Receiver<ToBackgroundChain>,
992        config: service::ChainConfig<Chain<TPlat>>,
993    },
994}
995
996enum ToBackgroundChain {
997    RemoveChain,
998    Subscribe {
999        sender: async_channel::Sender<Event>,
1000    },
1001    SubscribeBitswap {
1002        sender: async_channel::Sender<BitswapEvent>,
1003    },
1004    SubscribeStatements {
1005        sender: async_channel::Sender<StatementEvent>,
1006    },
1007    DisconnectAndBan {
1008        peer_id: PeerId,
1009        severity: BanSeverity,
1010        reason: BanReason,
1011    },
1012    // TODO: serialize the request before sending over channel
1013    StartBlocksRequest {
1014        target: PeerId, // TODO: takes by value because of future longevity issue
1015        config: codec::BlocksRequestConfig,
1016        timeout: Duration,
1017        result: oneshot::Sender<Result<Vec<codec::BlockData>, BlocksRequestError>>,
1018    },
1019    // TODO: serialize the request before sending over channel
1020    StartWarpSyncRequest {
1021        target: PeerId,
1022        begin_hash: [u8; 32],
1023        timeout: Duration,
1024        result:
1025            oneshot::Sender<Result<service::EncodedGrandpaWarpSyncResponse, WarpSyncRequestError>>,
1026    },
1027    // TODO: serialize the request before sending over channel
1028    StartStorageProofRequest {
1029        target: PeerId,
1030        config: codec::StorageProofRequestConfig<vec::IntoIter<Vec<u8>>>,
1031        timeout: Duration,
1032        result: oneshot::Sender<Result<service::EncodedMerkleProof, StorageProofRequestError>>,
1033    },
1034    // TODO: serialize the request before sending over channel
1035    StartCallProofRequest {
1036        target: PeerId, // TODO: takes by value because of futures longevity issue
1037        config: codec::CallProofRequestConfig<'static, vec::IntoIter<Vec<u8>>>,
1038        timeout: Duration,
1039        result: oneshot::Sender<Result<service::EncodedMerkleProof, CallProofRequestError>>,
1040    },
1041    // TODO: serialize the request before sending over channel
1042    StartChildStorageProofRequest {
1043        target: PeerId,
1044        config: ChildStorageProofRequestConfigOwned,
1045        timeout: Duration,
1046        result: oneshot::Sender<Result<service::EncodedMerkleProof, ChildStorageProofRequestError>>,
1047    },
1048    SetLocalBestBlock {
1049        best_hash: [u8; 32],
1050        best_number: u64,
1051    },
1052    SetLocalGrandpaState {
1053        grandpa_state: service::GrandpaState,
1054    },
1055    AnnounceTransaction {
1056        transaction: Vec<u8>,
1057        result: oneshot::Sender<Vec<PeerId>>,
1058    },
1059    SendBlockAnnounce {
1060        target: PeerId,
1061        scale_encoded_header: Vec<u8>,
1062        is_best: bool,
1063        result: oneshot::Sender<Result<(), QueueNotificationError>>,
1064    },
1065    SendBitswapMessage {
1066        target: PeerId,
1067        message: Vec<u8>,
1068        result: oneshot::Sender<Result<(), SendBitswapMessageError>>,
1069    },
1070    BroadcastBitswapMessage {
1071        message: Vec<u8>,
1072        result: oneshot::Sender<Result<Vec<PeerId>, SendBitswapMessageError>>,
1073    },
1074    BroadcastStatement {
1075        statement: Vec<u8>,
1076        result: oneshot::Sender<BroadcastStatementResult>,
1077    },
1078    UpdateTopicAffinity {
1079        filter: AffinityFilter,
1080    },
1081    Discover {
1082        list: vec::IntoIter<(PeerId, vec::IntoIter<Multiaddr>)>,
1083        important_nodes: bool,
1084    },
1085    DiscoveredNodes {
1086        result: oneshot::Sender<Vec<(PeerId, Vec<Multiaddr>)>>,
1087    },
1088    PeersList {
1089        result: oneshot::Sender<Vec<PeerId>>,
1090    },
1091}
1092
1093struct BackgroundTask<TPlat: PlatformRef> {
1094    /// See [`Config::platform`].
1095    platform: TPlat,
1096
1097    /// Process-wide network metrics. Also accessible through [`NetworkService::metrics`].
1098    metrics: Arc<metrics::NetworkMetrics>,
1099
1100    /// Random number generator.
1101    randomness: rand_chacha::ChaCha20Rng,
1102
1103    /// Value provided through [`Config::identify_agent_version`].
1104    identify_agent_version: String,
1105
1106    /// Channel to send messages to the background task.
1107    tasks_messages_tx:
1108        async_channel::Sender<(service::ConnectionId, service::ConnectionToCoordinator)>,
1109
1110    /// Channel to receive messages destined to the background task.
1111    tasks_messages_rx: Pin<
1112        Box<async_channel::Receiver<(service::ConnectionId, service::ConnectionToCoordinator)>>,
1113    >,
1114
1115    /// Data structure holding the entire state of the networking.
1116    network: service::ChainNetwork<
1117        Chain<TPlat>,
1118        async_channel::Sender<service::CoordinatorToConnection>,
1119        TPlat::Instant,
1120    >,
1121
1122    /// All known peers and their addresses.
1123    peering_strategy: basic_peering_strategy::BasicPeeringStrategy<ChainId, TPlat::Instant>,
1124
1125    /// Bitswap slot assignment strategy.
1126    bitswap_peering_strategy: bitswap_peering_strategy::BitswapPeeringStrategy<TPlat::Instant>,
1127
1128    /// See [`Config::connections_open_pool_size`].
1129    connections_open_pool_size: u32,
1130
1131    /// See [`Config::connections_open_pool_restore_delay`].
1132    connections_open_pool_restore_delay: Duration,
1133
1134    /// Every time a connection is opened, the value in this field is increased by one. After
1135    /// [`BackgroundTask::next_recent_connection_restore`] has yielded, the value is reduced by
1136    /// one.
1137    num_recent_connection_opening: u32,
1138
1139    /// Delay after which [`BackgroundTask::num_recent_connection_opening`] is increased by one.
1140    next_recent_connection_restore: Option<Pin<Box<TPlat::Delay>>>,
1141
1142    /// List of all open gossip links.
1143    // TODO: using this data structure unfortunately means that PeerIds are cloned a lot, maybe some user data in ChainNetwork is better? not sure
1144    open_gossip_links: BTreeMap<(ChainId, PeerId), OpenGossipLinkState>,
1145
1146    /// Chains for which a gossip link has been opened at least once. Used to prefer bootnodes for
1147    /// out slots only until the chain first connects.
1148    chains_ever_gossip_connected: HashSet<ChainId, fnv::FnvBuildHasher>,
1149
1150    /// Connected peers using statement protocol V2, per chain.
1151    v2_statement_peers: HashMap<ChainId, HashSet<PeerId, fnv::FnvBuildHasher>, fnv::FnvBuildHasher>,
1152
1153    /// Current topic affinity filter per chain, sent to V2 peers on connect.
1154    current_affinity_filter: HashMap<ChainId, AffinityFilter, fnv::FnvBuildHasher>,
1155
1156    /// Important nodes per chain (in practice the bootnodes; see [`NetworkServiceChain::discover`]).
1157    /// They get extra logging, and slot preference until the chain first connects.
1158    // TODO: should also detect whenever we fail to open a block announces substream with any of these peers
1159    important_nodes: HashMap<ChainId, HashSet<PeerId, fnv::FnvBuildHasher>, fnv::FnvBuildHasher>,
1160
1161    /// Events about to be sent on the senders of [`BackgroundTask::event_senders`].
1162    ///
1163    /// Network events are only pulled when this queue is empty, keeping it small. A queue is
1164    /// nonetheless necessary, as processing a [`ToBackgroundChain::DisconnectAndBan`] message can
1165    /// generate an event while another event is already waiting to be dispatched.
1166    events_pending_send: VecDeque<(ChainId, Event)>,
1167
1168    /// Bitswap event about to be sent on the senders of [`BackgroundTask::bitswap_event_senders`].
1169    bitswap_event_pending_send: Option<BitswapEvent>,
1170
1171    /// Running count of peers with an open Bitswap substream. Maintained from
1172    /// `service::Event::BitswapConnected` / `BitswapDisconnected`. Used for diagnostic logging
1173    /// only; the authoritative per-peer state lives in
1174    /// [`BackgroundTask::bitswap_peering_strategy`].
1175    bitswap_connected_peers: usize,
1176
1177    /// Sending events through the public API.
1178    ///
1179    /// Contains either senders, or a `Future` that is currently sending an event and will yield
1180    /// the senders back once it is finished.
1181    // TODO: sort by ChainId instead of using a Vec?
1182    event_senders: either::Either<
1183        Vec<(ChainId, async_channel::Sender<Event>)>,
1184        Pin<Box<dyn Future<Output = Vec<(ChainId, async_channel::Sender<Event>)>> + Send>>,
1185    >,
1186
1187    /// Whenever [`NetworkServiceChain::subscribe`] is called, the new sender is added to this list.
1188    /// Once [`BackgroundTask::event_senders`] is ready, we properly initialize these senders.
1189    pending_new_subscriptions: Vec<(ChainId, async_channel::Sender<Event>)>,
1190
1191    /// Sending Bitswap events through the public API. We use separate channels for Bitswap events,
1192    /// because Bitswap messages are big and only few of event subscribers are interested in them.
1193    ///
1194    /// Contains either senders, or a `Future` that is currently sending an event and will yield
1195    /// the senders back once it is finished.
1196    ///
1197    /// Note that compared to `event_senders`, `bitswap_event_senders` are not associated with
1198    /// chains, because Bitswap messages coming from the network do not have the information about
1199    /// what chain they are coming from.
1200    bitswap_event_senders: either::Either<
1201        Vec<async_channel::Sender<BitswapEvent>>,
1202        Pin<Box<dyn Future<Output = Vec<async_channel::Sender<BitswapEvent>>> + Send>>,
1203    >,
1204
1205    /// Whenever [`NetworkServiceChain::subscribe_bitswap`] is called, the new sender is added to
1206    /// this list. Once [`BackgroundTask::bitswap_event_senders`] is ready, we properly initialize
1207    /// these senders.
1208    pending_new_bitswap_subscriptions: Vec<async_channel::Sender<BitswapEvent>>,
1209
1210    /// Statement event about to be sent on the senders of
1211    /// [`BackgroundTask::statement_event_senders`].
1212    statement_event_pending_send: Option<(ChainId, StatementEvent)>,
1213
1214    /// Sending statement events through the public API.
1215    ///
1216    /// Contains either senders, or a `Future` that is currently sending an event and will yield
1217    /// the senders back once it is finished.
1218    statement_event_senders: either::Either<
1219        Vec<(ChainId, async_channel::Sender<StatementEvent>)>,
1220        Pin<Box<dyn Future<Output = Vec<(ChainId, async_channel::Sender<StatementEvent>)>> + Send>>,
1221    >,
1222
1223    /// Whenever [`NetworkServiceChain::subscribe_statements`] is called, the new sender is added to
1224    /// this list. Once [`BackgroundTask::statement_event_senders`] is ready, we properly initialize
1225    /// these senders.
1226    pending_new_statement_subscriptions: Vec<(ChainId, async_channel::Sender<StatementEvent>)>,
1227
1228    main_messages_rx: Pin<Box<async_channel::Receiver<ToBackground<TPlat>>>>,
1229
1230    messages_rx:
1231        stream::SelectAll<Pin<Box<dyn stream::Stream<Item = (ChainId, ToBackgroundChain)> + Send>>>,
1232
1233    blocks_requests: HashMap<
1234        service::SubstreamId,
1235        oneshot::Sender<Result<Vec<codec::BlockData>, BlocksRequestError>>,
1236        fnv::FnvBuildHasher,
1237    >,
1238
1239    grandpa_warp_sync_requests: HashMap<
1240        service::SubstreamId,
1241        oneshot::Sender<Result<service::EncodedGrandpaWarpSyncResponse, WarpSyncRequestError>>,
1242        fnv::FnvBuildHasher,
1243    >,
1244
1245    storage_proof_requests: HashMap<
1246        service::SubstreamId,
1247        oneshot::Sender<Result<service::EncodedMerkleProof, StorageProofRequestError>>,
1248        fnv::FnvBuildHasher,
1249    >,
1250
1251    call_proof_requests: HashMap<
1252        service::SubstreamId,
1253        oneshot::Sender<Result<service::EncodedMerkleProof, CallProofRequestError>>,
1254        fnv::FnvBuildHasher,
1255    >,
1256
1257    child_storage_proof_requests: HashMap<
1258        service::SubstreamId,
1259        oneshot::Sender<Result<service::EncodedMerkleProof, ChildStorageProofRequestError>>,
1260        fnv::FnvBuildHasher,
1261    >,
1262
1263    /// All chains, indexed by the value of [`Chain::next_discovery_when`].
1264    chains_by_next_discovery: BTreeMap<(TPlat::Instant, ChainId), Pin<Box<TPlat::Delay>>>,
1265
1266    /// Gossip links missing a transactions, Grandpa or statement substream, by when to call
1267    /// [`service::ChainNetwork::gossip_reopen_secondaries`]. One entry per link at most. An
1268    /// entry that outlives its link is harmless: the call is refused.
1269    secondary_reopens: BTreeMap<(TPlat::Instant, ChainId, PeerId), Pin<Box<TPlat::Delay>>>,
1270}
1271
1272struct Chain<TPlat: PlatformRef> {
1273    log_name: String,
1274
1275    // TODO: this field is a hack due to the fact that `add_chain` can't be `async`; should eventually be fixed after a lib.rs refactor
1276    num_references: NonZero<usize>,
1277
1278    /// See [`ConfigChain::block_number_bytes`].
1279    // TODO: redundant with ChainNetwork? since we might not need to know this in the future i'm reluctant to add a getter to ChainNetwork
1280    block_number_bytes: usize,
1281
1282    /// See [`ConfigChain::num_out_slots`].
1283    num_out_slots: usize,
1284
1285    /// See [`ConfigChain::metrics`].
1286    metrics: Arc<metrics::ChainMetrics>,
1287
1288    /// When the next discovery should be started for this chain.
1289    next_discovery_when: TPlat::Instant,
1290
1291    /// After [`Chain::next_discovery_when`] is reached, the following discovery happens after
1292    /// the given duration.
1293    next_discovery_period: Duration,
1294}
1295
1296#[derive(Clone)]
1297struct OpenGossipLinkState {
1298    role: Role,
1299    best_block_number: u64,
1300    best_block_hash: [u8; 32],
1301    /// `None` if unknown.
1302    finalized_block_height: Option<u64>,
1303    /// Whether a [`service::Event::GossipSecondaryClosed`] was already answered at once.
1304    secondary_reopened_at_once: bool,
1305}
1306
1307async fn background_task<TPlat: PlatformRef>(mut task: BackgroundTask<TPlat>) {
1308    loop {
1309        // Yield at every loop in order to provide better tasks granularity.
1310        futures_lite::future::yield_now().await;
1311
1312        enum WakeUpReason<TPlat: PlatformRef> {
1313            ForegroundClosed,
1314            Message(ToBackground<TPlat>),
1315            MessageForChain(ChainId, ToBackgroundChain),
1316            NetworkEvent(service::Event<async_channel::Sender<service::CoordinatorToConnection>>),
1317            CanAssignSlot(PeerId, ChainId),
1318            CanAssignBitswapSlot(PeerId),
1319            NextRecentConnectionRestore,
1320            CanStartConnect(PeerId),
1321            CanOpenGossip(PeerId, ChainId, service::GossipKind),
1322            CanOpenBitswap(PeerId),
1323            MessageFromConnection {
1324                connection_id: service::ConnectionId,
1325                message: service::ConnectionToCoordinator,
1326            },
1327            MessageToConnection {
1328                connection_id: service::ConnectionId,
1329                message: service::CoordinatorToConnection,
1330            },
1331            EventSendersReady,
1332            BitswapEventSendersReady,
1333            StatementEventSendersReady,
1334            StartDiscovery(ChainId),
1335            ReopenSecondaries {
1336                chain_id: ChainId,
1337                peer_id: PeerId,
1338            },
1339        }
1340
1341        let wake_up_reason = {
1342            let message_received = async {
1343                task.main_messages_rx
1344                    .next()
1345                    .await
1346                    .map_or(WakeUpReason::ForegroundClosed, WakeUpReason::Message)
1347            };
1348            let message_for_chain_received = async {
1349                // Note that when the last entry of `messages_rx` yields `None`, `messages_rx`
1350                // itself will yield `None`. For this reason, we can't use
1351                // `task.messages_rx.is_empty()` to determine whether `messages_rx` will
1352                // yield `None`.
1353                let Some((chain_id, message)) = task.messages_rx.next().await else {
1354                    future::pending().await
1355                };
1356                WakeUpReason::MessageForChain(chain_id, message)
1357            };
1358            let message_from_task_received = async {
1359                let (connection_id, message) = task.tasks_messages_rx.next().await.unwrap();
1360                WakeUpReason::MessageFromConnection {
1361                    connection_id,
1362                    message,
1363                }
1364            };
1365            let service_event = async {
1366                if let Some(event) = (task.events_pending_send.is_empty()
1367                    && task.bitswap_event_pending_send.is_none()
1368                    && task.statement_event_pending_send.is_none()
1369                    && task.pending_new_subscriptions.is_empty()
1370                    && task.pending_new_bitswap_subscriptions.is_empty()
1371                    && task.pending_new_statement_subscriptions.is_empty())
1372                .then(|| task.network.next_event())
1373                .flatten()
1374                {
1375                    WakeUpReason::NetworkEvent(event)
1376                } else if let Some(start_connect) = {
1377                    let x = (task.num_recent_connection_opening < task.connections_open_pool_size)
1378                        .then(|| {
1379                            task.network
1380                                .unconnected_desired()
1381                                .choose(&mut task.randomness)
1382                                .cloned()
1383                        })
1384                        .flatten();
1385                    x
1386                } {
1387                    WakeUpReason::CanStartConnect(start_connect)
1388                } else if let Some((peer_id, chain_id, kind)) = {
1389                    let x = task
1390                        .network
1391                        .connected_unopened_gossip_desired()
1392                        .choose(&mut task.randomness)
1393                        .map(|(peer_id, chain_id, kind)| (peer_id.clone(), chain_id, kind));
1394                    x
1395                } {
1396                    WakeUpReason::CanOpenGossip(peer_id, chain_id, kind)
1397                } else if let Some(peer_id) = {
1398                    let x = task
1399                        .network
1400                        .connected_unopened_bitswap_desired()
1401                        .choose(&mut task.randomness)
1402                        .cloned();
1403                    x
1404                } {
1405                    WakeUpReason::CanOpenBitswap(peer_id)
1406                } else if let Some((connection_id, message)) =
1407                    task.network.pull_message_to_connection()
1408                {
1409                    WakeUpReason::MessageToConnection {
1410                        connection_id,
1411                        message,
1412                    }
1413                } else {
1414                    'search: loop {
1415                        let mut earlier_unban = None;
1416
1417                        for chain_id in task.network.chains().collect::<Vec<_>>() {
1418                            if task.network.gossip_desired_num(
1419                                chain_id,
1420                                service::GossipKind::ConsensusTransactions,
1421                            ) >= task.network[chain_id].num_out_slots
1422                            {
1423                                continue;
1424                            }
1425
1426                            let now = task.platform.now();
1427
1428                            // Until the chain first connects, prefer slots for important nodes
1429                            // (the bootnodes); otherwise use the general pool.
1430                            if !task.chains_ever_gossip_connected.contains(&chain_id) {
1431                                if let basic_peering_strategy::AssignablePeer::Assignable(peer_id) =
1432                                    task.peering_strategy.pick_assignable_peer_filtered(
1433                                        &chain_id,
1434                                        &now,
1435                                        |peer_id| {
1436                                            task.important_nodes
1437                                                .get(&chain_id)
1438                                                .map_or(false, |nodes| nodes.contains(peer_id))
1439                                        },
1440                                    )
1441                                {
1442                                    break 'search WakeUpReason::CanAssignSlot(
1443                                        peer_id.clone(),
1444                                        chain_id,
1445                                    );
1446                                }
1447                            }
1448
1449                            match task.peering_strategy.pick_assignable_peer(&chain_id, &now) {
1450                                basic_peering_strategy::AssignablePeer::Assignable(peer_id) => {
1451                                    break 'search WakeUpReason::CanAssignSlot(
1452                                        peer_id.clone(),
1453                                        chain_id,
1454                                    );
1455                                }
1456                                basic_peering_strategy::AssignablePeer::AllPeersBanned {
1457                                    next_unban,
1458                                } => {
1459                                    if earlier_unban.as_ref().map_or(true, |b| b > next_unban) {
1460                                        earlier_unban = Some(next_unban.clone());
1461                                    }
1462                                }
1463                                basic_peering_strategy::AssignablePeer::NoPeer => continue,
1464                            }
1465                        }
1466
1467                        match task
1468                            .bitswap_peering_strategy
1469                            .pick_assignable_peer(&task.platform.now())
1470                        {
1471                            bitswap_peering_strategy::AssignablePeer::Assignable(peer_id) => {
1472                                break 'search WakeUpReason::CanAssignBitswapSlot(peer_id.clone());
1473                            }
1474                            bitswap_peering_strategy::AssignablePeer::AllPeersBanned {
1475                                next_unban,
1476                            } => {
1477                                if earlier_unban.as_ref().map_or(true, |b| b > next_unban) {
1478                                    earlier_unban = Some(next_unban.clone());
1479                                }
1480                            }
1481                            bitswap_peering_strategy::AssignablePeer::NoPeer => {}
1482                        }
1483
1484                        if let Some(earlier_unban) = earlier_unban {
1485                            task.platform.sleep_until(earlier_unban).await;
1486                        } else {
1487                            future::pending::<()>().await;
1488                        }
1489                    }
1490                }
1491            };
1492            let next_recent_connection_restore = async {
1493                if task.num_recent_connection_opening != 0
1494                    && task.next_recent_connection_restore.is_none()
1495                {
1496                    task.next_recent_connection_restore = Some(Box::pin(
1497                        task.platform
1498                            .sleep(task.connections_open_pool_restore_delay),
1499                    ));
1500                }
1501                if let Some(delay) = task.next_recent_connection_restore.as_mut() {
1502                    delay.await;
1503                    task.next_recent_connection_restore = None;
1504                    WakeUpReason::NextRecentConnectionRestore
1505                } else {
1506                    future::pending().await
1507                }
1508            };
1509            let finished_sending_event = async {
1510                if let either::Right(event_sending_future) = &mut task.event_senders {
1511                    let event_senders = event_sending_future.await;
1512                    task.event_senders = either::Left(event_senders);
1513                    WakeUpReason::EventSendersReady
1514                } else if !task.events_pending_send.is_empty()
1515                    || !task.pending_new_subscriptions.is_empty()
1516                {
1517                    WakeUpReason::EventSendersReady
1518                } else {
1519                    future::pending().await
1520                }
1521            };
1522            let finished_sending_bitswap_event = async {
1523                if let either::Right(bitswap_event_sending_future) = &mut task.bitswap_event_senders
1524                {
1525                    let bitswap_event_senders = bitswap_event_sending_future.await;
1526                    task.bitswap_event_senders = either::Left(bitswap_event_senders);
1527                    WakeUpReason::BitswapEventSendersReady
1528                } else if task.bitswap_event_pending_send.is_some()
1529                    || !task.pending_new_bitswap_subscriptions.is_empty()
1530                {
1531                    WakeUpReason::BitswapEventSendersReady
1532                } else {
1533                    future::pending().await
1534                }
1535            };
1536            let finished_sending_statement_event = async {
1537                if let either::Right(statement_event_sending_future) =
1538                    &mut task.statement_event_senders
1539                {
1540                    let statement_event_senders = statement_event_sending_future.await;
1541                    task.statement_event_senders = either::Left(statement_event_senders);
1542                    WakeUpReason::StatementEventSendersReady
1543                } else if task.statement_event_pending_send.is_some()
1544                    || !task.pending_new_statement_subscriptions.is_empty()
1545                {
1546                    WakeUpReason::StatementEventSendersReady
1547                } else {
1548                    future::pending().await
1549                }
1550            };
1551            let start_discovery = async {
1552                let Some(mut next_discovery) = task.chains_by_next_discovery.first_entry() else {
1553                    future::pending().await
1554                };
1555                next_discovery.get_mut().await;
1556                let ((_, chain_id), _) = next_discovery.remove_entry();
1557                WakeUpReason::StartDiscovery(chain_id)
1558            };
1559            let reopen_secondaries = async {
1560                let Some(mut next_reopen) = task.secondary_reopens.first_entry() else {
1561                    future::pending().await
1562                };
1563                next_reopen.get_mut().await;
1564                let ((_, chain_id, peer_id), _) = next_reopen.remove_entry();
1565                WakeUpReason::ReopenSecondaries { chain_id, peer_id }
1566            };
1567
1568            message_for_chain_received
1569                .or(message_received)
1570                .or(message_from_task_received)
1571                .or(service_event)
1572                .or(next_recent_connection_restore)
1573                .or(finished_sending_event)
1574                .or(finished_sending_bitswap_event)
1575                .or(finished_sending_statement_event)
1576                .or(start_discovery)
1577                .or(reopen_secondaries)
1578                .await
1579        };
1580
1581        match wake_up_reason {
1582            WakeUpReason::ForegroundClosed => {
1583                // End the task.
1584                return;
1585            }
1586            WakeUpReason::Message(ToBackground::AddChain {
1587                messages_rx,
1588                config,
1589            }) => {
1590                // TODO: this is not a completely clean way of handling duplicate chains, because the existing chain might have a different best block and role and all ; also, multiple sync services will call set_best_block and set_finalized_block
1591                let chain_id = match task.network.add_chain(config) {
1592                    Ok(id) => id,
1593                    Err(service::AddChainError::Duplicate { existing_identical }) => {
1594                        task.network[existing_identical].num_references = task.network
1595                            [existing_identical]
1596                            .num_references
1597                            .checked_add(1)
1598                            .unwrap();
1599                        existing_identical
1600                    }
1601                };
1602
1603                task.chains_by_next_discovery.insert(
1604                    (task.network[chain_id].next_discovery_when.clone(), chain_id),
1605                    Box::pin(
1606                        task.platform
1607                            .sleep_until(task.network[chain_id].next_discovery_when.clone()),
1608                    ),
1609                );
1610
1611                task.messages_rx
1612                    .push(Box::pin(
1613                        messages_rx
1614                            .map(move |msg| (chain_id, msg))
1615                            .chain(stream::once(future::ready((
1616                                chain_id,
1617                                ToBackgroundChain::RemoveChain,
1618                            )))),
1619                    ) as Pin<Box<_>>);
1620
1621                log!(
1622                    &task.platform,
1623                    Debug,
1624                    "network",
1625                    "chain-added",
1626                    id = task.network[chain_id].log_name
1627                );
1628            }
1629            WakeUpReason::EventSendersReady => {
1630                // Dispatch the pending event, if any, to the various senders.
1631
1632                // We made sure that the senders were ready before generating an event.
1633                let either::Left(event_senders) = &mut task.event_senders else {
1634                    unreachable!()
1635                };
1636
1637                if let Some((event_to_dispatch_chain_id, event_to_dispatch)) =
1638                    task.events_pending_send.pop_front()
1639                {
1640                    let mut event_senders = mem::take(event_senders);
1641                    task.event_senders = either::Right(Box::pin(async move {
1642                        // Elements in `event_senders` are removed one by one and inserted
1643                        // back if the channel is still open.
1644                        for index in (0..event_senders.len()).rev() {
1645                            let (event_sender_chain_id, event_sender) =
1646                                event_senders.swap_remove(index);
1647                            if event_sender_chain_id == event_to_dispatch_chain_id {
1648                                if event_sender.send(event_to_dispatch.clone()).await.is_err() {
1649                                    continue;
1650                                }
1651                            }
1652                            event_senders.push((event_sender_chain_id, event_sender));
1653                        }
1654                        event_senders
1655                    }));
1656                } else if !task.pending_new_subscriptions.is_empty() {
1657                    let pending_new_subscriptions = mem::take(&mut task.pending_new_subscriptions);
1658                    let mut event_senders = mem::take(event_senders);
1659                    // TODO: cloning :-/
1660                    let open_gossip_links = task.open_gossip_links.clone();
1661                    task.event_senders = either::Right(Box::pin(async move {
1662                        for (chain_id, new_subscription) in pending_new_subscriptions {
1663                            for ((link_chain_id, peer_id), state) in &open_gossip_links {
1664                                // TODO: optimize? this is O(n) by chain
1665                                if *link_chain_id != chain_id {
1666                                    continue;
1667                                }
1668
1669                                let _ = new_subscription
1670                                    .send(Event::Connected {
1671                                        peer_id: peer_id.clone(),
1672                                        role: state.role,
1673                                        best_block_number: state.best_block_number,
1674                                        best_block_hash: state.best_block_hash,
1675                                    })
1676                                    .await;
1677
1678                                if let Some(finalized_block_height) = state.finalized_block_height {
1679                                    let _ = new_subscription
1680                                        .send(Event::GrandpaNeighborPacket {
1681                                            peer_id: peer_id.clone(),
1682                                            finalized_block_height,
1683                                        })
1684                                        .await;
1685                                }
1686                            }
1687
1688                            event_senders.push((chain_id, new_subscription));
1689                        }
1690
1691                        event_senders
1692                    }));
1693                }
1694            }
1695            WakeUpReason::BitswapEventSendersReady => {
1696                // We made sure that the senders were ready before generating an event.
1697                let either::Left(bitswap_event_senders) = &mut task.bitswap_event_senders else {
1698                    unreachable!()
1699                };
1700
1701                if let Some(event_to_dispatch) = task.bitswap_event_pending_send.take() {
1702                    let mut bitswap_event_senders = mem::take(bitswap_event_senders);
1703                    task.bitswap_event_senders = either::Right(Box::pin(async move {
1704                        // Elements in `bitswap_event_senders` are removed one by one and
1705                        // inserted back if the channel is still open.
1706                        for index in (0..bitswap_event_senders.len()).rev() {
1707                            let event_sender = bitswap_event_senders.swap_remove(index);
1708                            if event_sender.send(event_to_dispatch.clone()).await.is_err() {
1709                                continue;
1710                            }
1711                            bitswap_event_senders.push(event_sender);
1712                        }
1713                        bitswap_event_senders
1714                    }));
1715                } else if !task.pending_new_bitswap_subscriptions.is_empty() {
1716                    bitswap_event_senders.append(&mut task.pending_new_bitswap_subscriptions);
1717                }
1718            }
1719            WakeUpReason::StatementEventSendersReady => {
1720                // We made sure that the senders were ready before generating an event.
1721                let either::Left(statement_event_senders) = &mut task.statement_event_senders
1722                else {
1723                    unreachable!()
1724                };
1725
1726                if let Some((event_to_dispatch_chain_id, event_to_dispatch)) =
1727                    task.statement_event_pending_send.take()
1728                {
1729                    let mut statement_event_senders = mem::take(statement_event_senders);
1730                    task.statement_event_senders = either::Right(Box::pin(async move {
1731                        // Elements in `statement_event_senders` are removed one by one and
1732                        // inserted back if the channel is still open.
1733                        for index in (0..statement_event_senders.len()).rev() {
1734                            let (event_sender_chain_id, event_sender) =
1735                                statement_event_senders.swap_remove(index);
1736                            if event_sender_chain_id == event_to_dispatch_chain_id {
1737                                if event_sender.send(event_to_dispatch.clone()).await.is_err() {
1738                                    continue;
1739                                }
1740                            }
1741                            statement_event_senders.push((event_sender_chain_id, event_sender));
1742                        }
1743                        statement_event_senders
1744                    }));
1745                } else if !task.pending_new_statement_subscriptions.is_empty() {
1746                    statement_event_senders.append(&mut task.pending_new_statement_subscriptions);
1747                }
1748            }
1749            WakeUpReason::MessageFromConnection {
1750                connection_id,
1751                message,
1752            } => {
1753                task.network
1754                    .inject_connection_message(connection_id, message);
1755            }
1756            WakeUpReason::MessageForChain(chain_id, ToBackgroundChain::RemoveChain) => {
1757                if let Some(new_ref) =
1758                    NonZero::<usize>::new(task.network[chain_id].num_references.get() - 1)
1759                {
1760                    task.network[chain_id].num_references = new_ref;
1761                    continue;
1762                }
1763
1764                for peer_id in task
1765                    .network
1766                    .gossip_connected_peers(chain_id, service::GossipKind::ConsensusTransactions)
1767                    .cloned()
1768                    .collect::<Vec<_>>()
1769                {
1770                    task.network
1771                        .gossip_close(
1772                            chain_id,
1773                            &peer_id,
1774                            service::GossipKind::ConsensusTransactions,
1775                        )
1776                        .unwrap();
1777
1778                    let _was_in = task.open_gossip_links.remove(&(chain_id, peer_id));
1779                    debug_assert!(_was_in.is_some());
1780                }
1781
1782                for peer_id in task
1783                    .network
1784                    .gossip_connected_peers(chain_id, service::GossipKind::Statement)
1785                    .cloned()
1786                    .collect::<Vec<_>>()
1787                {
1788                    task.network
1789                        .gossip_close(chain_id, &peer_id, service::GossipKind::Statement)
1790                        .unwrap();
1791                }
1792
1793                let _was_in = task
1794                    .chains_by_next_discovery
1795                    .remove(&(task.network[chain_id].next_discovery_when.clone(), chain_id));
1796                debug_assert!(_was_in.is_some());
1797                task.secondary_reopens.retain(|(_, c, _), _| *c != chain_id);
1798
1799                log!(
1800                    &task.platform,
1801                    Debug,
1802                    "network",
1803                    "chain-removed",
1804                    id = task.network[chain_id].log_name
1805                );
1806                task.v2_statement_peers.remove(&chain_id);
1807                task.current_affinity_filter.remove(&chain_id);
1808                task.important_nodes.remove(&chain_id);
1809                task.chains_ever_gossip_connected.remove(&chain_id);
1810                task.network.remove_chain(chain_id).unwrap();
1811                task.peering_strategy.remove_chain_peers(&chain_id);
1812            }
1813            WakeUpReason::MessageForChain(chain_id, ToBackgroundChain::Subscribe { sender }) => {
1814                task.pending_new_subscriptions.push((chain_id, sender));
1815            }
1816            WakeUpReason::MessageForChain(
1817                _chain_id,
1818                ToBackgroundChain::SubscribeBitswap { sender },
1819            ) => {
1820                task.pending_new_bitswap_subscriptions.push(sender);
1821            }
1822            WakeUpReason::MessageForChain(
1823                chain_id,
1824                ToBackgroundChain::SubscribeStatements { sender },
1825            ) => {
1826                task.pending_new_statement_subscriptions
1827                    .push((chain_id, sender));
1828            }
1829            WakeUpReason::MessageForChain(
1830                chain_id,
1831                ToBackgroundChain::DisconnectAndBan {
1832                    peer_id,
1833                    severity,
1834                    reason,
1835                },
1836            ) => {
1837                let ban_duration = Duration::from_secs(match severity {
1838                    BanSeverity::Low => 10,
1839                    BanSeverity::High => 40,
1840                });
1841
1842                let had_slot = matches!(
1843                    task.peering_strategy.unassign_slot_and_ban(
1844                        &chain_id,
1845                        &peer_id,
1846                        task.platform.now() + ban_duration,
1847                    ),
1848                    basic_peering_strategy::UnassignSlotAndBan::Banned { had_slot: true }
1849                );
1850
1851                if had_slot {
1852                    log!(
1853                        &task.platform,
1854                        Debug,
1855                        "network",
1856                        "slot-unassigned",
1857                        chain = &task.network[chain_id].log_name,
1858                        peer_id,
1859                        ?ban_duration,
1860                        reason = "user-ban",
1861                        user_reason = reason
1862                    );
1863                    task.network.gossip_remove_desired(
1864                        chain_id,
1865                        &peer_id,
1866                        service::GossipKind::ConsensusTransactions,
1867                    );
1868                }
1869
1870                if task.network.gossip_is_connected(
1871                    chain_id,
1872                    &peer_id,
1873                    service::GossipKind::ConsensusTransactions,
1874                ) {
1875                    let _closed_result = task.network.gossip_close(
1876                        chain_id,
1877                        &peer_id,
1878                        service::GossipKind::ConsensusTransactions,
1879                    );
1880                    debug_assert!(_closed_result.is_ok());
1881
1882                    log!(
1883                        &task.platform,
1884                        Debug,
1885                        "network",
1886                        "gossip-closed",
1887                        chain = &task.network[chain_id].log_name,
1888                        peer_id,
1889                    );
1890
1891                    let _was_in = task.open_gossip_links.remove(&(chain_id, peer_id.clone()));
1892                    debug_assert!(_was_in.is_some());
1893                    task.network[chain_id].metrics.gossip_peers_connected.dec();
1894
1895                    // Unlike the network-event handlers below, this message handler can run
1896                    // while another event is already queued, hence the push to a queue.
1897                    task.events_pending_send.push_back((
1898                        chain_id,
1899                        Event::Disconnected {
1900                            peer_id: peer_id.clone(),
1901                        },
1902                    ));
1903                }
1904
1905                // A statement link survives the closing of the block announces link, so it
1906                // needs a close of its own.
1907                task.network.gossip_remove_desired(
1908                    chain_id,
1909                    &peer_id,
1910                    service::GossipKind::Statement,
1911                );
1912                if task
1913                    .network
1914                    .gossip_close(chain_id, &peer_id, service::GossipKind::Statement)
1915                    .is_ok()
1916                {
1917                    log!(
1918                        &task.platform,
1919                        Debug,
1920                        "network",
1921                        "statement-protocol-closed",
1922                        chain = &task.network[chain_id].log_name,
1923                        peer_id,
1924                        reason = "user-ban",
1925                    );
1926                }
1927                if let Some(peers) = task.v2_statement_peers.get_mut(&chain_id) {
1928                    peers.remove(&peer_id);
1929                }
1930            }
1931            WakeUpReason::MessageForChain(
1932                chain_id,
1933                ToBackgroundChain::StartBlocksRequest {
1934                    target,
1935                    config,
1936                    timeout,
1937                    result,
1938                },
1939            ) => {
1940                match &config.start {
1941                    codec::BlocksRequestConfigStart::Hash(hash) => {
1942                        log!(
1943                            &task.platform,
1944                            Debug,
1945                            "network",
1946                            "blocks-request-started",
1947                            chain = task.network[chain_id].log_name, target,
1948                            start = HashDisplay(hash),
1949                            num = config.desired_count.get(),
1950                            descending = ?matches!(config.direction, codec::BlocksRequestDirection::Descending),
1951                            header = ?config.fields.header, body = ?config.fields.body,
1952                            justifications = ?config.fields.justifications
1953                        );
1954                    }
1955                    codec::BlocksRequestConfigStart::Number(number) => {
1956                        log!(
1957                            &task.platform,
1958                            Debug,
1959                            "network",
1960                            "blocks-request-started",
1961                            chain = task.network[chain_id].log_name, target, start = number,
1962                            num = config.desired_count.get(),
1963                            descending = ?matches!(config.direction, codec::BlocksRequestDirection::Descending),
1964                            header = ?config.fields.header, body = ?config.fields.body, justifications = ?config.fields.justifications
1965                        );
1966                    }
1967                }
1968
1969                match task
1970                    .network
1971                    .start_blocks_request(&target, chain_id, config.clone(), timeout)
1972                {
1973                    Ok(substream_id) => {
1974                        task.blocks_requests.insert(substream_id, result);
1975                    }
1976                    Err(service::StartRequestError::NoConnection) => {
1977                        log!(
1978                            &task.platform,
1979                            Debug,
1980                            "network",
1981                            "blocks-request-error",
1982                            chain = task.network[chain_id].log_name,
1983                            target,
1984                            error = "NoConnection"
1985                        );
1986                        let _ = result.send(Err(BlocksRequestError::NoConnection));
1987                    }
1988                }
1989            }
1990            WakeUpReason::MessageForChain(
1991                chain_id,
1992                ToBackgroundChain::StartWarpSyncRequest {
1993                    target,
1994                    begin_hash,
1995                    timeout,
1996                    result,
1997                },
1998            ) => {
1999                log!(
2000                    &task.platform,
2001                    Debug,
2002                    "network",
2003                    "warp-sync-request-started",
2004                    chain = task.network[chain_id].log_name,
2005                    target,
2006                    start = HashDisplay(&begin_hash)
2007                );
2008
2009                match task
2010                    .network
2011                    .start_grandpa_warp_sync_request(&target, chain_id, begin_hash, timeout)
2012                {
2013                    Ok(substream_id) => {
2014                        task.grandpa_warp_sync_requests.insert(substream_id, result);
2015                    }
2016                    Err(service::StartRequestError::NoConnection) => {
2017                        log!(
2018                            &task.platform,
2019                            Debug,
2020                            "network",
2021                            "warp-sync-request-error",
2022                            chain = task.network[chain_id].log_name,
2023                            target,
2024                            error = "NoConnection"
2025                        );
2026                        let _ = result.send(Err(WarpSyncRequestError::NoConnection));
2027                    }
2028                }
2029            }
2030            WakeUpReason::MessageForChain(
2031                chain_id,
2032                ToBackgroundChain::StartStorageProofRequest {
2033                    target,
2034                    config,
2035                    timeout,
2036                    result,
2037                },
2038            ) => {
2039                log!(
2040                    &task.platform,
2041                    Debug,
2042                    "network",
2043                    "storage-proof-request-started",
2044                    chain = task.network[chain_id].log_name,
2045                    target,
2046                    block_hash = HashDisplay(&config.block_hash)
2047                );
2048
2049                match task.network.start_storage_proof_request(
2050                    &target,
2051                    chain_id,
2052                    config.clone(),
2053                    timeout,
2054                ) {
2055                    Ok(substream_id) => {
2056                        task.storage_proof_requests.insert(substream_id, result);
2057                    }
2058                    Err(service::StartRequestMaybeTooLargeError::NoConnection) => {
2059                        log!(
2060                            &task.platform,
2061                            Debug,
2062                            "network",
2063                            "storage-proof-request-error",
2064                            chain = task.network[chain_id].log_name,
2065                            target,
2066                            error = "NoConnection"
2067                        );
2068                        let _ = result.send(Err(StorageProofRequestError::NoConnection));
2069                    }
2070                    Err(service::StartRequestMaybeTooLargeError::RequestTooLarge) => {
2071                        log!(
2072                            &task.platform,
2073                            Debug,
2074                            "network",
2075                            "storage-proof-request-error",
2076                            chain = task.network[chain_id].log_name,
2077                            target,
2078                            error = "RequestTooLarge"
2079                        );
2080                        let _ = result.send(Err(StorageProofRequestError::RequestTooLarge));
2081                    }
2082                };
2083            }
2084            WakeUpReason::MessageForChain(
2085                chain_id,
2086                ToBackgroundChain::StartCallProofRequest {
2087                    target,
2088                    config,
2089                    timeout,
2090                    result,
2091                },
2092            ) => {
2093                log!(
2094                    &task.platform,
2095                    Debug,
2096                    "network",
2097                    "call-proof-request-started",
2098                    chain = task.network[chain_id].log_name,
2099                    target,
2100                    block_hash = HashDisplay(&config.block_hash),
2101                    function = config.method
2102                );
2103                // TODO: log parameter
2104
2105                match task.network.start_call_proof_request(
2106                    &target,
2107                    chain_id,
2108                    config.clone(),
2109                    timeout,
2110                ) {
2111                    Ok(substream_id) => {
2112                        task.call_proof_requests.insert(substream_id, result);
2113                    }
2114                    Err(service::StartRequestMaybeTooLargeError::NoConnection) => {
2115                        log!(
2116                            &task.platform,
2117                            Debug,
2118                            "network",
2119                            "call-proof-request-error",
2120                            chain = task.network[chain_id].log_name,
2121                            target,
2122                            error = "NoConnection"
2123                        );
2124                        let _ = result.send(Err(CallProofRequestError::NoConnection));
2125                    }
2126                    Err(service::StartRequestMaybeTooLargeError::RequestTooLarge) => {
2127                        log!(
2128                            &task.platform,
2129                            Debug,
2130                            "network",
2131                            "call-proof-request-error",
2132                            chain = task.network[chain_id].log_name,
2133                            target,
2134                            error = "RequestTooLarge"
2135                        );
2136                        let _ = result.send(Err(CallProofRequestError::RequestTooLarge));
2137                    }
2138                };
2139            }
2140            WakeUpReason::MessageForChain(
2141                chain_id,
2142                ToBackgroundChain::StartChildStorageProofRequest {
2143                    target,
2144                    config,
2145                    timeout,
2146                    result,
2147                },
2148            ) => {
2149                log!(
2150                    &task.platform,
2151                    Debug,
2152                    "network",
2153                    "child-storage-proof-request-started",
2154                    chain = task.network[chain_id].log_name,
2155                    target,
2156                    block_hash = HashDisplay(&config.block_hash)
2157                );
2158
2159                match task.network.start_child_storage_proof_request(
2160                    &target,
2161                    chain_id,
2162                    codec::ChildStorageProofRequestConfig {
2163                        block_hash: config.block_hash,
2164                        child_trie: &config.child_trie,
2165                        keys: config.keys.iter().map(|k| k.as_slice()),
2166                    },
2167                    timeout,
2168                ) {
2169                    Ok(substream_id) => {
2170                        task.child_storage_proof_requests
2171                            .insert(substream_id, result);
2172                    }
2173                    Err(service::StartRequestMaybeTooLargeError::NoConnection) => {
2174                        log!(
2175                            &task.platform,
2176                            Debug,
2177                            "network",
2178                            "child-storage-proof-request-error",
2179                            chain = task.network[chain_id].log_name,
2180                            target,
2181                            error = "NoConnection"
2182                        );
2183                        let _ = result.send(Err(ChildStorageProofRequestError::NoConnection));
2184                    }
2185                    Err(service::StartRequestMaybeTooLargeError::RequestTooLarge) => {
2186                        log!(
2187                            &task.platform,
2188                            Debug,
2189                            "network",
2190                            "child-storage-proof-request-error",
2191                            chain = task.network[chain_id].log_name,
2192                            target,
2193                            error = "RequestTooLarge"
2194                        );
2195                        let _ = result.send(Err(ChildStorageProofRequestError::RequestTooLarge));
2196                    }
2197                };
2198            }
2199            WakeUpReason::MessageForChain(
2200                chain_id,
2201                ToBackgroundChain::SetLocalBestBlock {
2202                    best_hash,
2203                    best_number,
2204                },
2205            ) => {
2206                task.network
2207                    .set_chain_local_best_block(chain_id, best_hash, best_number);
2208            }
2209            WakeUpReason::MessageForChain(
2210                chain_id,
2211                ToBackgroundChain::SetLocalGrandpaState { grandpa_state },
2212            ) => {
2213                log!(
2214                    &task.platform,
2215                    Debug,
2216                    "network",
2217                    "local-grandpa-state-announced",
2218                    chain = task.network[chain_id].log_name,
2219                    set_id = grandpa_state.set_id,
2220                    commit_finalized_height = grandpa_state.commit_finalized_height,
2221                );
2222
2223                // TODO: log the list of peers we sent the packet to
2224
2225                task.network
2226                    .gossip_broadcast_grandpa_state_and_update(chain_id, grandpa_state);
2227            }
2228            WakeUpReason::MessageForChain(
2229                chain_id,
2230                ToBackgroundChain::AnnounceTransaction {
2231                    transaction,
2232                    result,
2233                },
2234            ) => {
2235                // TODO: keep track of which peer knows about which transaction, and don't send it again
2236
2237                let peers_to_send = task
2238                    .network
2239                    .gossip_connected_peers(chain_id, service::GossipKind::ConsensusTransactions)
2240                    .cloned()
2241                    .collect::<Vec<_>>();
2242
2243                let mut peers_sent = Vec::with_capacity(peers_to_send.len());
2244                let mut peers_queue_full = Vec::with_capacity(peers_to_send.len());
2245                for peer in &peers_to_send {
2246                    match task
2247                        .network
2248                        .gossip_send_transaction(peer, chain_id, &transaction)
2249                    {
2250                        Ok(()) => peers_sent.push(peer.to_base58()),
2251                        Err(QueueNotificationError::QueueFull) => {
2252                            peers_queue_full.push(peer.to_base58())
2253                        }
2254                        Err(QueueNotificationError::NoConnection) => unreachable!(),
2255                    }
2256                }
2257
2258                log!(
2259                    &task.platform,
2260                    Debug,
2261                    "network",
2262                    "transaction-announced",
2263                    chain = task.network[chain_id].log_name,
2264                    transaction =
2265                        hex::encode(blake2_rfc::blake2b::blake2b(32, &[], &transaction).as_bytes()),
2266                    size = transaction.len(),
2267                    peers_sent = peers_sent.join(", "),
2268                    peers_queue_full = peers_queue_full.join(", "),
2269                );
2270
2271                let _ = result.send(peers_to_send);
2272            }
2273            WakeUpReason::MessageForChain(
2274                chain_id,
2275                ToBackgroundChain::SendBlockAnnounce {
2276                    target,
2277                    scale_encoded_header,
2278                    is_best,
2279                    result,
2280                },
2281            ) => {
2282                // TODO: log who the announce was sent to
2283                let _ = result.send(task.network.gossip_send_block_announce(
2284                    &target,
2285                    chain_id,
2286                    &scale_encoded_header,
2287                    is_best,
2288                ));
2289            }
2290            WakeUpReason::MessageForChain(
2291                _chain_id,
2292                ToBackgroundChain::SendBitswapMessage {
2293                    target,
2294                    message,
2295                    result,
2296                },
2297            ) => {
2298                let _ = result.send(task.network.bitswap_send_message(&target, message));
2299            }
2300            WakeUpReason::MessageForChain(
2301                _chain_id,
2302                ToBackgroundChain::BroadcastBitswapMessage { message, result },
2303            ) => {
2304                let peers = task
2305                    .network
2306                    .established_bitswap_desired()
2307                    .cloned()
2308                    .collect::<Vec<_>>();
2309                let results = peers
2310                    .iter()
2311                    .map(|peer| {
2312                        (
2313                            peer,
2314                            task.network.bitswap_send_message(peer, message.clone()),
2315                        )
2316                    })
2317                    .collect::<Vec<_>>(); // we must collect first to send all messages
2318
2319                let succeeded_peers = results
2320                    .iter()
2321                    .filter_map(|(peer, r)| r.is_ok().then(|| (*peer).clone()))
2322                    .collect::<Vec<_>>();
2323
2324                // TODO: introspecting a third-party error type below doesn't seem good.
2325                let r = if !succeeded_peers.is_empty() {
2326                    Ok(succeeded_peers)
2327                } else if results
2328                    .iter()
2329                    .any(|(_peer, r)| matches!(r, Err(SendBitswapMessageError::QueueFull)))
2330                {
2331                    // `QueueFull` has higher priority than `NoConnection` for possible
2332                    // back-pressure in higher level code.
2333                    Err(SendBitswapMessageError::QueueFull)
2334                } else {
2335                    // This is only emitted if all peers fail with `NoConnection` or there is no
2336                    // peers at all.
2337                    Err(SendBitswapMessageError::NoConnection)
2338                };
2339
2340                let _ = result.send(r);
2341            }
2342            WakeUpReason::MessageForChain(
2343                chain_id,
2344                ToBackgroundChain::BroadcastStatement { statement, result },
2345            ) => {
2346                let peers_to_send = task
2347                    .network
2348                    .gossip_connected_peers(chain_id, service::GossipKind::Statement)
2349                    .cloned()
2350                    .collect::<Vec<_>>();
2351
2352                let total = peers_to_send.len();
2353                let mut sent = 0;
2354                for peer in &peers_to_send {
2355                    if task
2356                        .network
2357                        .gossip_send_statement(peer, chain_id, statement.clone())
2358                        .is_ok()
2359                    {
2360                        sent += 1;
2361                    }
2362                }
2363
2364                log!(
2365                    &task.platform,
2366                    Debug,
2367                    "network",
2368                    "statement-broadcast",
2369                    chain = task.network[chain_id].log_name,
2370                    sent,
2371                    total,
2372                );
2373
2374                let _ = result.send(BroadcastStatementResult { sent, total });
2375            }
2376            WakeUpReason::MessageForChain(
2377                chain_id,
2378                ToBackgroundChain::UpdateTopicAffinity { filter },
2379            ) => {
2380                task.current_affinity_filter
2381                    .insert(chain_id, filter.clone());
2382                if let Some(peers) = task.v2_statement_peers.get_mut(&chain_id) {
2383                    let mut to_remove = Vec::new();
2384                    for peer_id in peers.iter() {
2385                        if let Err(
2386                            SendTopicAffinityError::NoConnection
2387                            | SendTopicAffinityError::ProtocolV1,
2388                        ) = task.network.send_topic_affinity(peer_id, chain_id, &filter)
2389                        {
2390                            to_remove.push(peer_id.clone());
2391                        }
2392                    }
2393                    for peer_id in &to_remove {
2394                        peers.remove(peer_id);
2395                    }
2396                }
2397            }
2398            WakeUpReason::MessageForChain(
2399                chain_id,
2400                ToBackgroundChain::Discover {
2401                    list,
2402                    important_nodes,
2403                },
2404            ) => {
2405                for (peer_id, addrs) in list {
2406                    if important_nodes {
2407                        task.important_nodes
2408                            .entry(chain_id)
2409                            .or_default()
2410                            .insert(peer_id.clone());
2411                    }
2412
2413                    // Note that we must call this function before `insert_address`, as documented
2414                    // in `basic_peering_strategy`.
2415                    task.peering_strategy
2416                        .insert_chain_peer(chain_id, peer_id.clone(), 30); // TODO: constant
2417
2418                    for addr in addrs {
2419                        let _ =
2420                            task.peering_strategy
2421                                .insert_address(&peer_id, addr.into_bytes(), 10);
2422                        // TODO: constant
2423                    }
2424                }
2425            }
2426            WakeUpReason::MessageForChain(
2427                chain_id,
2428                ToBackgroundChain::DiscoveredNodes { result },
2429            ) => {
2430                // TODO: consider returning Vec<u8>s for the addresses?
2431                let _ = result.send(
2432                    task.peering_strategy
2433                        .chain_peers_unordered(&chain_id)
2434                        .map(|peer_id| {
2435                            let addrs = task
2436                                .peering_strategy
2437                                .peer_addresses(peer_id)
2438                                .map(|a| Multiaddr::from_bytes(a.to_owned()).unwrap())
2439                                .collect::<Vec<_>>();
2440                            (peer_id.clone(), addrs)
2441                        })
2442                        .collect::<Vec<_>>(),
2443                );
2444            }
2445            WakeUpReason::MessageForChain(chain_id, ToBackgroundChain::PeersList { result }) => {
2446                let _ = result.send(
2447                    task.network
2448                        .gossip_connected_peers(
2449                            chain_id,
2450                            service::GossipKind::ConsensusTransactions,
2451                        )
2452                        .cloned()
2453                        .collect(),
2454                );
2455            }
2456            WakeUpReason::ReopenSecondaries { chain_id, peer_id } => {
2457                // Refused if the link is gone meanwhile.
2458                let outcome = task.network.gossip_reopen_secondaries(chain_id, &peer_id);
2459                log!(
2460                    &task.platform,
2461                    Debug,
2462                    "network",
2463                    "gossip-secondaries-reopen",
2464                    chain = &task.network[chain_id].log_name,
2465                    peer_id,
2466                    ?outcome,
2467                );
2468            }
2469            WakeUpReason::StartDiscovery(chain_id) => {
2470                // Re-insert the chain in `chains_by_next_discovery`.
2471                let chain = &mut task.network[chain_id];
2472                chain.next_discovery_when = task.platform.now() + chain.next_discovery_period;
2473                chain.next_discovery_period =
2474                    cmp::min(chain.next_discovery_period * 2, Duration::from_secs(120));
2475                task.chains_by_next_discovery.insert(
2476                    (chain.next_discovery_when.clone(), chain_id),
2477                    Box::pin(
2478                        task.platform
2479                            .sleep(task.network[chain_id].next_discovery_period),
2480                    ),
2481                );
2482
2483                // Iterative-style discovery: instead of a single FindNode against one peer,
2484                // dispatch up to ALPHA=3 FindNode requests in parallel to distinct peers,
2485                // each with a distinct random target. This gives substantially better DHT
2486                // coverage per discovery round (more peers asked, more diverse keyspace
2487                // walked) without requiring a per-query state machine.
2488                //
2489                // Order of preference for the target peer pool:
2490                //  1. Peers we know speak this chain's Kad protocol (from Identify).
2491                //  2. Peers with an open block-announces gossip substream (best-effort:
2492                //     they're connected and likely speak Kad even if we haven't gotten
2493                //     Identify yet).
2494                const PARALLEL_FIND_NODE_PER_ROUND: usize = 3;
2495
2496                let mut candidates: Vec<PeerId> = task
2497                    .network
2498                    .kademlia_capable_peers(chain_id)
2499                    .cloned()
2500                    .collect();
2501                for p in task
2502                    .network
2503                    .gossip_connected_peers(chain_id, service::GossipKind::ConsensusTransactions)
2504                    .cloned()
2505                {
2506                    if !candidates.contains(&p) {
2507                        candidates.push(p);
2508                    }
2509                }
2510
2511                let started = dispatch_find_node_requests(
2512                    &mut task.network,
2513                    &mut task.randomness,
2514                    chain_id,
2515                    &candidates,
2516                    PARALLEL_FIND_NODE_PER_ROUND,
2517                );
2518
2519                let chain_log_name = &task.network[chain_id].log_name;
2520                for (request_target, requested_peer_id) in &started {
2521                    log!(
2522                        &task.platform,
2523                        Debug,
2524                        "network",
2525                        "discovery-find-node-started",
2526                        chain = chain_log_name,
2527                        request_target,
2528                        requested_peer_id
2529                    );
2530                }
2531                if started.is_empty() {
2532                    log!(
2533                        &task.platform,
2534                        Debug,
2535                        "network",
2536                        "discovery-skipped-no-peer",
2537                        chain = chain_log_name
2538                    );
2539                }
2540            }
2541            WakeUpReason::NetworkEvent(service::Event::HandshakeFinished {
2542                peer_id,
2543                expected_peer_id,
2544                id,
2545            }) => {
2546                let remote_addr =
2547                    Multiaddr::from_bytes(task.network.connection_remote_addr(id)).unwrap(); // TODO: review this unwrap
2548                if let Some(expected_peer_id) = expected_peer_id.as_ref().filter(|p| **p != peer_id)
2549                {
2550                    log!(
2551                        &task.platform,
2552                        Debug,
2553                        "network",
2554                        "handshake-finished-peer-id-mismatch",
2555                        remote_addr,
2556                        expected_peer_id,
2557                        actual_peer_id = peer_id
2558                    );
2559
2560                    let _was_in = task
2561                        .peering_strategy
2562                        .decrease_address_connections_and_remove_if_zero(
2563                            expected_peer_id,
2564                            remote_addr.as_ref(),
2565                        );
2566                    debug_assert!(_was_in.is_ok());
2567                    let _ = task.peering_strategy.increase_address_connections(
2568                        &peer_id,
2569                        remote_addr.into_bytes().to_vec(),
2570                        10,
2571                    );
2572                } else {
2573                    log!(
2574                        &task.platform,
2575                        Debug,
2576                        "network",
2577                        "handshake-finished",
2578                        remote_addr,
2579                        peer_id
2580                    );
2581                }
2582
2583                task.metrics.connections_handshakes_finished.inc();
2584                task.bitswap_peering_strategy
2585                    .increase_peer_connections(&peer_id);
2586            }
2587            WakeUpReason::NetworkEvent(service::Event::PreHandshakeDisconnected {
2588                expected_peer_id: Some(_),
2589                ..
2590            })
2591            | WakeUpReason::NetworkEvent(service::Event::Disconnected { .. }) => {
2592                let (address, peer_id, handshake_finished) = match wake_up_reason {
2593                    WakeUpReason::NetworkEvent(service::Event::PreHandshakeDisconnected {
2594                        address,
2595                        expected_peer_id: Some(peer_id),
2596                        ..
2597                    }) => (address, peer_id, false),
2598                    WakeUpReason::NetworkEvent(service::Event::Disconnected {
2599                        address,
2600                        peer_id,
2601                        ..
2602                    }) => (address, peer_id, true),
2603                    _ => unreachable!(),
2604                };
2605
2606                task.peering_strategy
2607                    .decrease_address_connections(&peer_id, &address)
2608                    .unwrap();
2609                let address = Multiaddr::from_bytes(address).unwrap();
2610                log!(
2611                    &task.platform,
2612                    Debug,
2613                    "network",
2614                    "connection-shutdown",
2615                    peer_id,
2616                    address,
2617                    ?handshake_finished
2618                );
2619                task.metrics.connections_shutdowns.inc();
2620
2621                // Ban the peer in order to avoid trying over and over again the same address(es).
2622                // Even if the handshake was finished, it is possible that the peer simply shuts
2623                // down connections immediately after it has been opened, hence the ban.
2624                // Due to race conditions and peerid mismatches, it is possible that there is
2625                // another existing connection or connection attempt with that same peer. However,
2626                // it is not possible to be sure that we will reach 0 connections or connection
2627                // attempts, and thus we ban the peer every time.
2628                // Pre-handshake failures get a shorter ban: many parallel dials time out
2629                // before any handshake completes, and a long slot-hold there dominates
2630                // peer-discovery latency on restarts.
2631                let ban_duration = if handshake_finished {
2632                    Duration::from_secs(5)
2633                } else {
2634                    Duration::from_secs(2)
2635                };
2636                task.network.gossip_remove_desired_all(
2637                    &peer_id,
2638                    service::GossipKind::ConsensusTransactions,
2639                );
2640                task.network
2641                    .gossip_remove_desired_all(&peer_id, service::GossipKind::Statement);
2642                for (&chain_id, what_happened) in task
2643                    .peering_strategy
2644                    .unassign_slots_and_ban(&peer_id, task.platform.now() + ban_duration)
2645                {
2646                    if matches!(
2647                        what_happened,
2648                        basic_peering_strategy::UnassignSlotsAndBan::Banned { had_slot: true }
2649                    ) {
2650                        log!(
2651                            &task.platform,
2652                            Debug,
2653                            "network",
2654                            "slot-unassigned",
2655                            chain = &task.network[chain_id].log_name,
2656                            peer_id,
2657                            ?ban_duration,
2658                            // TODO: `reason` might be wrong, `handshake_finished` is not checked.
2659                            reason = "pre-handshake-disconnect"
2660                        );
2661                    }
2662                }
2663
2664                if handshake_finished {
2665                    task.network.bitswap_remove_desired(&peer_id);
2666                    let what_happened = task
2667                        .bitswap_peering_strategy
2668                        .unassign_slot_and_ban(&peer_id, task.platform.now() + ban_duration);
2669                    if matches!(
2670                        what_happened,
2671                        bitswap_peering_strategy::UnassignSlotAndBan::Banned { had_slot: true },
2672                    ) {
2673                        log!(
2674                            &task.platform,
2675                            Debug,
2676                            "network",
2677                            "bitswap-slot-unassigned",
2678                            peer_id,
2679                            ?ban_duration,
2680                            reason = "disconnect",
2681                        );
2682                    }
2683                    let _ = task
2684                        .bitswap_peering_strategy
2685                        .decrease_peer_connections(&peer_id);
2686                }
2687            }
2688            WakeUpReason::NetworkEvent(service::Event::PreHandshakeDisconnected {
2689                expected_peer_id: None,
2690                ..
2691            }) => {
2692                // This path can't be reached as we always set an expected peer id when creating
2693                // a connection.
2694                debug_assert!(false);
2695            }
2696            WakeUpReason::NetworkEvent(service::Event::PingOutSuccess {
2697                id,
2698                peer_id,
2699                ping_time,
2700            }) => {
2701                let remote_addr =
2702                    Multiaddr::from_bytes(task.network.connection_remote_addr(id)).unwrap(); // TODO: review this unwrap
2703                log!(
2704                    &task.platform,
2705                    Debug,
2706                    "network",
2707                    "pong",
2708                    peer_id,
2709                    remote_addr,
2710                    ?ping_time
2711                );
2712            }
2713            WakeUpReason::NetworkEvent(service::Event::BlockAnnounce {
2714                chain_id,
2715                peer_id,
2716                announce,
2717            }) => {
2718                log!(
2719                    &task.platform,
2720                    Debug,
2721                    "network",
2722                    "block-announce-received",
2723                    chain = &task.network[chain_id].log_name,
2724                    peer_id,
2725                    block_hash = HashDisplay(&header::hash_from_scale_encoded_header(
2726                        announce.decode().scale_encoded_header
2727                    )),
2728                    is_best = announce.decode().is_best
2729                );
2730
2731                let decoded_announce = announce.decode();
2732                if decoded_announce.is_best {
2733                    let link = task
2734                        .open_gossip_links
2735                        .get_mut(&(chain_id, peer_id.clone()))
2736                        .unwrap();
2737                    if let Ok(decoded) = header::decode(
2738                        decoded_announce.scale_encoded_header,
2739                        task.network[chain_id].block_number_bytes,
2740                    ) {
2741                        link.best_block_hash = header::hash_from_scale_encoded_header(
2742                            decoded_announce.scale_encoded_header,
2743                        );
2744                        link.best_block_number = decoded.number;
2745                    }
2746                }
2747
2748                debug_assert!(task.events_pending_send.is_empty());
2749                task.events_pending_send
2750                    .push_back((chain_id, Event::BlockAnnounce { peer_id, announce }));
2751            }
2752            WakeUpReason::NetworkEvent(service::Event::GossipConnected {
2753                peer_id,
2754                chain_id,
2755                role,
2756                best_number,
2757                best_hash,
2758                kind: service::GossipKind::ConsensusTransactions,
2759            }) => {
2760                log!(
2761                    &task.platform,
2762                    Debug,
2763                    "network",
2764                    "gossip-open-success",
2765                    chain = &task.network[chain_id].log_name,
2766                    peer_id,
2767                    best_number,
2768                    best_hash = HashDisplay(&best_hash)
2769                );
2770
2771                let _prev_value = task.open_gossip_links.insert(
2772                    (chain_id, peer_id.clone()),
2773                    OpenGossipLinkState {
2774                        best_block_number: best_number,
2775                        best_block_hash: best_hash,
2776                        role,
2777                        finalized_block_height: None,
2778                        secondary_reopened_at_once: false,
2779                    },
2780                );
2781                debug_assert!(_prev_value.is_none());
2782                task.network[chain_id].metrics.gossip_peers_connected.inc();
2783
2784                task.chains_ever_gossip_connected.insert(chain_id);
2785
2786                debug_assert!(task.events_pending_send.is_empty());
2787                task.events_pending_send.push_back((
2788                    chain_id,
2789                    Event::Connected {
2790                        peer_id,
2791                        role,
2792                        best_block_number: best_number,
2793                        best_block_hash: best_hash,
2794                    },
2795                ));
2796            }
2797            WakeUpReason::NetworkEvent(service::Event::GossipOpenFailed {
2798                peer_id,
2799                chain_id,
2800                error,
2801                kind: service::GossipKind::ConsensusTransactions,
2802            }) => {
2803                log!(
2804                    &task.platform,
2805                    Debug,
2806                    "network",
2807                    "gossip-open-error",
2808                    chain = &task.network[chain_id].log_name,
2809                    peer_id,
2810                    ?error,
2811                );
2812                // Must exceed polkadot-sdk's 5s notification-reject ban; otherwise we retry
2813                // into a still-active remote ban. 0.5s margin covers network delay and
2814                // clock skew between the two sides' ban timers.
2815                let ban_duration = Duration::from_millis(5500);
2816
2817                // Note that peer doesn't necessarily have an out slot, as this event might happen
2818                // as a result of an inbound gossip connection.
2819                let had_slot = if let service::GossipConnectError::GenesisMismatch { .. } = error {
2820                    matches!(
2821                        task.peering_strategy
2822                            .unassign_slot_and_remove_chain_peer(&chain_id, &peer_id),
2823                        basic_peering_strategy::UnassignSlotAndRemoveChainPeer::HadSlot
2824                    )
2825                } else {
2826                    matches!(
2827                        task.peering_strategy.unassign_slot_and_ban(
2828                            &chain_id,
2829                            &peer_id,
2830                            task.platform.now() + ban_duration,
2831                        ),
2832                        basic_peering_strategy::UnassignSlotAndBan::Banned { had_slot: true }
2833                    )
2834                };
2835
2836                if had_slot {
2837                    log!(
2838                        &task.platform,
2839                        Debug,
2840                        "network",
2841                        "slot-unassigned",
2842                        chain = &task.network[chain_id].log_name,
2843                        peer_id,
2844                        ?ban_duration,
2845                        reason = "gossip-open-failed"
2846                    );
2847                    task.network.gossip_remove_desired(
2848                        chain_id,
2849                        &peer_id,
2850                        service::GossipKind::ConsensusTransactions,
2851                    );
2852                }
2853            }
2854            WakeUpReason::NetworkEvent(service::Event::GossipDisconnected {
2855                peer_id,
2856                chain_id,
2857                kind: service::GossipKind::ConsensusTransactions,
2858            }) => {
2859                log!(
2860                    &task.platform,
2861                    Debug,
2862                    "network",
2863                    "gossip-closed",
2864                    chain = &task.network[chain_id].log_name,
2865                    peer_id,
2866                );
2867                let ban_duration = Duration::from_secs(10);
2868
2869                // The secondary substreams close with the block announces substream.
2870                task.secondary_reopens
2871                    .retain(|(_, c, p), _| *c != chain_id || *p != peer_id);
2872
2873                let _was_in = task.open_gossip_links.remove(&(chain_id, peer_id.clone()));
2874                debug_assert!(_was_in.is_some());
2875                task.network[chain_id].metrics.gossip_peers_connected.dec();
2876
2877                // Note that peer doesn't necessarily have an out slot, as this event might happen
2878                // as a result of an inbound gossip connection.
2879                if matches!(
2880                    task.peering_strategy.unassign_slot_and_ban(
2881                        &chain_id,
2882                        &peer_id,
2883                        task.platform.now() + ban_duration,
2884                    ),
2885                    basic_peering_strategy::UnassignSlotAndBan::Banned { had_slot: true }
2886                ) {
2887                    log!(
2888                        &task.platform,
2889                        Debug,
2890                        "network",
2891                        "slot-unassigned",
2892                        chain = &task.network[chain_id].log_name,
2893                        peer_id,
2894                        ?ban_duration,
2895                        reason = "gossip-closed"
2896                    );
2897                    task.network.gossip_remove_desired(
2898                        chain_id,
2899                        &peer_id,
2900                        service::GossipKind::ConsensusTransactions,
2901                    );
2902                }
2903
2904                // The statement substream closes with the block announces substream, unless
2905                // the peer has a statement link of its own.
2906                if !task.network.gossip_is_connected(
2907                    chain_id,
2908                    &peer_id,
2909                    service::GossipKind::Statement,
2910                ) && let Some(peers) = task.v2_statement_peers.get_mut(&chain_id)
2911                {
2912                    peers.remove(&peer_id);
2913                }
2914
2915                debug_assert!(task.events_pending_send.is_empty());
2916                task.events_pending_send
2917                    .push_back((chain_id, Event::Disconnected { peer_id }));
2918            }
2919            WakeUpReason::NetworkEvent(service::Event::GossipSecondaryClosed {
2920                peer_id,
2921                chain_id,
2922                error,
2923            }) => {
2924                log!(
2925                    &task.platform,
2926                    Debug,
2927                    "network",
2928                    "gossip-secondary-closed",
2929                    chain = &task.network[chain_id].log_name,
2930                    peer_id,
2931                    ?error,
2932                );
2933
2934                // Reopen at once the first time, as polkadot-sdk reserves the peer for these
2935                // protocols slightly after block announces open. Afterwards, retry at a short
2936                // interval rather than in a tight loop.
2937                let link = task.open_gossip_links.get_mut(&(chain_id, peer_id.clone()));
2938                let Some(link) = link else {
2939                    // The link is open while this event can happen.
2940                    log!(
2941                        &task.platform,
2942                        Warn,
2943                        "network",
2944                        "gossip-secondary-closed-unknown-link",
2945                        chain = &task.network[chain_id].log_name,
2946                        peer_id,
2947                    );
2948                    continue;
2949                };
2950                if !link.secondary_reopened_at_once {
2951                    link.secondary_reopened_at_once = true;
2952                    let outcome = task.network.gossip_reopen_secondaries(chain_id, &peer_id);
2953                    log!(
2954                        &task.platform,
2955                        Debug,
2956                        "network",
2957                        "gossip-secondaries-reopen",
2958                        chain = &task.network[chain_id].log_name,
2959                        peer_id,
2960                        ?outcome,
2961                    );
2962                } else if !task
2963                    .secondary_reopens
2964                    .keys()
2965                    .any(|(_, c, p)| *c == chain_id && *p == peer_id)
2966                {
2967                    task.secondary_reopens.insert(
2968                        (
2969                            task.platform.now() + SECONDARY_SUBSTREAM_RETRY_DELAY,
2970                            chain_id,
2971                            peer_id,
2972                        ),
2973                        Box::pin(task.platform.sleep(SECONDARY_SUBSTREAM_RETRY_DELAY)),
2974                    );
2975                }
2976            }
2977            WakeUpReason::NetworkEvent(service::Event::BitswapConnected { peer_id }) => {
2978                task.bitswap_connected_peers = task.bitswap_connected_peers.saturating_add(1);
2979                log!(
2980                    &task.platform,
2981                    Debug,
2982                    "network",
2983                    "bitswap-open-success",
2984                    peer_id,
2985                    total = task.bitswap_connected_peers
2986                );
2987            }
2988            WakeUpReason::NetworkEvent(service::Event::BitswapOpenFailed { peer_id, error }) => {
2989                log!(
2990                    &task.platform,
2991                    Debug,
2992                    "network",
2993                    "bitswap-open-error",
2994                    peer_id,
2995                    ?error
2996                );
2997                let ban_duration = if error.is_protocol_not_available() {
2998                    Duration::from_secs(600)
2999                } else {
3000                    Duration::from_secs(15)
3001                };
3002                if matches!(
3003                    task.bitswap_peering_strategy
3004                        .unassign_slot_and_ban(&peer_id, task.platform.now() + ban_duration,),
3005                    bitswap_peering_strategy::UnassignSlotAndBan::Banned { had_slot: true }
3006                ) {
3007                    log!(
3008                        &task.platform,
3009                        Debug,
3010                        "network",
3011                        "bitswap-slot-unassigned",
3012                        peer_id,
3013                        ?ban_duration,
3014                        reason = "bitswap-open-failed"
3015                    );
3016                    task.network.bitswap_remove_desired(&peer_id);
3017                }
3018            }
3019            WakeUpReason::NetworkEvent(service::Event::BitswapMessage { peer_id, message }) => {
3020                log!(
3021                    &task.platform,
3022                    Debug,
3023                    "network",
3024                    "bitswap-message-received",
3025                    peer_id
3026                );
3027                debug_assert!(task.bitswap_event_pending_send.is_none());
3028                task.bitswap_event_pending_send =
3029                    Some(BitswapEvent::BitswapMessage { peer_id, message });
3030            }
3031            WakeUpReason::NetworkEvent(service::Event::BitswapDisconnected { peer_id }) => {
3032                debug_assert!(task.bitswap_connected_peers > 0);
3033                task.bitswap_connected_peers = task.bitswap_connected_peers.saturating_sub(1);
3034                log!(
3035                    &task.platform,
3036                    Debug,
3037                    "network",
3038                    "bitswap-closed",
3039                    peer_id,
3040                    total = task.bitswap_connected_peers
3041                );
3042                let ban_duration = Duration::from_secs(10);
3043                if matches!(
3044                    task.bitswap_peering_strategy
3045                        .unassign_slot_and_ban(&peer_id, task.platform.now() + ban_duration,),
3046                    bitswap_peering_strategy::UnassignSlotAndBan::Banned { had_slot: true }
3047                ) {
3048                    log!(
3049                        &task.platform,
3050                        Debug,
3051                        "network",
3052                        "bitswap-slot-unassigned",
3053                        peer_id,
3054                        ?ban_duration,
3055                        reason = "bitswap-closed"
3056                    );
3057                    task.network.bitswap_remove_desired(&peer_id);
3058                }
3059            }
3060            WakeUpReason::NetworkEvent(service::Event::RequestResult {
3061                substream_id,
3062                peer_id,
3063                chain_id,
3064                response: service::RequestResult::Blocks(response),
3065            }) => {
3066                match &response {
3067                    Ok(blocks) => {
3068                        log!(
3069                            &task.platform,
3070                            Debug,
3071                            "network",
3072                            "blocks-request-success",
3073                            chain = task.network[chain_id].log_name,
3074                            target = peer_id,
3075                            num_blocks = blocks.len(),
3076                            block_data_total_size =
3077                                BytesDisplay(blocks.iter().fold(0, |sum, block| {
3078                                    let block_size = block.header.as_ref().map_or(0, |h| h.len())
3079                                        + block
3080                                            .body
3081                                            .as_ref()
3082                                            .map_or(0, |b| b.iter().fold(0, |s, e| s + e.len()))
3083                                        + block
3084                                            .justifications
3085                                            .as_ref()
3086                                            .into_iter()
3087                                            .flat_map(|l| l.iter())
3088                                            .fold(0, |s, j| s + j.justification.len());
3089                                    sum + u64::try_from(block_size).unwrap()
3090                                }))
3091                        );
3092                    }
3093                    Err(error) => {
3094                        log!(
3095                            &task.platform,
3096                            Debug,
3097                            "network",
3098                            "blocks-request-error",
3099                            chain = task.network[chain_id].log_name,
3100                            target = peer_id,
3101                            ?error
3102                        );
3103                    }
3104                }
3105
3106                match &response {
3107                    Ok(_) => {}
3108                    Err(service::BlocksRequestError::Request(err)) if !err.is_protocol_error() => {}
3109                    Err(err) => {
3110                        log!(
3111                            &task.platform,
3112                            Debug,
3113                            "network",
3114                            format!(
3115                                "Error in block request with {}. This might indicate an \
3116                                incompatibility. Error: {}",
3117                                peer_id, err
3118                            )
3119                        );
3120                    }
3121                }
3122
3123                let _ = task
3124                    .blocks_requests
3125                    .remove(&substream_id)
3126                    .unwrap()
3127                    .send(response.map_err(BlocksRequestError::Request));
3128            }
3129            WakeUpReason::NetworkEvent(service::Event::RequestResult {
3130                substream_id,
3131                peer_id,
3132                chain_id,
3133                response: service::RequestResult::GrandpaWarpSync(response),
3134            }) => {
3135                match &response {
3136                    Ok(response) => {
3137                        // TODO: print total bytes size
3138                        let decoded = response.decode();
3139                        log!(
3140                            &task.platform,
3141                            Debug,
3142                            "network",
3143                            "warp-sync-request-success",
3144                            chain = task.network[chain_id].log_name,
3145                            target = peer_id,
3146                            num_fragments = decoded.fragments.len(),
3147                            is_finished = ?decoded.is_finished,
3148                        );
3149                    }
3150                    Err(error) => {
3151                        log!(
3152                            &task.platform,
3153                            Debug,
3154                            "network",
3155                            "warp-sync-request-error",
3156                            chain = task.network[chain_id].log_name,
3157                            target = peer_id,
3158                            ?error,
3159                        );
3160                    }
3161                }
3162
3163                let _ = task
3164                    .grandpa_warp_sync_requests
3165                    .remove(&substream_id)
3166                    .unwrap()
3167                    .send(response.map_err(WarpSyncRequestError::Request));
3168            }
3169            WakeUpReason::NetworkEvent(service::Event::RequestResult {
3170                substream_id,
3171                peer_id,
3172                chain_id,
3173                response: service::RequestResult::StorageProof(response),
3174            }) => {
3175                match &response {
3176                    Ok(items) => {
3177                        let decoded = items.decode();
3178                        log!(
3179                            &task.platform,
3180                            Debug,
3181                            "network",
3182                            "storage-proof-request-success",
3183                            chain = task.network[chain_id].log_name,
3184                            target = peer_id,
3185                            total_size = BytesDisplay(u64::try_from(decoded.len()).unwrap()),
3186                        );
3187                    }
3188                    Err(error) => {
3189                        log!(
3190                            &task.platform,
3191                            Debug,
3192                            "network",
3193                            "storage-proof-request-error",
3194                            chain = task.network[chain_id].log_name,
3195                            target = peer_id,
3196                            ?error
3197                        );
3198                    }
3199                }
3200
3201                // Both regular storage proof and child storage proof use the same protocol,
3202                // so check both HashMaps for the request.
3203                if let Some(sender) = task.storage_proof_requests.remove(&substream_id) {
3204                    let _ = sender.send(response.map_err(StorageProofRequestError::Request));
3205                } else if let Some(sender) = task.child_storage_proof_requests.remove(&substream_id)
3206                {
3207                    let _ = sender.send(response.map_err(ChildStorageProofRequestError::Request));
3208                } else {
3209                    unreachable!()
3210                }
3211            }
3212            WakeUpReason::NetworkEvent(service::Event::RequestResult {
3213                substream_id,
3214                peer_id,
3215                chain_id,
3216                response: service::RequestResult::CallProof(response),
3217            }) => {
3218                match &response {
3219                    Ok(items) => {
3220                        let decoded = items.decode();
3221                        log!(
3222                            &task.platform,
3223                            Debug,
3224                            "network",
3225                            "call-proof-request-success",
3226                            chain = task.network[chain_id].log_name,
3227                            target = peer_id,
3228                            total_size = BytesDisplay(u64::try_from(decoded.len()).unwrap())
3229                        );
3230                    }
3231                    Err(error) => {
3232                        log!(
3233                            &task.platform,
3234                            Debug,
3235                            "network",
3236                            "call-proof-request-error",
3237                            chain = task.network[chain_id].log_name,
3238                            target = peer_id,
3239                            ?error
3240                        );
3241                    }
3242                }
3243
3244                let _ = task
3245                    .call_proof_requests
3246                    .remove(&substream_id)
3247                    .unwrap()
3248                    .send(response.map_err(CallProofRequestError::Request));
3249            }
3250            WakeUpReason::NetworkEvent(service::Event::RequestResult {
3251                peer_id: requestee_peer_id,
3252                chain_id,
3253                response: service::RequestResult::KademliaFindNode(Ok(nodes)),
3254                ..
3255            }) => {
3256                // Track whether this response taught us anything new. If so, we reset the
3257                // chain's discovery backoff so that the next FindNode round runs at the
3258                // initial 2s interval rather than continuing to back off — Kademlia is
3259                // making progress, walk the DHT eagerly.
3260                let mut any_new_peer = false;
3261                for (peer_id, mut addrs) in nodes {
3262                    // Make sure to not insert too many address for a single peer.
3263                    // While the .
3264                    if addrs.len() >= 10 {
3265                        addrs.truncate(10);
3266                    }
3267
3268                    let mut valid_addrs = Vec::with_capacity(addrs.len());
3269                    for addr in addrs {
3270                        match Multiaddr::from_bytes(addr) {
3271                            Ok(mut a) => {
3272                                if !pop_p2p_if_matches(&mut a, &peer_id) {
3273                                    let reason = DiscoveredAddressDropReason::PeerIdMismatch;
3274                                    log!(
3275                                        &task.platform,
3276                                        Debug,
3277                                        "network",
3278                                        "discovered-address-dropped",
3279                                        chain = &task.network[chain_id].log_name,
3280                                        reason,
3281                                        announced_peer_id = peer_id,
3282                                        addr = &a,
3283                                        obtained_from = requestee_peer_id
3284                                    );
3285                                    task.metrics.discovery_addresses_dropped.inc(reason);
3286                                    continue;
3287                                }
3288                                if platform::address_parse::multiaddr_to_address(&a)
3289                                    .ok()
3290                                    .map_or(false, |addr| {
3291                                        task.platform.supports_connection_type((&addr).into())
3292                                    })
3293                                {
3294                                    valid_addrs.push(a)
3295                                } else {
3296                                    let reason = DiscoveredAddressDropReason::NotSupported;
3297                                    log!(
3298                                        &task.platform,
3299                                        Debug,
3300                                        "network",
3301                                        "discovered-address-dropped",
3302                                        chain = &task.network[chain_id].log_name,
3303                                        reason,
3304                                        peer_id,
3305                                        addr = &a,
3306                                        obtained_from = requestee_peer_id
3307                                    );
3308                                    task.metrics.discovery_addresses_dropped.inc(reason);
3309                                }
3310                            }
3311                            Err((error, addr)) => {
3312                                let reason = DiscoveredAddressDropReason::Invalid;
3313                                log!(
3314                                    &task.platform,
3315                                    Debug,
3316                                    "network",
3317                                    "discovered-address-dropped",
3318                                    chain = &task.network[chain_id].log_name,
3319                                    reason,
3320                                    peer_id,
3321                                    error,
3322                                    addr = hex::encode(&addr),
3323                                    obtained_from = requestee_peer_id
3324                                );
3325                                task.metrics.discovery_addresses_dropped.inc(reason);
3326                            }
3327                        }
3328                    }
3329
3330                    if !valid_addrs.is_empty() {
3331                        // Note that we must call this function before `insert_address`,
3332                        // as documented in `basic_peering_strategy`.
3333                        let insert_outcome =
3334                            task.peering_strategy
3335                                .insert_chain_peer(chain_id, peer_id.clone(), 30); // TODO: constant
3336
3337                        if let basic_peering_strategy::InsertChainPeerResult::Inserted {
3338                            peer_removed,
3339                        } = insert_outcome
3340                        {
3341                            any_new_peer = true;
3342                            if let Some(peer_removed) = peer_removed {
3343                                log!(
3344                                    &task.platform,
3345                                    Debug,
3346                                    "network",
3347                                    "peer-purged-from-address-book",
3348                                    chain = &task.network[chain_id].log_name,
3349                                    peer_id = peer_removed,
3350                                );
3351                            }
3352
3353                            log!(
3354                                &task.platform,
3355                                Debug,
3356                                "network",
3357                                "peer-discovered",
3358                                chain = &task.network[chain_id].log_name,
3359                                peer_id,
3360                                addrs = ?valid_addrs.iter().map(|a| a.to_string()).collect::<Vec<_>>(), // TODO: better formatting?
3361                                obtained_from = requestee_peer_id
3362                            );
3363                        }
3364                    }
3365
3366                    for addr in valid_addrs {
3367                        let _insert_result =
3368                            task.peering_strategy
3369                                .insert_address(&peer_id, addr.into_bytes(), 10); // TODO: constant
3370                        debug_assert!(!matches!(
3371                            _insert_result,
3372                            basic_peering_strategy::InsertAddressResult::UnknownPeer
3373                        ));
3374                    }
3375                }
3376
3377                if any_new_peer {
3378                    task.network[chain_id].next_discovery_period = Duration::from_secs(2);
3379                }
3380            }
3381            WakeUpReason::NetworkEvent(service::Event::RequestResult {
3382                peer_id,
3383                chain_id,
3384                response: service::RequestResult::KademliaFindNode(Err(error)),
3385                ..
3386            }) => {
3387                log!(
3388                    &task.platform,
3389                    Debug,
3390                    "network",
3391                    "discovery-find-node-error",
3392                    chain = &task.network[chain_id].log_name,
3393                    ?error,
3394                    find_node_target = peer_id,
3395                );
3396
3397                // No error is printed if the request fails due to a benign networking error such
3398                // as an unresponsive peer.
3399                match error {
3400                    service::KademliaFindNodeError::RequestFailed(err)
3401                        if !err.is_protocol_error() => {}
3402
3403                    service::KademliaFindNodeError::RequestFailed(
3404                        service::RequestError::Substream(
3405                            connection::established::RequestError::ProtocolNotAvailable,
3406                        ),
3407                    ) => {
3408                        // TODO: remove this warning in a long time
3409                        log!(
3410                            &task.platform,
3411                            Warn,
3412                            "network",
3413                            format!(
3414                                "Problem during discovery on {}: protocol not available. \
3415                                This might indicate that the version of Substrate used by \
3416                                the chain doesn't include \
3417                                <https://github.com/paritytech/substrate/pull/12545>.",
3418                                &task.network[chain_id].log_name
3419                            )
3420                        );
3421                    }
3422                    _ => {
3423                        log!(
3424                            &task.platform,
3425                            Debug,
3426                            "network",
3427                            format!(
3428                                "Problem during discovery on {}: {}",
3429                                &task.network[chain_id].log_name, error
3430                            )
3431                        );
3432                    }
3433                }
3434            }
3435            WakeUpReason::NetworkEvent(service::Event::RequestResult { .. }) => {
3436                // We never start any other kind of requests.
3437                unreachable!()
3438            }
3439            WakeUpReason::NetworkEvent(service::Event::GossipInDesired {
3440                peer_id,
3441                chain_id,
3442                kind: service::GossipKind::ConsensusTransactions,
3443            }) => {
3444                // The networking state machine guarantees that `GossipInDesired`
3445                // can't happen if we are already opening an out slot, which we do
3446                // immediately.
3447                // TODO: add debug_assert! ^
3448
3449                // Statement links also appear among the opened undesired links and must
3450                // not use up the inbound block announces slots.
3451                if task
3452                    .network
3453                    .opened_gossip_undesired_by_chain(chain_id)
3454                    .filter(|(_, kind)| *kind == service::GossipKind::ConsensusTransactions)
3455                    .count()
3456                    < 4
3457                {
3458                    log!(
3459                        &task.platform,
3460                        Debug,
3461                        "network",
3462                        "gossip-in-request",
3463                        chain = &task.network[chain_id].log_name,
3464                        peer_id,
3465                        outcome = "accepted"
3466                    );
3467                    task.network
3468                        .gossip_open(
3469                            chain_id,
3470                            &peer_id,
3471                            service::GossipKind::ConsensusTransactions,
3472                        )
3473                        .unwrap();
3474                } else {
3475                    log!(
3476                        &task.platform,
3477                        Debug,
3478                        "network",
3479                        "gossip-in-request",
3480                        chain = &task.network[chain_id].log_name,
3481                        peer_id,
3482                        outcome = "rejected",
3483                    );
3484                    task.network
3485                        .gossip_close(
3486                            chain_id,
3487                            &peer_id,
3488                            service::GossipKind::ConsensusTransactions,
3489                        )
3490                        .unwrap();
3491                }
3492            }
3493            WakeUpReason::NetworkEvent(service::Event::GossipInDesiredCancel { .. }) => {
3494                // Can't happen as we already instantaneously accept or reject gossip in requests.
3495                unreachable!()
3496            }
3497            WakeUpReason::NetworkEvent(
3498                service::Event::GossipConnected {
3499                    kind: service::GossipKind::Statement,
3500                    ..
3501                }
3502                | service::Event::GossipOpenFailed {
3503                    kind: service::GossipKind::Statement,
3504                    ..
3505                }
3506                | service::Event::GossipDisconnected {
3507                    kind: service::GossipKind::Statement,
3508                    ..
3509                }
3510                | service::Event::GossipInDesired {
3511                    kind: service::GossipKind::Statement,
3512                    ..
3513                },
3514            ) => {
3515                // Can't happen as the statement kind reports through the `StatementProtocol*`
3516                // events, and inbound statement substreams are accepted or refused without
3517                // asking.
3518                unreachable!()
3519            }
3520            WakeUpReason::NetworkEvent(service::Event::IdentifyRequestIn {
3521                peer_id,
3522                substream_id,
3523            }) => {
3524                log!(
3525                    &task.platform,
3526                    Debug,
3527                    "network",
3528                    "identify-request-received",
3529                    peer_id,
3530                );
3531                task.network
3532                    .respond_identify(substream_id, &task.identify_agent_version);
3533            }
3534            WakeUpReason::NetworkEvent(service::Event::BlocksRequestIn { .. }) => unreachable!(),
3535            WakeUpReason::NetworkEvent(service::Event::RequestInCancel { .. }) => {
3536                // All incoming requests are immediately answered.
3537                unreachable!()
3538            }
3539            WakeUpReason::NetworkEvent(service::Event::GrandpaNeighborPacket {
3540                chain_id,
3541                peer_id,
3542                state,
3543            }) => {
3544                log!(
3545                    &task.platform,
3546                    Debug,
3547                    "network",
3548                    "grandpa-neighbor-packet-received",
3549                    chain = &task.network[chain_id].log_name,
3550                    peer_id,
3551                    round_number = state.round_number,
3552                    set_id = state.set_id,
3553                    commit_finalized_height = state.commit_finalized_height,
3554                );
3555
3556                task.open_gossip_links
3557                    .get_mut(&(chain_id, peer_id.clone()))
3558                    .unwrap()
3559                    .finalized_block_height = Some(state.commit_finalized_height);
3560
3561                debug_assert!(task.events_pending_send.is_empty());
3562                task.events_pending_send.push_back((
3563                    chain_id,
3564                    Event::GrandpaNeighborPacket {
3565                        peer_id,
3566                        finalized_block_height: state.commit_finalized_height,
3567                    },
3568                ));
3569            }
3570            WakeUpReason::NetworkEvent(service::Event::GrandpaCommitMessage {
3571                chain_id,
3572                peer_id,
3573                message,
3574            }) => {
3575                log!(
3576                    &task.platform,
3577                    Debug,
3578                    "network",
3579                    "grandpa-commit-message-received",
3580                    chain = &task.network[chain_id].log_name,
3581                    peer_id,
3582                    target_block_hash = HashDisplay(message.decode().target_hash),
3583                );
3584
3585                debug_assert!(task.events_pending_send.is_empty());
3586                task.events_pending_send
3587                    .push_back((chain_id, Event::GrandpaCommitMessage { peer_id, message }));
3588            }
3589            WakeUpReason::NetworkEvent(service::Event::StatementsNotification {
3590                chain_id,
3591                peer_id,
3592                statements,
3593            }) => {
3594                debug_assert!(task.statement_event_pending_send.is_none());
3595
3596                if statements.is_empty() {
3597                    continue;
3598                }
3599
3600                task.statement_event_pending_send = Some((
3601                    chain_id,
3602                    StatementEvent::StatementsNotification {
3603                        peer_id,
3604                        statements,
3605                    },
3606                ));
3607            }
3608            WakeUpReason::NetworkEvent(service::Event::StatementProtocolConnected {
3609                peer_id,
3610                chain_id,
3611                version,
3612            }) => {
3613                log!(
3614                    &task.platform,
3615                    Trace,
3616                    "network",
3617                    "statement-protocol-open-success",
3618                    chain = &task.network[chain_id].log_name,
3619                    peer_id,
3620                    ?version,
3621                );
3622
3623                if matches!(version, codec::StatementProtocolVersion::V2) {
3624                    task.v2_statement_peers
3625                        .entry(chain_id)
3626                        .or_insert_with(|| {
3627                            HashSet::with_capacity_and_hasher(16, Default::default())
3628                        })
3629                        .insert(peer_id.clone());
3630                    if let Some(filter) = task.current_affinity_filter.get(&chain_id) {
3631                        if let Err(
3632                            SendTopicAffinityError::NoConnection
3633                            | SendTopicAffinityError::ProtocolV1,
3634                        ) = task.network.send_topic_affinity(&peer_id, chain_id, filter)
3635                        {
3636                            task.v2_statement_peers
3637                                .get_mut(&chain_id)
3638                                .unwrap()
3639                                .remove(&peer_id);
3640                        }
3641                    }
3642                }
3643            }
3644            WakeUpReason::NetworkEvent(service::Event::StatementProtocolOpenFailed {
3645                peer_id,
3646                chain_id,
3647                error,
3648            }) => {
3649                log!(
3650                    &task.platform,
3651                    Debug,
3652                    "network",
3653                    "statement-protocol-open-error",
3654                    chain = &task.network[chain_id].log_name,
3655                    peer_id,
3656                    ?error,
3657                );
3658
3659                // A peer still desired under the statement kind would be opened again at
3660                // once. Whoever marks a peer as desired under that kind decides whether to
3661                // retry.
3662                task.network.gossip_remove_desired(
3663                    chain_id,
3664                    &peer_id,
3665                    service::GossipKind::Statement,
3666                );
3667            }
3668            WakeUpReason::NetworkEvent(service::Event::StatementProtocolDisconnected {
3669                peer_id,
3670                chain_id,
3671            }) => {
3672                log!(
3673                    &task.platform,
3674                    Debug,
3675                    "network",
3676                    "statement-protocol-closed",
3677                    chain = &task.network[chain_id].log_name,
3678                    peer_id,
3679                );
3680
3681                // A peer still desired under the statement kind would be opened again at
3682                // once, as after an open failure.
3683                task.network.gossip_remove_desired(
3684                    chain_id,
3685                    &peer_id,
3686                    service::GossipKind::Statement,
3687                );
3688
3689                if let Some(peers) = task.v2_statement_peers.get_mut(&chain_id) {
3690                    peers.remove(&peer_id);
3691                }
3692            }
3693            // TODO: we don't filter outbound statements yet
3694            WakeUpReason::NetworkEvent(service::Event::StatementTopicAffinityReceived {
3695                ..
3696            }) => {}
3697            WakeUpReason::NetworkEvent(service::Event::ProtocolError { peer_id, error }) => {
3698                // TODO: handle properly?
3699                log!(
3700                    &task.platform,
3701                    Warn,
3702                    "network",
3703                    "protocol-error",
3704                    peer_id,
3705                    ?error
3706                );
3707
3708                // TODO: disconnect peer
3709            }
3710            WakeUpReason::CanAssignSlot(peer_id, chain_id) => {
3711                task.peering_strategy.assign_slot(&chain_id, &peer_id);
3712
3713                log!(
3714                    &task.platform,
3715                    Debug,
3716                    "network",
3717                    "slot-assigned",
3718                    chain = &task.network[chain_id].log_name,
3719                    peer_id
3720                );
3721
3722                task.network.gossip_insert_desired(
3723                    chain_id,
3724                    peer_id,
3725                    service::GossipKind::ConsensusTransactions,
3726                );
3727            }
3728            WakeUpReason::CanAssignBitswapSlot(peer_id) => {
3729                task.bitswap_peering_strategy.assign_slot(&peer_id).unwrap();
3730
3731                log!(
3732                    &task.platform,
3733                    Debug,
3734                    "network",
3735                    "bitswap-slot-assigned",
3736                    peer_id
3737                );
3738
3739                task.network.bitswap_insert_desired(peer_id);
3740            }
3741            WakeUpReason::NextRecentConnectionRestore => {
3742                task.num_recent_connection_opening =
3743                    task.num_recent_connection_opening.saturating_sub(1);
3744            }
3745            WakeUpReason::CanStartConnect(expected_peer_id) => {
3746                let Some(multiaddr) = task
3747                    .peering_strategy
3748                    .pick_address_and_add_connection(&expected_peer_id)
3749                else {
3750                    // There is no address for that peer in the address book.
3751                    task.network.gossip_remove_desired_all(
3752                        &expected_peer_id,
3753                        service::GossipKind::ConsensusTransactions,
3754                    );
3755                    task.network.gossip_remove_desired_all(
3756                        &expected_peer_id,
3757                        service::GossipKind::Statement,
3758                    );
3759                    let ban_duration = Duration::from_secs(10);
3760                    for (&chain_id, what_happened) in task.peering_strategy.unassign_slots_and_ban(
3761                        &expected_peer_id,
3762                        task.platform.now() + ban_duration,
3763                    ) {
3764                        if matches!(
3765                            what_happened,
3766                            basic_peering_strategy::UnassignSlotsAndBan::Banned { had_slot: true }
3767                        ) {
3768                            log!(
3769                                &task.platform,
3770                                Debug,
3771                                "network",
3772                                "slot-unassigned",
3773                                chain = &task.network[chain_id].log_name,
3774                                peer_id = expected_peer_id,
3775                                ?ban_duration,
3776                                reason = "no-address"
3777                            );
3778                        }
3779                    }
3780                    continue;
3781                };
3782
3783                let multiaddr = match multiaddr::Multiaddr::from_bytes(multiaddr.to_owned()) {
3784                    Ok(a) => a,
3785                    Err((multiaddr::FromBytesError, addr)) => {
3786                        // Address is in an invalid format.
3787                        let _was_in = task
3788                            .peering_strategy
3789                            .decrease_address_connections_and_remove_if_zero(
3790                                &expected_peer_id,
3791                                &addr,
3792                            );
3793                        debug_assert!(_was_in.is_ok());
3794                        continue;
3795                    }
3796                };
3797
3798                let address = address_parse::multiaddr_to_address(&multiaddr)
3799                    .ok()
3800                    .filter(|addr| {
3801                        task.platform.supports_connection_type(match &addr {
3802                            address_parse::AddressOrMultiStreamAddress::Address(addr) => {
3803                                From::from(addr)
3804                            }
3805                            address_parse::AddressOrMultiStreamAddress::MultiStreamAddress(
3806                                addr,
3807                            ) => From::from(addr),
3808                        })
3809                    });
3810
3811                let Some(address) = address else {
3812                    // Address is in an invalid format or isn't supported by the platform.
3813                    let _was_in = task
3814                        .peering_strategy
3815                        .decrease_address_connections_and_remove_if_zero(
3816                            &expected_peer_id,
3817                            multiaddr.as_ref(),
3818                        );
3819                    debug_assert!(_was_in.is_ok());
3820                    continue;
3821                };
3822
3823                // Each connection has its own individual Noise key.
3824                let noise_key = {
3825                    let mut noise_static_key = zeroize::Zeroizing::new([0u8; 32]);
3826                    task.platform.fill_random_bytes(&mut *noise_static_key);
3827                    let mut libp2p_key = zeroize::Zeroizing::new([0u8; 32]);
3828                    task.platform.fill_random_bytes(&mut *libp2p_key);
3829                    connection::NoiseKey::new(&libp2p_key, &noise_static_key)
3830                };
3831
3832                log!(
3833                    &task.platform,
3834                    Debug,
3835                    "network",
3836                    "connection-started",
3837                    expected_peer_id,
3838                    remote_addr = multiaddr,
3839                    local_peer_id =
3840                        peer_id::PublicKey::Ed25519(*noise_key.libp2p_public_ed25519_key())
3841                            .into_peer_id(),
3842                );
3843
3844                task.num_recent_connection_opening += 1;
3845                task.metrics.connections_dialed.inc();
3846
3847                let (coordinator_to_connection_tx, coordinator_to_connection_rx) =
3848                    async_channel::bounded(8);
3849                let task_name = format!("connection-{}", multiaddr);
3850
3851                match address {
3852                    address_parse::AddressOrMultiStreamAddress::Address(address) => {
3853                        // As documented in the `PlatformRef` trait, `connect_stream` must
3854                        // return as soon as possible.
3855                        let connection = task.platform.connect_stream(address).await;
3856
3857                        let (connection_id, connection_task) =
3858                            task.network.add_single_stream_connection(
3859                                task.platform.now(),
3860                                service::SingleStreamHandshakeKind::MultistreamSelectNoiseYamux {
3861                                    is_initiator: true,
3862                                    noise_key: &noise_key,
3863                                },
3864                                multiaddr.clone().into_bytes(),
3865                                Some(expected_peer_id.clone()),
3866                                coordinator_to_connection_tx,
3867                            );
3868
3869                        task.platform.spawn_task(
3870                            task_name.into(),
3871                            tasks::single_stream_connection_task::<TPlat>(
3872                                connection,
3873                                multiaddr.to_string(),
3874                                task.platform.clone(),
3875                                connection_id,
3876                                connection_task,
3877                                coordinator_to_connection_rx,
3878                                task.tasks_messages_tx.clone(),
3879                            ),
3880                        );
3881                    }
3882                    address_parse::AddressOrMultiStreamAddress::MultiStreamAddress(address) => {
3883                        let remote_certificate_sha256 = match &address {
3884                            platform::MultiStreamAddress::WebRtc {
3885                                remote_certificate_sha256,
3886                                ..
3887                            }
3888                            | platform::MultiStreamAddress::WebRtcDns {
3889                                remote_certificate_sha256,
3890                                ..
3891                            } => *remote_certificate_sha256,
3892                        };
3893
3894                        // We need to know the local TLS certificate in order to insert the
3895                        // connection, and as such we need to call `connect_multistream` here.
3896                        // As documented in the `PlatformRef` trait, `connect_multistream` must
3897                        // return as soon as possible.
3898                        let connection = task.platform.connect_multistream(address).await;
3899
3900                        // Convert the SHA256 hashes into multihashes.
3901                        let local_tls_certificate_multihash = [18u8, 32]
3902                            .into_iter()
3903                            .chain(connection.local_tls_certificate_sha256.into_iter())
3904                            .collect();
3905                        let remote_tls_certificate_multihash = [18u8, 32]
3906                            .into_iter()
3907                            .chain(remote_certificate_sha256.iter().copied())
3908                            .collect();
3909
3910                        let (connection_id, connection_task) =
3911                            task.network.add_multi_stream_connection(
3912                                task.platform.now(),
3913                                service::MultiStreamHandshakeKind::WebRtc {
3914                                    is_initiator: true,
3915                                    local_tls_certificate_multihash,
3916                                    remote_tls_certificate_multihash,
3917                                    noise_key: &noise_key,
3918                                },
3919                                multiaddr.clone().into_bytes(),
3920                                Some(expected_peer_id.clone()),
3921                                coordinator_to_connection_tx,
3922                            );
3923
3924                        task.platform.spawn_task(
3925                            task_name.into(),
3926                            tasks::webrtc_multi_stream_connection_task::<TPlat>(
3927                                connection.connection,
3928                                multiaddr.to_string(),
3929                                task.platform.clone(),
3930                                connection_id,
3931                                connection_task,
3932                                coordinator_to_connection_rx,
3933                                task.tasks_messages_tx.clone(),
3934                            ),
3935                        );
3936                    }
3937                }
3938            }
3939            WakeUpReason::CanOpenGossip(peer_id, chain_id, kind) => {
3940                task.network.gossip_open(chain_id, &peer_id, kind).unwrap();
3941
3942                log!(
3943                    &task.platform,
3944                    Debug,
3945                    "network",
3946                    "gossip-open-start",
3947                    chain = &task.network[chain_id].log_name,
3948                    peer_id,
3949                    ?kind,
3950                );
3951            }
3952            WakeUpReason::CanOpenBitswap(peer_id) => {
3953                task.network.bitswap_open(&peer_id).unwrap();
3954
3955                log!(
3956                    &task.platform,
3957                    Debug,
3958                    "network",
3959                    "bitswap-open-start",
3960                    peer_id
3961                );
3962            }
3963            WakeUpReason::MessageToConnection {
3964                connection_id,
3965                message,
3966            } => {
3967                // Note that it is critical for the sending to not take too long here, in order to
3968                // not block the process of the network service.
3969                // In particular, if sending the message to the connection is blocked due to
3970                // sending a message on the connection-to-coordinator channel, this will result
3971                // in a deadlock.
3972                // For this reason, the connection task is always ready to immediately accept a
3973                // message on the coordinator-to-connection channel.
3974                let _send_result = task.network[connection_id].send(message).await;
3975                debug_assert!(_send_result.is_ok());
3976            }
3977        }
3978    }
3979}
3980
3981/// Starts find-node requests against `candidates` until `max` have started, each with a fresh
3982/// random target key, and returns the `(request_target, requested_peer_id)` of each.
3983///
3984/// A `kademlia_capable_peers` candidate may have no usable connection (the flag outlives the
3985/// connection it was learned on); such a peer fails with `NoConnection` and is skipped without
3986/// counting towards `max`.
3987fn dispatch_find_node_requests<TChain, TConn, TNow>(
3988    network: &mut service::ChainNetwork<TChain, TConn, TNow>,
3989    randomness: &mut impl rand_chacha::rand_core::RngCore,
3990    chain_id: service::ChainId,
3991    candidates: &[PeerId],
3992    max: usize,
3993) -> Vec<(PeerId, PeerId)>
3994where
3995    TNow: Clone
3996        + core::ops::Add<Duration, Output = TNow>
3997        + core::ops::Sub<TNow, Output = Duration>
3998        + Ord,
3999{
4000    let mut started = Vec::with_capacity(max);
4001
4002    for target in candidates {
4003        if started.len() >= max {
4004            break;
4005        }
4006
4007        let random_peer_id = {
4008            let mut pub_key = [0; 32];
4009            randomness.fill_bytes(&mut pub_key);
4010            PeerId::from_public_key(&peer_id::PublicKey::Ed25519(pub_key))
4011        };
4012
4013        match network.start_kademlia_find_node_request(
4014            target,
4015            chain_id,
4016            &random_peer_id,
4017            Duration::from_secs(20),
4018        ) {
4019            Ok(_) => started.push((target.clone(), random_peer_id)),
4020            Err(service::StartRequestError::NoConnection) => {}
4021        }
4022    }
4023
4024    started
4025}
4026
4027/// Pops a trailing `/p2p/<peer_id>` from `addr` if it matches `expected_peer`. Returns `false`
4028/// (caller must discard the address) on mismatch.
4029fn pop_p2p_if_matches(
4030    addr: &mut smoldot::libp2p::multiaddr::Multiaddr,
4031    expected_peer: &smoldot::libp2p::peer_id::PeerId,
4032) -> bool {
4033    use smoldot::libp2p::multiaddr::Protocol;
4034    match addr.iter().last() {
4035        Some(Protocol::P2p(mh)) => {
4036            if mh.into_bytes() == expected_peer.as_bytes() {
4037                addr.pop();
4038                true
4039            } else {
4040                false
4041            }
4042        }
4043        _ => true,
4044    }
4045}
4046
4047#[cfg(test)]
4048mod tests {
4049    use super::{Role, dispatch_find_node_requests, pop_p2p_if_matches, service};
4050    use core::time::Duration;
4051    use rand_chacha::rand_core::SeedableRng as _;
4052    use smoldot::libp2p::{multiaddr::Multiaddr, peer_id::PeerId};
4053
4054    // Two distinct, valid PeerIds. The first is reused from existing smoldot tests in
4055    // `lib/src/libp2p/multiaddr.rs:629`; the second is the bootnode peer-id observed in the
4056    // test environment that motivated this change.
4057    const PEER_A: &str = "12D3KooWDpJ7As7BWAwRMfu1VU2WCqNjvq387JEYKDBj4kx6nXTN";
4058    const PEER_B: &str = "12D3KooWQk1yQtG1YugyKjiQf6KNk8VjGGAT5xy1FWcnRKN4yXYJ";
4059
4060    fn peer(s: &str) -> PeerId {
4061        PeerId::from_bytes(bs58::decode(s).into_vec().unwrap()).unwrap()
4062    }
4063
4064    #[test]
4065    fn no_suffix_passes_through_unchanged() {
4066        let mut addr: Multiaddr = "/ip4/127.0.0.1/tcp/30333/ws".parse().unwrap();
4067        let before = addr.clone();
4068        assert!(pop_p2p_if_matches(&mut addr, &peer(PEER_A)));
4069        assert_eq!(addr, before);
4070    }
4071
4072    #[test]
4073    fn matching_suffix_is_stripped() {
4074        let mut addr: Multiaddr = format!("/ip4/127.0.0.1/tcp/30333/ws/p2p/{PEER_A}")
4075            .parse()
4076            .unwrap();
4077        assert!(pop_p2p_if_matches(&mut addr, &peer(PEER_A)));
4078        let expected: Multiaddr = "/ip4/127.0.0.1/tcp/30333/ws".parse().unwrap();
4079        assert_eq!(addr, expected);
4080    }
4081
4082    #[test]
4083    fn mismatched_suffix_rejects_and_keeps_addr() {
4084        let original: Multiaddr = format!("/ip4/127.0.0.1/tcp/30333/ws/p2p/{PEER_A}")
4085            .parse()
4086            .unwrap();
4087        let mut addr = original.clone();
4088        assert!(!pop_p2p_if_matches(&mut addr, &peer(PEER_B)));
4089        assert_eq!(addr, original);
4090    }
4091
4092    fn empty_network() -> (service::ChainNetwork<(), (), Duration>, service::ChainId) {
4093        let mut network = service::ChainNetwork::new(service::Config {
4094            connections_capacity: 8,
4095            chains_capacity: 1,
4096            randomness_seed: [0; 32],
4097            handshake_timeout: Duration::from_secs(10),
4098        });
4099        let chain_id = network
4100            .add_chain(service::ChainConfig {
4101                user_data: (),
4102                genesis_hash: [0; 32],
4103                fork_id: None,
4104                block_number_bytes: 4,
4105                grandpa_protocol_config: None,
4106                allow_inbound_block_requests: false,
4107                best_hash: [0; 32],
4108                best_number: 0,
4109                role: Role::Light,
4110                enable_statement_protocol: false,
4111            })
4112            .unwrap();
4113        (network, chain_id)
4114    }
4115
4116    // With no connections every candidate returns `NoConnection`, so all are skipped and no
4117    // request is started. The dispatch loop must not treat that as unreachable.
4118    #[test]
4119    fn dispatch_skips_unreachable_candidates() {
4120        let (mut network, chain_id) = empty_network();
4121        let mut randomness = rand_chacha::ChaCha20Rng::from_seed([7; 32]);
4122
4123        let candidates = [peer(PEER_A), peer(PEER_B)];
4124        let started =
4125            dispatch_find_node_requests(&mut network, &mut randomness, chain_id, &candidates, 3);
4126
4127        assert!(started.is_empty());
4128    }
4129}