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