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