Skip to main content

zebra_network/
peer_cache_updater.rs

1//! An async task that regularly updates the peer cache on disk from the current address book.
2
3use std::io;
4
5use futures::FutureExt;
6use tokio::time::sleep;
7use tower::ServiceExt;
8
9use crate::{
10    address_book_updater::{AddressBookRequest, AddressBookResponse, AddressBookService},
11    constants::{DNS_LOOKUP_TIMEOUT, PEER_DISK_CACHE_UPDATE_INTERVAL},
12    meta_addr::MetaAddr,
13    BoxError, Config,
14};
15
16/// An ongoing task that regularly caches the current peers to disk, based on `config`.
17#[instrument(skip(config, address_book_service))]
18pub async fn peer_cache_updater(
19    config: Config,
20    mut address_book_service: AddressBookService,
21) -> Result<(), BoxError> {
22    // Wait until we've queried DNS and (hopefully) sent peers to the address book.
23    // Ideally we'd wait for at least one peer crawl, but that makes tests very slow.
24    //
25    // TODO: turn the initial sleep time into a parameter of this function,
26    //       and allow it to be set in tests
27    sleep(DNS_LOOKUP_TIMEOUT * 4).await;
28
29    let mut wrote_cache = false;
30
31    loop {
32        // Ignore errors because updating the cache is optional.
33        // Errors are already logged by the functions we're calling.
34        wrote_cache |= update_peer_cache_once(&config, &mut address_book_service)
35            .await
36            .unwrap_or(false);
37
38        // Right after a cold start, the address book can still be empty at the first attempt,
39        // and the cache is only written once there are cacheable peers. Retry soon until the
40        // first write, so the cache exists shortly after the node finds its first peers.
41        let interval = if wrote_cache {
42            PEER_DISK_CACHE_UPDATE_INTERVAL
43        } else {
44            DNS_LOOKUP_TIMEOUT * 4
45        };
46
47        sleep(interval).await;
48    }
49}
50
51/// Caches the current cacheable peers to disk, based on `config`.
52///
53/// Returns `true` if the cache was written, and `false` if the cacheable peer list was empty,
54/// keeping any previous cache.
55pub async fn update_peer_cache_once(
56    config: &Config,
57    address_book_service: &mut AddressBookService,
58) -> io::Result<bool> {
59    let peer_list: std::collections::HashSet<_> = cacheable_peers(address_book_service)
60        .await?
61        .iter()
62        .map(|meta_addr| meta_addr.addr)
63        .collect();
64    let has_peers = !peer_list.is_empty();
65
66    config.update_peer_cache(peer_list).await?;
67
68    Ok(has_peers)
69}
70
71/// Returns a list of cacheable peers from the address book updater task.
72async fn cacheable_peers(
73    address_book_service: &mut AddressBookService,
74) -> io::Result<Vec<MetaAddr>> {
75    // Correctness: box the request future, so its captured types don't leak
76    // into generic callers (rustc's async Send inference struggles with
77    // boxed trait objects inside generic spawned tasks).
78    match address_book_service
79        .oneshot(AddressBookRequest::CacheablePeers)
80        .boxed()
81        .await
82    {
83        Ok(AddressBookResponse::Peers(peers)) => Ok(peers),
84        Ok(_) => unreachable!("CacheablePeers requests always return Peers"),
85        Err(error) => Err(io::Error::other(format!(
86            "error requesting cacheable peers, is Zebra shutting down? {error}"
87        ))),
88    }
89}