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