Skip to main content

zebra_network/peer_set/
initialize.rs

1//! A peer set whose size is dynamically determined by resource constraints.
2//!
3//! The [`PeerSet`] implementation is adapted from the one in [tower::Balance][tower-balance].
4//!
5//! [tower-balance]: https://github.com/tower-rs/tower/tree/master/tower/src/balance
6
7use std::{
8    collections::{BTreeMap, HashMap, HashSet},
9    convert::Infallible,
10    net::{IpAddr, SocketAddr},
11    pin::Pin,
12    sync::Arc,
13    time::Duration,
14};
15
16use futures::{
17    future::{self, FutureExt},
18    sink::SinkExt,
19    stream::{FuturesUnordered, StreamExt},
20    Future, TryFutureExt,
21};
22use indexmap::IndexMap;
23use rand::seq::SliceRandom;
24use tokio::{
25    net::{TcpListener, TcpStream},
26    sync::{broadcast, mpsc, watch},
27    time::{sleep, Instant},
28};
29use tokio_stream::wrappers::IntervalStream;
30use tower::{
31    buffer::Buffer, discover::Change, layer::Layer, util::BoxService, Service, ServiceExt,
32};
33use tracing_futures::Instrument;
34
35use zebra_chain::{chain_tip::ChainTip, diagnostic::task::WaitForPanics, parameters::Network};
36
37use crate::{
38    address_book_updater::{AddressBookUpdater, MIN_CHANNEL_SIZE},
39    connection_metrics::{
40        network_kind_label, record_connection_attempt_finished, record_connection_attempt_started,
41        record_inbound_connection_rejected, ConnectionDirection,
42    },
43    constants,
44    meta_addr::{MetaAddr, MetaAddrChange},
45    peer::{
46        self, address_is_valid_for_inbound_listeners, HandshakeRequest, MinimumPeerVersion,
47        OutboundConnectorRequest, PeerPreference,
48    },
49    peer_cache_updater::peer_cache_updater,
50    peer_set::{set::MorePeers, ActiveConnectionCounter, CandidateSet, ConnectionTracker, PeerSet},
51    protocol::external::{canonical_peer_addr, canonical_socket_addr},
52    AddressBook, BoxError, Config, PeerSocketAddr, Request, Response,
53};
54
55#[cfg(test)]
56mod tests;
57
58mod recent_by_ip;
59
60/// A successful outbound peer connection attempt or inbound connection handshake.
61///
62/// The [`Handshake`](peer::Handshake) service returns a [`Result`]. Only successful connections
63/// should be sent on the channel. Errors should be logged or ignored.
64///
65/// We don't allow any errors in this type, because:
66/// - The connection limits don't include failed connections
67/// - tower::Discover interprets an error as stream termination
68type DiscoveredPeer = (PeerSocketAddr, peer::Client);
69
70/// Initialize a peer set, using a network `config`, `inbound_service`,
71/// and `latest_chain_tip`.
72///
73/// The peer set abstracts away peer management to provide a
74/// [`tower::Service`] representing "the network" that load-balances requests
75/// over available peers.  The peer set automatically crawls the network to
76/// find more peer addresses and opportunistically connects to new peers.
77///
78/// Each peer connection's message handling is isolated from other
79/// connections, unlike in `zcashd`.  The peer connection first attempts to
80/// interpret inbound messages as part of a response to a previously-issued
81/// request.  Otherwise, inbound messages are interpreted as requests and sent
82/// to the supplied `inbound_service`.
83///
84/// Wrapping the `inbound_service` in [`tower::load_shed`] middleware will
85/// cause the peer set to shrink when the inbound service is unable to keep up
86/// with the volume of inbound requests.
87///
88/// Use [`NoChainTip`][1] to explicitly provide no chain tip receiver.
89///
90/// In addition to returning a service for outbound requests, this method
91/// returns a shared [`AddressBook`] updated with last-seen timestamps for
92/// connected peers. The shared address book should be accessed using a
93/// [blocking thread](https://docs.rs/tokio/1.15.0/tokio/task/index.html#blocking-and-yielding),
94/// to avoid async task deadlocks.
95///
96/// # Panics
97///
98/// If `config.config.peerset_initial_target_size` is zero.
99/// (zebra-network expects to be able to connect to at least one peer.)
100///
101/// [1]: zebra_chain::chain_tip::NoChainTip
102pub async fn init<S, C>(
103    config: Config,
104    inbound_service: S,
105    latest_chain_tip: C,
106    user_agent: String,
107) -> (
108    Buffer<BoxService<Request, Response, BoxError>, Request>,
109    Arc<std::sync::Mutex<AddressBook>>,
110    mpsc::Sender<(PeerSocketAddr, u32)>,
111)
112where
113    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + Sync + 'static,
114    S::Future: Send + 'static,
115    C: ChainTip + Clone + Send + Sync + 'static,
116{
117    init_with_block_gossip_peer_ips(
118        config,
119        inbound_service,
120        latest_chain_tip,
121        user_agent,
122        Vec::new(),
123    )
124    .await
125}
126
127/// Initialize a peer set like [`init`], additionally pinning block inventory
128/// broadcasts to the inbound peers at `block_gossip_peer_ips`.
129///
130/// Inbound peers connecting from these IP addresses always receive block
131/// inventory broadcasts, and one inbound connection slot is reserved for them.
132/// This is used by zcashd-compat mode, where a zcashd wallet sidecar makes a
133/// single P2P connection to this node and must reliably learn about new blocks.
134pub async fn init_with_block_gossip_peer_ips<S, C>(
135    config: Config,
136    inbound_service: S,
137    latest_chain_tip: C,
138    user_agent: String,
139    block_gossip_peer_ips: Vec<IpAddr>,
140) -> (
141    Buffer<BoxService<Request, Response, BoxError>, Request>,
142    Arc<std::sync::Mutex<AddressBook>>,
143    mpsc::Sender<(PeerSocketAddr, u32)>,
144)
145where
146    S: Service<Request, Response = Response, Error = BoxError> + Clone + Send + Sync + 'static,
147    S::Future: Send + 'static,
148    C: ChainTip + Clone + Send + Sync + 'static,
149{
150    let (tcp_listener, listen_addr) = open_listener(&config.clone()).await;
151
152    let (
153        address_book,
154        bans_receiver,
155        address_book_updater,
156        address_metrics,
157        address_book_updater_guard,
158    ) = AddressBookUpdater::spawn(&config, listen_addr);
159
160    let (misbehavior_tx, mut misbehavior_rx) = mpsc::channel(
161        // Leave enough room for a misbehaviour update on every peer connection
162        // before the channel is drained.
163        config
164            .peerset_total_connection_limit()
165            .max(MIN_CHANNEL_SIZE),
166    );
167
168    let misbehaviour_updater = address_book_updater.clone();
169    tokio::spawn(
170        async move {
171            let mut misbehaviors: HashMap<PeerSocketAddr, u32> = HashMap::new();
172            // Batch misbehaviour updates so peers can't keep the address book mutex locked
173            // by repeatedly sending invalid blocks or transactions.
174            let mut flush_timer =
175                IntervalStream::new(tokio::time::interval(constants::MISBEHAVIOR_FLUSH_INTERVAL));
176
177            loop {
178                tokio::select! {
179                    msg = misbehavior_rx.recv() => match msg {
180                        Some((peer_addr, score_increment)) => *misbehaviors
181                            .entry(peer_addr)
182                            .or_default()
183                            += score_increment,
184                        None => break,
185                    },
186
187                    _ = flush_timer.next() => {
188                        for (addr, score_increment) in misbehaviors.drain() {
189                            let _ = misbehaviour_updater
190                                .send(MetaAddr::new_misbehavior(addr, score_increment))
191                                .await;
192                        }
193                    },
194                };
195            }
196
197            tracing::warn!("exiting misbehavior update batch task");
198        }
199        .in_current_span(),
200    );
201
202    // Create a broadcast channel for peer inventory advertisements.
203    // If it reaches capacity, this channel drops older inventory advertisements.
204    //
205    // When Zebra is at the chain tip with an up-to-date mempool,
206    // we expect to have at most 1 new transaction per connected peer,
207    // and 1-2 new blocks across the entire network.
208    // (The block syncer and mempool crawler handle bulk fetches of blocks and transactions.)
209    let (inv_sender, inv_receiver) = broadcast::channel(config.peerset_total_connection_limit());
210
211    // Construct services that handle inbound handshakes and perform outbound
212    // handshakes. These use the same handshake service internally to detect
213    // self-connection attempts. Both are decorated with a tower TimeoutLayer to
214    // enforce timeouts as specified in the Config.
215    let (listen_handshaker, outbound_connector) = {
216        use tower::timeout::TimeoutLayer;
217        let hs_timeout = TimeoutLayer::new(constants::HANDSHAKE_TIMEOUT);
218        use crate::protocol::external::types::PeerServices;
219        let hs = peer::Handshake::builder()
220            .with_config(config.clone())
221            .with_inbound_service(inbound_service)
222            .with_inventory_collector(inv_sender)
223            .with_address_book_updater(address_book_updater.clone())
224            .with_advertised_services(PeerServices::NODE_NETWORK)
225            .with_user_agent(user_agent)
226            .with_latest_chain_tip(latest_chain_tip.clone())
227            .want_transactions(true)
228            .finish()
229            .expect("configured all required parameters");
230        (
231            hs_timeout.layer(hs.clone()),
232            hs_timeout.layer(peer::Connector::new(hs)),
233        )
234    };
235
236    // Create an mpsc channel for peer changes,
237    // based on the maximum number of inbound and outbound peers.
238    //
239    // The connection limit does not apply to errors,
240    // so they need to be handled before sending to this channel.
241    let (peerset_tx, peerset_rx) =
242        futures::channel::mpsc::channel::<DiscoveredPeer>(config.peerset_total_connection_limit());
243
244    let discovered_peers = peerset_rx.map(|(address, client)| {
245        Result::<_, Infallible>::Ok(Change::Insert(address, client.into()))
246    });
247
248    // Create an mpsc channel for peerset demand signaling,
249    // based on the maximum number of outbound peers.
250    let (mut demand_tx, demand_rx) =
251        futures::channel::mpsc::channel::<MorePeers>(config.peerset_outbound_connection_limit());
252
253    // Create a oneshot to send background task JoinHandles to the peer set
254    let (handle_tx, handle_rx) = tokio::sync::oneshot::channel();
255
256    // Connect the rx end to a PeerSet, wrapping new peers in load instruments.
257    let peer_set = PeerSet::new(
258        &config,
259        block_gossip_peer_ips.clone(),
260        discovered_peers,
261        demand_tx.clone(),
262        handle_rx,
263        inv_receiver,
264        bans_receiver.clone(),
265        address_metrics,
266        MinimumPeerVersion::new(latest_chain_tip, &config.network),
267        None,
268    );
269    let peer_set = Buffer::new(BoxService::new(peer_set), constants::PEERSET_BUFFER_SIZE);
270
271    // Connect peerset_tx to the 3 peer sources:
272    //
273    // 1. Incoming peer connections, via a listener.
274    let listen_fut = accept_inbound_connections(
275        config.clone(),
276        tcp_listener,
277        constants::MIN_INBOUND_PEER_CONNECTION_INTERVAL,
278        listen_handshaker,
279        peerset_tx.clone(),
280        bans_receiver,
281        block_gossip_peer_ips,
282    );
283    let listen_guard = tokio::spawn(listen_fut.in_current_span());
284
285    // 2. Initial peers, specified in the config and cached on disk.
286    let initial_peers_fut = add_initial_peers(
287        config.clone(),
288        outbound_connector.clone(),
289        peerset_tx.clone(),
290        address_book_updater.clone(),
291    );
292    let initial_peers_join = tokio::spawn(initial_peers_fut.in_current_span());
293
294    // 3. Outgoing peers we connect to in response to load.
295    let mut candidates = CandidateSet::new(address_book.clone(), peer_set.clone());
296
297    // Wait for the initial seed peer count
298    let mut active_outbound_connections = initial_peers_join
299        .wait_for_panics()
300        .await
301        .expect("unexpected error connecting to initial peers");
302    let active_initial_peer_count = active_outbound_connections.update_count();
303
304    // We need to await candidates.update() here,
305    // because zcashd rate-limits `addr`/`addrv2` messages per connection,
306    // and if we only have one initial peer,
307    // we need to ensure that its `Response::Addr` is used by the crawler.
308    //
309    // TODO: this might not be needed after we added the Connection peer address cache,
310    //       try removing it in a future release?
311    info!(
312        ?active_initial_peer_count,
313        "sending initial request for peers"
314    );
315    let _ = candidates.update_initial(active_initial_peer_count).await;
316
317    // Compute remaining connections to open.
318    let demand_count = config
319        .peerset_initial_target_size
320        .saturating_sub(active_outbound_connections.update_count());
321
322    for _ in 0..demand_count {
323        let _ = demand_tx.try_send(MorePeers);
324    }
325
326    // Start the peer crawler
327    let crawl_fut = crawl_and_dial(
328        config.clone(),
329        demand_tx,
330        demand_rx,
331        candidates,
332        outbound_connector,
333        peerset_tx,
334        active_outbound_connections,
335        address_book_updater,
336    );
337    let crawl_guard = tokio::spawn(crawl_fut.in_current_span());
338
339    // Start the peer disk cache updater
340    let peer_cache_updater_fut = peer_cache_updater(config, address_book.clone());
341    let peer_cache_updater_guard = tokio::spawn(peer_cache_updater_fut.in_current_span());
342
343    handle_tx
344        .send(vec![
345            listen_guard,
346            crawl_guard,
347            address_book_updater_guard,
348            peer_cache_updater_guard,
349        ])
350        .unwrap();
351
352    (peer_set, address_book, misbehavior_tx)
353}
354
355/// Use the provided `outbound_connector` to connect to the configured DNS seeder and
356/// disk cache initial peers, then send the resulting peer connections over `peerset_tx`.
357///
358/// Also sends every initial peer address to the `address_book_updater`.
359#[instrument(skip(config, outbound_connector, peerset_tx, address_book_updater))]
360async fn add_initial_peers<S>(
361    config: Config,
362    outbound_connector: S,
363    mut peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
364    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
365) -> Result<ActiveConnectionCounter, BoxError>
366where
367    S: Service<
368            OutboundConnectorRequest,
369            Response = (PeerSocketAddr, peer::Client),
370            Error = BoxError,
371        > + Clone
372        + Send
373        + 'static,
374    S::Future: Send + 'static,
375{
376    let initial_peers = limit_initial_peers(&config, address_book_updater).await;
377
378    let mut handshake_success_total: usize = 0;
379    let mut handshake_error_total: usize = 0;
380
381    let mut active_outbound_connections = ActiveConnectionCounter::new_counter_with(
382        config.peerset_outbound_connection_limit(),
383        "Outbound Connections",
384    );
385
386    // TODO: update when we add Tor peers or other kinds of addresses.
387    let ipv4_peer_count = initial_peers.iter().filter(|ip| ip.is_ipv4()).count();
388    let ipv6_peer_count = initial_peers.iter().filter(|ip| ip.is_ipv6()).count();
389    info!(
390        ?ipv4_peer_count,
391        ?ipv6_peer_count,
392        "connecting to initial peer set"
393    );
394
395    // # Security
396    //
397    // Resists distributed denial of service attacks by making sure that
398    // new peer connections are initiated at least `MIN_OUTBOUND_PEER_CONNECTION_INTERVAL` apart.
399    //
400    // # Correctness
401    //
402    // Each `FuturesUnordered` can hold one `Buffer` or `Batch` reservation for
403    // an indefinite period. We can use `FuturesUnordered` without filling
404    // the underlying network buffers, because we immediately drive this
405    // single `FuturesUnordered` to completion, and handshakes have a short timeout.
406    let network = config.network.clone();
407    let mut handshakes: FuturesUnordered<_> = initial_peers
408        .into_iter()
409        .enumerate()
410        .map(|(i, addr)| {
411            let connection_tracker = active_outbound_connections.track_connection();
412            let req = OutboundConnectorRequest {
413                addr,
414                connection_tracker,
415            };
416            let outbound_connector = outbound_connector.clone();
417            let network = network.clone();
418
419            // Spawn a new task to make the outbound connection.
420            tokio::spawn(
421                async move {
422                    // Only spawn one outbound connector per
423                    // `MIN_OUTBOUND_PEER_CONNECTION_INTERVAL`,
424                    // by sleeping for the interval multiplied by the peer's index in the list.
425                    sleep(
426                        constants::MIN_OUTBOUND_PEER_CONNECTION_INTERVAL.saturating_mul(i as u32),
427                    )
428                    .await;
429
430                    // As soon as we create the connector future,
431                    // the handshake starts running as a spawned task.
432                    record_connection_attempt_started(
433                        &network,
434                        ConnectionDirection::Outbound,
435                        addr,
436                    );
437                    let result = outbound_connector
438                        .oneshot(req)
439                        .map_err(move |e| (addr, e))
440                        .await;
441                    record_connection_attempt_finished(
442                        &network,
443                        ConnectionDirection::Outbound,
444                        addr,
445                        result.as_ref().err().map(|(_, error)| error),
446                    );
447                    result
448                }
449                .in_current_span(),
450            )
451            .wait_for_panics()
452        })
453        .collect();
454
455    while let Some(handshake_result) = handshakes.next().await {
456        match handshake_result {
457            Ok(change) => {
458                handshake_success_total += 1;
459                debug!(
460                    ?handshake_success_total,
461                    ?handshake_error_total,
462                    ?change,
463                    "an initial peer handshake succeeded"
464                );
465
466                // The connection limit makes sure this send doesn't block
467                peerset_tx.send(change).await?;
468            }
469            Err((addr, ref e)) => {
470                handshake_error_total += 1;
471
472                // this is verbose, but it's better than just hanging with no output when there are errors
473                let mut expected_error = false;
474                if let Some(io_error) = e.downcast_ref::<tokio::io::Error>() {
475                    // Some systems only have IPv4, or only have IPv6,
476                    // so these errors are not particularly interesting.
477                    if io_error.kind() == tokio::io::ErrorKind::AddrNotAvailable {
478                        expected_error = true;
479                    }
480                }
481
482                if expected_error {
483                    debug!(
484                        successes = ?handshake_success_total,
485                        errors = ?handshake_error_total,
486                        ?addr,
487                        ?e,
488                        "an initial peer connection failed"
489                    );
490                } else {
491                    info!(
492                        successes = ?handshake_success_total,
493                        errors = ?handshake_error_total,
494                        ?addr,
495                        %e,
496                        "an initial peer connection failed"
497                    );
498                }
499            }
500        }
501
502        // Security: Let other tasks run after each connection is processed.
503        //
504        // Avoids remote peers starving other Zebra tasks using initial connection successes or errors.
505        tokio::task::yield_now().await;
506    }
507
508    let outbound_connections = active_outbound_connections.update_count();
509    info!(
510        ?handshake_success_total,
511        ?handshake_error_total,
512        ?outbound_connections,
513        "finished connecting to initial seed and disk cache peers"
514    );
515
516    Ok(active_outbound_connections)
517}
518
519/// Limit the number of `initial_peers` addresses entries to the configured
520/// `peerset_initial_target_size`.
521///
522/// Returns randomly chosen entries from the provided set of addresses,
523/// in a random order.
524///
525/// Also sends every initial peer to the `address_book_updater`.
526async fn limit_initial_peers(
527    config: &Config,
528    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
529) -> HashSet<PeerSocketAddr> {
530    let all_peers: HashSet<PeerSocketAddr> = config.initial_peers().await;
531    let mut preferred_peers: BTreeMap<PeerPreference, Vec<PeerSocketAddr>> = BTreeMap::new();
532
533    let all_peers_count = all_peers.len();
534    if all_peers_count > config.peerset_initial_target_size {
535        info!(
536            "limiting the initial peers list from {} to {}",
537            all_peers_count, config.peerset_initial_target_size,
538        );
539    }
540
541    // Filter out invalid initial peers, and prioritise valid peers for initial connections.
542    // (This treats initial peers the same way we treat gossiped peers.)
543    for peer_addr in all_peers {
544        let preference = PeerPreference::new(peer_addr, config.network.clone());
545
546        match preference {
547            Ok(preference) => preferred_peers
548                .entry(preference)
549                .or_default()
550                .push(peer_addr),
551            Err(error) => info!(
552                ?peer_addr,
553                ?error,
554                "invalid initial peer from DNS seeder, configured IP address, or disk cache",
555            ),
556        }
557    }
558
559    // Send every initial peer to the address book, in preferred order.
560    // (This treats initial peers the same way we treat gossiped peers.)
561    //
562    // # Security
563    //
564    // Initial peers are limited because:
565    // - the number of initial peers is limited
566    // - this code only runs once at startup
567    for peer in preferred_peers.values().flatten() {
568        let peer_addr = MetaAddr::new_initial_peer(*peer);
569        // `send` only waits when the channel is full.
570        // The address book updater runs in its own thread, so we will only wait for a short time.
571        let _ = address_book_updater.send(peer_addr).await;
572    }
573
574    // Split out the `initial_peers` that will be shuffled and returned,
575    // choosing preferred peers first.
576    let mut initial_peers: HashSet<PeerSocketAddr> = HashSet::new();
577    for better_peers in preferred_peers.values() {
578        let mut better_peers = better_peers.clone();
579        let (chosen_peers, _unused_peers) = better_peers.partial_shuffle(
580            &mut rand::thread_rng(),
581            config.peerset_initial_target_size - initial_peers.len(),
582        );
583
584        initial_peers.extend(chosen_peers.iter());
585
586        if initial_peers.len() >= config.peerset_initial_target_size {
587            break;
588        }
589    }
590
591    initial_peers
592}
593
594/// Open a peer connection listener on `config.listen_addr`,
595/// returning the opened [`TcpListener`], and the address it is bound to.
596///
597/// If the listener is configured to use an automatically chosen port (port `0`),
598/// then the returned address will contain the actual port.
599///
600/// # Panics
601///
602/// If opening the listener fails.
603#[instrument(skip(config), fields(addr = ?config.listen_addr))]
604pub(crate) async fn open_listener(config: &Config) -> (TcpListener, SocketAddr) {
605    // Warn if we're configured using the wrong network port.
606    if let Err(wrong_addr) =
607        address_is_valid_for_inbound_listeners(config.listen_addr, config.network.clone())
608    {
609        warn!(
610            "We are configured with address {} on {:?}, but it could cause network issues. \
611             The default port for {:?} is {}. Error: {wrong_addr:?}",
612            config.listen_addr,
613            config.network,
614            config.network,
615            config.network.default_port(),
616        );
617    }
618
619    info!(
620        "Trying to open Zcash protocol endpoint at {}...",
621        config.listen_addr
622    );
623    let listener_result = TcpListener::bind(config.listen_addr).await;
624
625    let listener = match listener_result {
626        Ok(l) => l,
627        Err(e) => panic!(
628            "Opening Zcash network protocol listener {:?} failed: {e:?}. \
629             Hint: Check if another zebrad or zcashd process is running. \
630             Try changing the network listen_addr in the Zebra config.",
631            config.listen_addr,
632        ),
633    };
634
635    let local_addr = listener
636        .local_addr()
637        .expect("unexpected missing local addr for open listener");
638    info!("Opened Zcash protocol endpoint at {}", local_addr);
639
640    (listener, local_addr)
641}
642
643/// Listens for peer connections on `addr`, then sets up each connection as a
644/// Zcash peer.
645///
646/// Uses `handshaker` to perform a Zcash network protocol handshake, and sends
647/// the [`peer::Client`] result over `peerset_tx`.
648///
649/// Limits the number of active inbound connections based on `config`,
650/// and waits `min_inbound_peer_connection_interval` between connections.
651#[instrument(skip(config, listener, handshaker, peerset_tx), fields(listener_addr = ?listener.local_addr()))]
652async fn accept_inbound_connections<S>(
653    config: Config,
654    listener: TcpListener,
655    min_inbound_peer_connection_interval: Duration,
656    handshaker: S,
657    peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
658    bans_receiver: watch::Receiver<Arc<IndexMap<IpAddr, std::time::Instant>>>,
659    zcashd_compat_peer_ips: Vec<IpAddr>,
660) -> Result<(), BoxError>
661where
662    S: Service<peer::HandshakeRequest<TcpStream>, Response = peer::Client, Error = BoxError>
663        + Clone,
664    S::Future: Send + 'static,
665{
666    let mut recent_inbound_connections =
667        recent_by_ip::RecentByIp::new(None, Some(config.max_connections_per_ip));
668
669    let mut active_inbound_connections = ActiveConnectionCounter::new_counter_with(
670        config.peerset_inbound_connection_limit(),
671        "Inbound Connections",
672    );
673    let zcashd_compat_peer_ips: HashSet<_> = zcashd_compat_peer_ips
674        .into_iter()
675        .map(|ip| canonical_socket_addr(SocketAddr::new(ip, 0)).ip())
676        .collect();
677    // Reserve one inbound slot per configured sidecar IP, always leaving at
678    // least one public slot. Reserved slots are a fast path, not a cap:
679    // when they are taken, further connections from listed IPs compete for
680    // public slots like any other peer, so an unrelated local process (or a
681    // sidecar reconnecting before its dead connection is noticed) can never
682    // lock the real sidecar out of the node.
683    let zcashd_compat_reserved_slots = zcashd_compat_peer_ips
684        .len()
685        .min(config.peerset_inbound_connection_limit().saturating_sub(1));
686    let mut active_zcashd_compat_connections = ActiveConnectionCounter::new_counter_with(
687        zcashd_compat_reserved_slots,
688        "zcashd-compat Inbound Connections",
689    );
690    let public_inbound_connection_limit = config
691        .peerset_inbound_connection_limit()
692        .saturating_sub(zcashd_compat_reserved_slots);
693
694    let mut handshakes: FuturesUnordered<Pin<Box<dyn Future<Output = ()> + Send>>> =
695        FuturesUnordered::new();
696    // Keeping an unresolved future in the pool means the stream never terminates.
697    handshakes.push(future::pending().boxed());
698
699    loop {
700        // Check for panics in finished tasks, before accepting new connections
701        let inbound_result = tokio::select! {
702            biased;
703            next_handshake_res = handshakes.next() => match next_handshake_res {
704                // The task has already sent the peer change to the peer set.
705                Some(()) => continue,
706                None => unreachable!("handshakes never terminates, because it contains a future that never resolves"),
707            },
708
709            // This future must wait until new connections are available: it can't have a timeout.
710            inbound_result = listener.accept() => inbound_result,
711        };
712
713        if let Ok((tcp_stream, addr)) = inbound_result {
714            // # Security
715            //
716            // Canonicalize the accepted address before it is used as a key. On a
717            // dual-stack listener an IPv4 peer connects as IPv4-mapped IPv6, but
718            // bans and misbehaviour updates are keyed on the canonical IPv4 address.
719            // This address also becomes the peer set key and the per-IP limiter key
720            // below, so canonicalizing once here keeps all of them in the same form.
721            let addr: PeerSocketAddr = canonical_peer_addr(addr);
722            record_connection_attempt_started(&config.network, ConnectionDirection::Inbound, addr);
723
724            // # Security
725            //
726            // Check bans in both the raw and canonical representations: on a
727            // dual-stack listener, an IPv4 peer can connect as IPv4-mapped IPv6,
728            // and it must not dodge a ban stored for its IPv4 address and then
729            // be granted the zcashd-compat sidecar privileges below.
730            let canonical_ip = canonical_socket_addr(addr.remove_socket_addr_privacy()).ip();
731            let bans = bans_receiver.borrow().clone();
732            if bans.contains_key(&addr.ip()) || bans.contains_key(&canonical_ip) {
733                debug!(?addr, "banned inbound connection attempt");
734                record_inbound_connection_rejected(&config.network, addr, "banned");
735                std::mem::drop(tcp_stream);
736                continue;
737            }
738
739            let active_public_inbound_connections = active_inbound_connections.update_count();
740            let active_zcashd_compat_inbound_connections =
741                active_zcashd_compat_connections.update_count();
742            let active_total_inbound_connections =
743                active_public_inbound_connections + active_zcashd_compat_inbound_connections;
744            let is_zcashd_compat_peer = zcashd_compat_peer_ips.contains(&canonical_ip);
745
746            // The peer already opened a connection to us.
747            // So we want to increment the connection count as soon as possible.
748            //
749            // Sidecar-listed IPs use a reserved slot when one is free, and
750            // otherwise fall back to competing for a public slot (including
751            // the recent-IP rate limit), so a taken reserved slot delays a
752            // sidecar rather than locking it out.
753            let use_reserved_slot = is_zcashd_compat_peer
754                && active_zcashd_compat_inbound_connections < zcashd_compat_reserved_slots
755                && active_total_inbound_connections < config.peerset_inbound_connection_limit();
756
757            let connection_tracker = if use_reserved_slot {
758                active_zcashd_compat_connections.track_connection()
759            } else if active_public_inbound_connections >= public_inbound_connection_limit
760                || active_total_inbound_connections >= config.peerset_inbound_connection_limit()
761                || recent_inbound_connections.is_past_limit_or_add(addr.ip())
762            {
763                // Too many open inbound connections or pending handshakes already.
764                // Close the connection.
765                record_inbound_connection_rejected(
766                    &config.network,
767                    addr,
768                    "capacity_or_rate_limited",
769                );
770                std::mem::drop(tcp_stream);
771                // Allow invalid connections to be cleared quickly,
772                // but still put a limit on our CPU and network usage from failed connections.
773                tokio::time::sleep(constants::MIN_INBOUND_PEER_FAILED_CONNECTION_INTERVAL).await;
774                continue;
775            } else {
776                active_inbound_connections.track_connection()
777            };
778            debug!(
779                inbound_connections = ?active_total_inbound_connections,
780                ?is_zcashd_compat_peer,
781                "handshaking on an open inbound peer connection"
782            );
783
784            let handshake_task = accept_inbound_handshake(
785                config.network.clone(),
786                addr,
787                handshaker.clone(),
788                tcp_stream,
789                connection_tracker,
790                peerset_tx.clone(),
791            )
792            .await?
793            .wait_for_panics();
794
795            handshakes.push(handshake_task);
796
797            // Rate-limit inbound connection handshakes.
798            // But sleep longer after a successful connection,
799            // so we can clear out failed connections at a higher rate.
800            //
801            // If there is a flood of connections,
802            // this stops Zebra overloading the network with handshake data.
803            //
804            // Zebra can't control how many queued connections are waiting,
805            // but most OSes also limit the number of queued inbound connections on a listener port.
806            tokio::time::sleep(min_inbound_peer_connection_interval).await;
807        } else {
808            // Allow invalid connections to be cleared quickly,
809            // but still put a limit on our CPU and network usage from failed connections.
810            debug!(?inbound_result, "error accepting inbound connection");
811            tokio::time::sleep(constants::MIN_INBOUND_PEER_FAILED_CONNECTION_INTERVAL).await;
812        }
813
814        // Security: Let other tasks run after each connection is processed.
815        //
816        // Avoids remote peers starving other Zebra tasks using inbound connection successes or
817        // errors.
818        //
819        // Preventing a denial of service is important in this code, so we want to sleep *and* make
820        // the next connection after other tasks have run. (Sleeps are not guaranteed to do that.)
821        tokio::task::yield_now().await;
822    }
823}
824
825/// Set up a new inbound connection as a Zcash peer.
826///
827/// Uses `handshaker` to perform a Zcash network protocol handshake, and sends
828/// the [`peer::Client`] result over `peerset_tx`.
829//
830// TODO: when we support inbound proxies, distinguish between proxied listeners and
831//       direct listeners in the span generated by this instrument macro
832#[instrument(skip(handshaker, tcp_stream, connection_tracker, peerset_tx))]
833async fn accept_inbound_handshake<S>(
834    network: Network,
835    addr: PeerSocketAddr,
836    mut handshaker: S,
837    tcp_stream: TcpStream,
838    connection_tracker: ConnectionTracker,
839    peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
840) -> Result<tokio::task::JoinHandle<()>, BoxError>
841where
842    S: Service<peer::HandshakeRequest<TcpStream>, Response = peer::Client, Error = BoxError>
843        + Clone,
844    S::Future: Send + 'static,
845{
846    let connected_addr = peer::ConnectedAddr::new_inbound_direct(addr);
847
848    debug!("got incoming connection");
849
850    // # Correctness
851    //
852    // Holding the drop guard returned by Span::enter across .await points will
853    // result in incorrect traces if it yields.
854    //
855    // This await is okay because the handshaker's `poll_ready` method always returns Ready.
856    handshaker.ready().await?;
857
858    // Construct a handshake future but do not drive it yet....
859    let handshake = handshaker.call(HandshakeRequest {
860        data_stream: tcp_stream,
861        connected_addr,
862        connection_tracker,
863    });
864    // ... instead, spawn a new task to handle this connection
865    let mut peerset_tx = peerset_tx.clone();
866
867    let handshake_task = tokio::spawn(
868        async move {
869            let handshake_result = handshake.await;
870            record_connection_attempt_finished(
871                &network,
872                ConnectionDirection::Inbound,
873                addr,
874                handshake_result.as_ref().err(),
875            );
876
877            if let Ok(client) = handshake_result {
878                // The connection limit makes sure this send doesn't block
879                let _ = peerset_tx.send((addr, client)).await;
880            } else {
881                debug!(?handshake_result, "error handshaking with inbound peer");
882            }
883        }
884        .in_current_span(),
885    );
886
887    Ok(handshake_task)
888}
889
890/// An action that the peer crawler can take.
891enum CrawlerAction {
892    /// Drop the demand signal because there are too many pending handshakes.
893    DemandDrop,
894    /// Initiate a handshake to the next candidate peer in response to demand.
895    ///
896    /// If there are no available candidates, crawl existing peers.
897    DemandHandshakeOrCrawl,
898    /// Crawl existing peers for more peers, and queue dial attempts to fill
899    /// any spare outbound connection capacity, in response to a timer `tick`.
900    TimerCrawl { tick: Instant },
901    /// Clear a finished handshake.
902    HandshakeFinished,
903    /// Clear a finished demand crawl (DemandHandshakeOrCrawl with no peers).
904    DemandCrawlFinished,
905    /// Clear a finished TimerCrawl.
906    TimerCrawlFinished,
907}
908
909/// Given a channel `demand_rx` that signals a need for new peers, try to find
910/// and connect to new peers, and send the resulting `peer::Client`s through the
911/// `peerset_tx` channel.
912///
913/// Crawl for new peers every `config.crawl_new_peer_interval`.
914/// Also crawl whenever there is demand, but no new peers in `candidates`.
915/// After crawling, try to connect to one new peer using `outbound_connector`.
916///
917/// On every crawl timer tick, also queue a dial attempt for each spare outbound
918/// connection slot that has a candidate ready to dial, so the peer set keeps
919/// growing until it reaches `config.peerset_outbound_connection_limit()` or
920/// runs out of ready candidates.
921///
922/// If a handshake fails, restore the unused demand signal by sending it to
923/// `demand_tx`.
924///
925/// The crawler terminates when `candidates.update()` or `peerset_tx` returns a
926/// permanent internal error. Transient errors and individual peer errors should
927/// be handled within the crawler.
928///
929/// Uses `active_outbound_connections` to limit the number of active outbound connections
930/// across both the initial peers and crawler. The limit is based on `config`.
931#[allow(clippy::too_many_arguments)]
932#[instrument(
933    skip(
934        config,
935        demand_tx,
936        demand_rx,
937        candidates,
938        outbound_connector,
939        peerset_tx,
940        active_outbound_connections,
941        address_book_updater,
942    ),
943    fields(
944        new_peer_interval = ?config.crawl_new_peer_interval,
945    )
946)]
947async fn crawl_and_dial<C, S>(
948    config: Config,
949    demand_tx: futures::channel::mpsc::Sender<MorePeers>,
950    mut demand_rx: futures::channel::mpsc::Receiver<MorePeers>,
951    candidates: CandidateSet<S>,
952    outbound_connector: C,
953    peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
954    mut active_outbound_connections: ActiveConnectionCounter,
955    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
956) -> Result<(), BoxError>
957where
958    C: Service<
959            OutboundConnectorRequest,
960            Response = (PeerSocketAddr, peer::Client),
961            Error = BoxError,
962        > + Clone
963        + Send
964        + 'static,
965    C::Future: Send + 'static,
966    S: Service<Request, Response = Response, Error = BoxError> + Send + Sync + 'static,
967    S::Future: Send + 'static,
968{
969    use CrawlerAction::*;
970
971    info!(
972        crawl_new_peer_interval = ?config.crawl_new_peer_interval,
973        outbound_connections = ?active_outbound_connections.update_count(),
974        "starting the peer address crawler",
975    );
976
977    // # Concurrency
978    //
979    // Allow tasks using the candidate set to be spawned, so they can run concurrently.
980    // Previously, Zebra has had deadlocks and long hangs caused by running dependent
981    // candidate set futures in the same async task.
982    let candidates = Arc::new(futures::lock::Mutex::new(candidates));
983
984    // This contains both crawl and handshake tasks.
985    let mut handshakes: FuturesUnordered<
986        Pin<Box<dyn Future<Output = Result<CrawlerAction, BoxError>> + Send>>,
987    > = FuturesUnordered::new();
988    // <FuturesUnordered as Stream> returns None when empty.
989    // Keeping an unresolved future in the pool means the stream never terminates.
990    handshakes.push(future::pending().boxed());
991
992    let mut crawl_timer = tokio::time::interval(config.crawl_new_peer_interval);
993    // If the crawl is delayed, also delay all future crawls.
994    // (Shorter intervals just add load, without any benefit.)
995    crawl_timer.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
996
997    let mut crawl_timer = IntervalStream::new(crawl_timer).map(|tick| TimerCrawl { tick });
998
999    // # Concurrency
1000    //
1001    // To avoid hangs and starvation, the crawler must spawn a separate task for each crawl
1002    // and handshake, so they can make progress independently (and avoid deadlocking each other).
1003    loop {
1004        metrics::gauge!(
1005            "crawler.in_flight_handshakes",
1006            "network" => network_kind_label(&config.network),
1007        )
1008        .set(
1009            handshakes
1010                .len()
1011                .checked_sub(1)
1012                .expect("the pool always contains an unresolved future") as f64,
1013        );
1014
1015        let crawler_action = tokio::select! {
1016            biased;
1017            // Check for completed handshakes first, because the rest of the app needs them.
1018            // Pending handshakes are limited by the connection limit.
1019            next_handshake_res = handshakes.next() => next_handshake_res.expect(
1020                "handshakes never terminates, because it contains a future that never resolves"
1021            ),
1022            // The timer is rate-limited
1023            next_timer = crawl_timer.next() => Ok(next_timer.expect("timers never terminate")),
1024            // Turn any new demand into an action, based on the crawler's current state.
1025            //
1026            // # Concurrency
1027            //
1028            // Demand is potentially unlimited, so it must go last in a biased select!.
1029            next_demand = demand_rx.next() => next_demand.ok_or("demand stream closed, is Zebra shutting down?".into()).map(|MorePeers|{
1030                if active_outbound_connections.update_count() >= config.peerset_outbound_connection_limit() {
1031                    // Too many open outbound connections or pending handshakes already
1032                    DemandDrop
1033                } else {
1034                    DemandHandshakeOrCrawl
1035                }
1036            })
1037        };
1038
1039        match crawler_action {
1040            // Dummy actions
1041            Ok(DemandDrop) => {
1042                // This is set to trace level because when the peerset is
1043                // congested it can generate a lot of demand signal very rapidly.
1044                trace!("too many open connections or in-flight handshakes, dropping demand signal");
1045            }
1046
1047            // Spawned tasks
1048            Ok(DemandHandshakeOrCrawl) => {
1049                let candidates = candidates.clone();
1050                let outbound_connector = outbound_connector.clone();
1051                let peerset_tx = peerset_tx.clone();
1052                let address_book_updater = address_book_updater.clone();
1053                let demand_tx = demand_tx.clone();
1054                let network = config.network.clone();
1055
1056                // Increment the connection count before we spawn the connection.
1057                let outbound_connection_tracker = active_outbound_connections.track_connection();
1058                let outbound_connections = active_outbound_connections.update_count();
1059                debug!(?outbound_connections, "opening an outbound peer connection");
1060
1061                // Spawn each handshake or crawl into an independent task, so handshakes can make
1062                // progress while crawls are running.
1063                //
1064                // # Concurrency
1065                //
1066                // The peer crawler must be able to make progress even if some handshakes are
1067                // rate-limited. So the async mutex and next peer timeout are awaited inside the
1068                // spawned task.
1069                let handshake_or_crawl_handle = tokio::spawn(
1070                    async move {
1071                        // Try to get the next available peer for a handshake.
1072                        //
1073                        // candidates.next() has a short timeout, and briefly holds the address
1074                        // book lock, so it shouldn't hang.
1075                        //
1076                        // Hold the lock for as short a time as possible.
1077                        let candidate = { candidates.lock().await.next().await };
1078
1079                        if let Some(candidate) = candidate {
1080                            // we don't need to spawn here, because there's nothing running concurrently
1081                            dial(
1082                                network,
1083                                candidate,
1084                                outbound_connector,
1085                                outbound_connection_tracker,
1086                                outbound_connections,
1087                                peerset_tx,
1088                                address_book_updater,
1089                                demand_tx,
1090                            )
1091                            .await?;
1092
1093                            Ok(HandshakeFinished)
1094                        } else {
1095                            // There weren't any peers, so try to get more peers.
1096                            debug!("demand for peers but no available candidates");
1097
1098                            crawl(candidates, demand_tx).await?;
1099
1100                            Ok(DemandCrawlFinished)
1101                        }
1102                    }
1103                    .in_current_span(),
1104                )
1105                .wait_for_panics();
1106
1107                handshakes.push(handshake_or_crawl_handle);
1108            }
1109            Ok(TimerCrawl { tick }) => {
1110                let candidates = candidates.clone();
1111                let crawl_demand_tx = demand_tx.clone();
1112                let spare_capacity = config
1113                    .peerset_outbound_connection_limit()
1114                    .saturating_sub(active_outbound_connections.update_count());
1115
1116                let crawl_handle = tokio::spawn(
1117                    async move {
1118                        debug!(
1119                            ?tick,
1120                            spare_capacity,
1121                            "crawling for more peers in response to the crawl timer"
1122                        );
1123
1124                        // Queue a dial attempt for each free outbound slot that has a
1125                        // candidate ready to dial, so the crawler keeps trying to fill
1126                        // the peer set up to the outbound connection limit.
1127                        //
1128                        // Capping the queued attempts at the ready candidate count avoids
1129                        // spawning fallback crawls for demand that can't be met. The
1130                        // demand handler re-checks the connection limit before each
1131                        // handshake, so excess signals are dropped, and failed handshakes
1132                        // restore their demand signal.
1133                        let ready_candidates = { candidates.lock().await.ready_peer_count().await };
1134                        let mut fill_demand_tx = crawl_demand_tx.clone();
1135                        for _ in 0..spare_capacity.min(ready_candidates) {
1136                            if fill_demand_tx.try_send(MorePeers).is_err() {
1137                                break;
1138                            }
1139                        }
1140
1141                        crawl(candidates, crawl_demand_tx).await?;
1142
1143                        Ok(TimerCrawlFinished)
1144                    }
1145                    .in_current_span(),
1146                )
1147                .wait_for_panics();
1148
1149                handshakes.push(crawl_handle);
1150            }
1151
1152            // Completed spawned tasks
1153            Ok(HandshakeFinished) => {
1154                // Already logged in dial()
1155            }
1156            Ok(DemandCrawlFinished) => {
1157                // This is set to trace level because when the peerset is
1158                // congested it can generate a lot of demand signal very rapidly.
1159                trace!("demand-based crawl finished");
1160            }
1161            Ok(TimerCrawlFinished) => {
1162                debug!("timer-based crawl finished");
1163            }
1164
1165            // Fatal errors and shutdowns
1166            Err(error) => {
1167                info!(?error, "crawler task exiting due to an error");
1168                return Err(error);
1169            }
1170        }
1171
1172        // Security: Let other tasks run after each crawler action is processed.
1173        //
1174        // Avoids remote peers starving other Zebra tasks using outbound connection errors.
1175        tokio::task::yield_now().await;
1176    }
1177}
1178
1179/// Try to get more peers using `candidates`, then queue a connection attempt using `demand_tx`.
1180/// If there were no new peers, the connection attempt is skipped.
1181#[instrument(skip(candidates, demand_tx))]
1182async fn crawl<S>(
1183    candidates: Arc<futures::lock::Mutex<CandidateSet<S>>>,
1184    mut demand_tx: futures::channel::mpsc::Sender<MorePeers>,
1185) -> Result<(), BoxError>
1186where
1187    S: Service<Request, Response = Response, Error = BoxError> + Send + Sync + 'static,
1188    S::Future: Send + 'static,
1189{
1190    // update() has timeouts, and briefly holds the address book
1191    // lock, so it shouldn't hang.
1192    // Try to get new peers, holding the lock for as short a time as possible.
1193    let result = {
1194        let result = candidates.lock().await.update().await;
1195        std::mem::drop(candidates);
1196        result
1197    };
1198    let more_peers = match result {
1199        Ok(more_peers) => more_peers,
1200        Err(e) => {
1201            info!(
1202                ?e,
1203                "candidate set returned an error, is Zebra shutting down?"
1204            );
1205            return Err(e);
1206        }
1207    };
1208
1209    // If we got more peers, try to connect to a new peer on our next loop.
1210    //
1211    // # Security
1212    //
1213    // Update attempts are rate-limited by the candidate set,
1214    // and we only try peers if there was actually an update.
1215    //
1216    // So if all peers have had a recent attempt, and there was recent update
1217    // with no peers, the channel will drain. This prevents useless update attempt
1218    // loops.
1219    if let Some(more_peers) = more_peers {
1220        if let Err(send_error) = demand_tx.try_send(more_peers) {
1221            if send_error.is_disconnected() {
1222                // Zebra is shutting down
1223                return Err(send_error.into());
1224            }
1225        }
1226    }
1227
1228    Ok(())
1229}
1230
1231/// Try to connect to `candidate` using `outbound_connector`.
1232/// Uses `outbound_connection_tracker` to track the active connection count.
1233///
1234/// On success, sends peers to `peerset_tx`.
1235/// On failure, marks the peer as failed in the address book,
1236/// then re-adds demand to `demand_tx`.
1237#[allow(clippy::too_many_arguments)]
1238#[instrument(skip(
1239    outbound_connector,
1240    outbound_connection_tracker,
1241    outbound_connections,
1242    peerset_tx,
1243    address_book_updater,
1244    demand_tx
1245))]
1246async fn dial<C>(
1247    network: Network,
1248    candidate: MetaAddr,
1249    mut outbound_connector: C,
1250    outbound_connection_tracker: ConnectionTracker,
1251    outbound_connections: usize,
1252    mut peerset_tx: futures::channel::mpsc::Sender<DiscoveredPeer>,
1253    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
1254    mut demand_tx: futures::channel::mpsc::Sender<MorePeers>,
1255) -> Result<(), BoxError>
1256where
1257    C: Service<
1258            OutboundConnectorRequest,
1259            Response = (PeerSocketAddr, peer::Client),
1260            Error = BoxError,
1261        > + Clone
1262        + Send
1263        + 'static,
1264    C::Future: Send + 'static,
1265{
1266    // If Zebra only has a few connections, we log connection failures at info level,
1267    // so users can diagnose and fix the problem. This defines the threshold for info logs.
1268    const MAX_CONNECTIONS_FOR_INFO_LOG: usize = 5;
1269
1270    // # Correctness
1271    //
1272    // To avoid hangs, the dialer must only await:
1273    // - functions that return immediately, or
1274    // - functions that have a reasonable timeout
1275
1276    debug!(?candidate.addr, "attempting outbound connection in response to demand");
1277
1278    // the connector is always ready, so this can't hang
1279    let outbound_connector = outbound_connector.ready().await?;
1280
1281    let req = OutboundConnectorRequest {
1282        addr: candidate.addr,
1283        connection_tracker: outbound_connection_tracker,
1284    };
1285
1286    // the handshake has timeouts, so it shouldn't hang
1287    record_connection_attempt_started(&network, ConnectionDirection::Outbound, candidate.addr);
1288    let handshake_result: Result<(PeerSocketAddr, peer::Client), BoxError> =
1289        outbound_connector.call(req).map(Into::into).await;
1290    record_connection_attempt_finished(
1291        &network,
1292        ConnectionDirection::Outbound,
1293        candidate.addr,
1294        handshake_result.as_ref().err(),
1295    );
1296
1297    match handshake_result {
1298        Ok((address, client)) => {
1299            debug!(?candidate.addr, "successfully dialed new peer");
1300
1301            // The connection limit makes sure this send doesn't block.
1302            peerset_tx.send((address, client)).await?;
1303        }
1304        // The connection was never opened, or it failed the handshake and was dropped.
1305        Err(error) => {
1306            // Silence verbose info logs in production, but keep logs if the number of connections is low.
1307            // Also silence them completely in tests.
1308            if outbound_connections <= MAX_CONNECTIONS_FOR_INFO_LOG && !cfg!(test) {
1309                info!(?error, ?candidate.addr, "failed to make outbound connection to peer");
1310            } else {
1311                debug!(?error, ?candidate.addr, "failed to make outbound connection to peer");
1312            }
1313            report_failed(address_book_updater.clone(), candidate).await;
1314
1315            // The demand signal that was taken out of the queue to attempt to connect to the
1316            // failed candidate never turned into a connection, so add it back.
1317            //
1318            // # Security
1319            //
1320            // Handshake failures are rate-limited by peer attempt timeouts.
1321            if let Err(send_error) = demand_tx.try_send(MorePeers) {
1322                if send_error.is_disconnected() {
1323                    // Zebra is shutting down
1324                    return Err(send_error.into());
1325                }
1326            }
1327        }
1328    }
1329
1330    Ok(())
1331}
1332
1333/// Mark `addr` as a failed peer to `address_book_updater`.
1334#[instrument(skip(address_book_updater))]
1335async fn report_failed(
1336    address_book_updater: tokio::sync::mpsc::Sender<MetaAddrChange>,
1337    addr: MetaAddr,
1338) {
1339    // The connection info is the same as what's already in the address book.
1340    let addr = MetaAddr::new_errored(addr.addr, None);
1341
1342    // Ignore send errors on Zebra shutdown.
1343    let _ = address_book_updater.send(addr).await;
1344}