Skip to main content

zebra_network/peer_set/
initialize.rs

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