Skip to main content

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