2023-04-03 19:28:58 -07:00
|
|
|
//! Configuration for Zebra's network communication.
|
|
|
|
|
2021-04-21 16:14:29 -07:00
|
|
|
use std::{
|
|
|
|
collections::HashSet,
|
2023-06-06 01:28:14 -07:00
|
|
|
ffi::OsString,
|
|
|
|
io::{self, ErrorKind},
|
2021-04-21 16:14:29 -07:00
|
|
|
net::{IpAddr, SocketAddr},
|
|
|
|
string::String,
|
|
|
|
time::Duration,
|
|
|
|
};
|
|
|
|
|
2022-06-26 17:07:37 -07:00
|
|
|
use indexmap::IndexSet;
|
2021-04-21 16:14:29 -07:00
|
|
|
use serde::{de, Deserialize, Deserializer};
|
2023-06-06 01:28:14 -07:00
|
|
|
use tempfile::NamedTempFile;
|
|
|
|
use tokio::{fs, io::AsyncWriteExt};
|
2023-06-19 11:17:39 -07:00
|
|
|
use tracing::Span;
|
2019-10-16 15:16:29 -07:00
|
|
|
|
2021-11-09 12:47:50 -08:00
|
|
|
use zebra_chain::parameters::Network;
|
2019-10-16 15:16:29 -07:00
|
|
|
|
2022-02-14 08:00:31 -08:00
|
|
|
use crate::{
|
|
|
|
constants::{
|
2023-06-19 22:11:45 -07:00
|
|
|
DEFAULT_CRAWL_NEW_PEER_INTERVAL, DEFAULT_MAX_CONNS_PER_IP, DNS_LOOKUP_TIMEOUT,
|
|
|
|
INBOUND_PEER_LIMIT_MULTIPLIER, MAX_PEER_DISK_CACHE_SIZE, OUTBOUND_PEER_LIMIT_MULTIPLIER,
|
2022-02-14 08:00:31 -08:00
|
|
|
},
|
2023-05-14 08:06:07 -07:00
|
|
|
protocol::external::{canonical_peer_addr, canonical_socket_addr},
|
|
|
|
BoxError, PeerSocketAddr,
|
2022-02-14 08:00:31 -08:00
|
|
|
};
|
2021-10-27 14:28:51 -07:00
|
|
|
|
2023-06-06 01:28:14 -07:00
|
|
|
mod cache_dir;
|
|
|
|
|
2021-10-27 14:28:51 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests;
|
2021-02-25 15:06:27 -08:00
|
|
|
|
2023-06-06 01:28:14 -07:00
|
|
|
pub use cache_dir::CacheDir;
|
|
|
|
|
2021-06-23 04:10:21 -07:00
|
|
|
/// The number of times Zebra will retry each initial peer's DNS resolution,
|
|
|
|
/// before checking if any other initial peers have returned addresses.
|
2023-04-03 19:28:58 -07:00
|
|
|
///
|
|
|
|
/// After doing this number of retries of a failed single peer, Zebra will
|
|
|
|
/// check if it has enough peer addresses from other seed peers. If it has
|
|
|
|
/// enough addresses, it won't retry this peer again.
|
|
|
|
///
|
|
|
|
/// If the number of retries is `0`, other peers are checked after every successful
|
|
|
|
/// or failed DNS attempt.
|
|
|
|
const MAX_SINGLE_SEED_PEER_DNS_RETRIES: usize = 0;
|
2021-02-25 15:06:27 -08:00
|
|
|
|
2019-10-08 13:57:24 -07:00
|
|
|
/// Configuration for networking code.
|
2021-04-21 16:14:29 -07:00
|
|
|
#[derive(Clone, Debug, Serialize)]
|
2021-10-13 08:04:49 -07:00
|
|
|
#[serde(deny_unknown_fields, default)]
|
2019-10-08 13:57:24 -07:00
|
|
|
pub struct Config {
|
2019-10-16 18:29:45 -07:00
|
|
|
/// The address on which this node should listen for connections.
|
2021-03-14 15:25:07 -07:00
|
|
|
///
|
2021-04-21 16:14:29 -07:00
|
|
|
/// Can be `address:port` or just `address`. If there is no configured
|
|
|
|
/// port, Zebra will use the default port for the configured `network`.
|
2021-06-23 04:10:21 -07:00
|
|
|
///
|
2021-04-21 16:14:29 -07:00
|
|
|
/// `address` can be an IP address or a DNS name. DNS names are
|
|
|
|
/// only resolved once, when Zebra starts up.
|
|
|
|
///
|
2021-06-23 04:10:21 -07:00
|
|
|
/// If a specific listener address is configured, Zebra will advertise
|
|
|
|
/// it to other nodes. But by default, Zebra uses an unspecified address
|
2022-06-02 08:07:35 -07:00
|
|
|
/// ("0.0.0.0" or "\[::\]"), which is not advertised to other nodes.
|
2021-06-23 04:10:21 -07:00
|
|
|
///
|
|
|
|
/// Zebra does not currently support:
|
|
|
|
/// - [Advertising a different external IP address #1890](https://github.com/ZcashFoundation/zebra/issues/1890), or
|
|
|
|
/// - [Auto-discovering its own external IP address #1893](https://github.com/ZcashFoundation/zebra/issues/1893).
|
|
|
|
///
|
|
|
|
/// However, other Zebra instances compensate for unspecified or incorrect
|
|
|
|
/// listener addresses by adding the external IP addresses of peers to
|
|
|
|
/// their address books.
|
2019-10-16 18:29:45 -07:00
|
|
|
pub listen_addr: SocketAddr,
|
2019-10-24 13:28:42 -07:00
|
|
|
|
2019-10-16 15:16:29 -07:00
|
|
|
/// The network to connect to.
|
|
|
|
pub network: Network,
|
2019-10-24 13:28:42 -07:00
|
|
|
|
|
|
|
/// A list of initial peers for the peerset when operating on
|
|
|
|
/// mainnet.
|
2022-06-26 17:07:37 -07:00
|
|
|
pub initial_mainnet_peers: IndexSet<String>,
|
2019-10-24 13:28:42 -07:00
|
|
|
|
|
|
|
/// A list of initial peers for the peerset when operating on
|
|
|
|
/// testnet.
|
2022-06-26 17:07:37 -07:00
|
|
|
pub initial_testnet_peers: IndexSet<String>,
|
2019-10-24 13:28:42 -07:00
|
|
|
|
2023-06-06 01:28:14 -07:00
|
|
|
/// An optional root directory for storing cached peer address data.
|
|
|
|
///
|
|
|
|
/// # Configuration
|
|
|
|
///
|
|
|
|
/// Set to:
|
|
|
|
/// - `true` to read and write peer addresses to disk using the default cache path,
|
|
|
|
/// - `false` to disable reading and writing peer addresses to disk,
|
|
|
|
/// - `'/custom/cache/directory'` to read and write peer addresses to a custom directory.
|
|
|
|
///
|
|
|
|
/// By default, all Zebra instances run by the same user will share a single peer cache.
|
|
|
|
/// If you use a custom cache path, you might also want to change `state.cache_dir`.
|
|
|
|
///
|
|
|
|
/// # Functionality
|
|
|
|
///
|
|
|
|
/// The peer cache is a list of the addresses of some recently useful peers.
|
|
|
|
///
|
|
|
|
/// For privacy reasons, the cache does *not* include any other information about peers,
|
|
|
|
/// such as when they were connected to the node.
|
|
|
|
///
|
|
|
|
/// Deleting or modifying the peer cache can impact your node's:
|
|
|
|
/// - reliability: if DNS or the Zcash DNS seeders are unavailable or broken
|
|
|
|
/// - security: if DNS is compromised with malicious peers
|
|
|
|
///
|
|
|
|
/// If you delete it, Zebra will replace it with a fresh set of peers from the DNS seeders.
|
|
|
|
///
|
|
|
|
/// # Defaults
|
|
|
|
///
|
|
|
|
/// The default directory is platform dependent, based on
|
|
|
|
/// [`dirs::cache_dir()`](https://docs.rs/dirs/3.0.1/dirs/fn.cache_dir.html):
|
|
|
|
///
|
|
|
|
/// |Platform | Value | Example |
|
|
|
|
/// | ------- | ----------------------------------------------- | ------------------------------------ |
|
|
|
|
/// | Linux | `$XDG_CACHE_HOME/zebra` or `$HOME/.cache/zebra` | `/home/alice/.cache/zebra` |
|
|
|
|
/// | macOS | `$HOME/Library/Caches/zebra` | `/Users/Alice/Library/Caches/zebra` |
|
|
|
|
/// | Windows | `{FOLDERID_LocalAppData}\zebra` | `C:\Users\Alice\AppData\Local\zebra` |
|
|
|
|
/// | Other | `std::env::current_dir()/cache/zebra` | `/cache/zebra` |
|
|
|
|
///
|
|
|
|
/// # Security
|
|
|
|
///
|
|
|
|
/// If you are running Zebra with elevated permissions ("root"), create the
|
|
|
|
/// directory for this file before running Zebra, and make sure the Zebra user
|
|
|
|
/// account has exclusive access to that directory, and other users can't modify
|
|
|
|
/// its parent directories.
|
|
|
|
///
|
|
|
|
/// # Implementation Details
|
|
|
|
///
|
|
|
|
/// Each network has a separate peer list, which is updated regularly from the current
|
|
|
|
/// address book. These lists are stored in `network/mainnet.peers` and
|
|
|
|
/// `network/testnet.peers` files, underneath the `cache_dir` path.
|
|
|
|
///
|
|
|
|
/// Previous peer lists are automatically loaded at startup, and used to populate the
|
|
|
|
/// initial peer set and address book.
|
|
|
|
pub cache_dir: CacheDir,
|
|
|
|
|
2020-07-20 23:27:47 -07:00
|
|
|
/// The initial target size for the peer set.
|
2020-09-08 03:04:01 -07:00
|
|
|
///
|
2023-06-06 01:28:14 -07:00
|
|
|
/// Also used to limit the number of inbound and outbound connections made by Zebra,
|
|
|
|
/// and the size of the cached peer list.
|
2021-10-27 14:28:51 -07:00
|
|
|
///
|
2020-09-08 03:04:01 -07:00
|
|
|
/// If you have a slow network connection, and Zebra is having trouble
|
|
|
|
/// syncing, try reducing the peer set size. You can also reduce the peer
|
|
|
|
/// set size to reduce Zebra's bandwidth usage.
|
2020-07-20 23:27:47 -07:00
|
|
|
pub peerset_initial_target_size: usize,
|
|
|
|
|
2021-03-09 17:36:05 -08:00
|
|
|
/// How frequently we attempt to crawl the network to discover new peer
|
2021-04-12 18:12:10 -07:00
|
|
|
/// addresses.
|
2021-03-09 17:36:05 -08:00
|
|
|
///
|
2021-04-12 18:12:10 -07:00
|
|
|
/// Zebra asks its connected peers for more peer addresses:
|
|
|
|
/// - regularly, every time `crawl_new_peer_interval` elapses, and
|
|
|
|
/// - if the peer set is busy, and there aren't any peer addresses for the
|
|
|
|
/// next connection attempt.
|
2022-06-13 23:21:24 -07:00
|
|
|
#[serde(with = "humantime_serde")]
|
2021-03-09 17:36:05 -08:00
|
|
|
pub crawl_new_peer_interval: Duration,
|
2023-06-19 22:11:45 -07:00
|
|
|
|
|
|
|
/// The maximum number of peer connections Zebra will keep for a given IP address
|
|
|
|
/// before it drops any additional peer connections with that IP.
|
|
|
|
///
|
|
|
|
/// The default and minimum value are 1.
|
|
|
|
pub max_connections_per_ip: usize,
|
2019-10-08 13:57:24 -07:00
|
|
|
}
|
|
|
|
|
2019-10-24 13:28:42 -07:00
|
|
|
impl Config {
|
2021-10-27 14:28:51 -07:00
|
|
|
/// The maximum number of outbound connections that Zebra will open at the same time.
|
|
|
|
/// When this limit is reached, Zebra stops opening outbound connections.
|
|
|
|
///
|
|
|
|
/// # Security
|
|
|
|
///
|
2022-02-14 08:00:31 -08:00
|
|
|
/// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
|
|
|
|
///
|
|
|
|
/// # Performance
|
|
|
|
///
|
|
|
|
/// Zebra's peer set should be limited to a reasonable size,
|
|
|
|
/// to avoid queueing too many in-flight block downloads.
|
|
|
|
/// A large queue of in-flight block downloads can choke a
|
|
|
|
/// constrained local network connection.
|
|
|
|
///
|
|
|
|
/// We assume that Zebra nodes have at least 10 Mbps bandwidth.
|
|
|
|
/// Therefore, a maximum-sized block can take up to 2 seconds to
|
|
|
|
/// download. So the initial outbound peer set adds up to 100 seconds worth
|
|
|
|
/// of blocks to the queue. If Zebra has reached its outbound peer limit,
|
|
|
|
/// that adds an extra 200 seconds of queued blocks.
|
|
|
|
///
|
|
|
|
/// But the peer set for slow nodes is typically much smaller, due to
|
|
|
|
/// the handshake RTT timeout. And Zebra responds to inbound request
|
|
|
|
/// overloads by dropping peer connections.
|
2021-10-27 14:28:51 -07:00
|
|
|
pub fn peerset_outbound_connection_limit(&self) -> usize {
|
2022-02-14 08:00:31 -08:00
|
|
|
self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
|
2021-10-27 14:28:51 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// The maximum number of inbound connections that Zebra will accept at the same time.
|
2022-02-14 08:00:31 -08:00
|
|
|
/// When this limit is reached, Zebra drops new inbound connections,
|
|
|
|
/// without handshaking on them.
|
|
|
|
///
|
|
|
|
/// # Security
|
|
|
|
///
|
|
|
|
/// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
|
2021-10-27 14:28:51 -07:00
|
|
|
pub fn peerset_inbound_connection_limit(&self) -> usize {
|
2022-02-14 08:00:31 -08:00
|
|
|
self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
|
2021-10-27 14:28:51 -07:00
|
|
|
}
|
|
|
|
|
2022-02-14 08:00:31 -08:00
|
|
|
/// The maximum number of inbound and outbound connections that Zebra will have
|
|
|
|
/// at the same time.
|
2021-10-27 14:28:51 -07:00
|
|
|
pub fn peerset_total_connection_limit(&self) -> usize {
|
|
|
|
self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
|
|
|
|
}
|
|
|
|
|
2022-01-24 17:46:31 -08:00
|
|
|
/// Returns the initial seed peer hostnames for the configured network.
|
2022-06-26 17:07:37 -07:00
|
|
|
pub fn initial_peer_hostnames(&self) -> &IndexSet<String> {
|
2021-10-27 14:28:51 -07:00
|
|
|
match self.network {
|
2022-01-24 17:46:31 -08:00
|
|
|
Network::Mainnet => &self.initial_mainnet_peers,
|
|
|
|
Network::Testnet => &self.initial_testnet_peers,
|
2021-10-27 14:28:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-06 01:28:14 -07:00
|
|
|
/// Resolve initial seed peer IP addresses, based on the configured network,
|
|
|
|
/// and load cached peers from disk, if available.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// If a configured address is an invalid [`SocketAddr`] or DNS name.
|
2023-05-14 08:06:07 -07:00
|
|
|
pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
|
2023-06-06 01:28:14 -07:00
|
|
|
// TODO: do DNS and disk in parallel if startup speed becomes important
|
|
|
|
let dns_peers =
|
|
|
|
Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
|
|
|
|
|
|
|
|
// Ignore disk errors because the cache is optional and the method already logs them.
|
|
|
|
let disk_peers = self.load_peer_cache().await.unwrap_or_default();
|
|
|
|
|
|
|
|
dns_peers
|
|
|
|
.into_iter()
|
|
|
|
.chain(disk_peers.into_iter())
|
|
|
|
.collect()
|
2022-01-24 17:46:31 -08:00
|
|
|
}
|
|
|
|
|
2021-02-25 15:06:27 -08:00
|
|
|
/// Concurrently resolves `peers` into zero or more IP addresses, with a
|
|
|
|
/// timeout of a few seconds on each DNS request.
|
2021-02-02 18:20:26 -08:00
|
|
|
///
|
2021-02-25 15:06:27 -08:00
|
|
|
/// If DNS resolution fails or times out for all peers, continues retrying
|
|
|
|
/// until at least one peer is found.
|
2023-05-14 08:06:07 -07:00
|
|
|
async fn resolve_peers(peers: &HashSet<String>) -> HashSet<PeerSocketAddr> {
|
2021-02-02 18:20:26 -08:00
|
|
|
use futures::stream::StreamExt;
|
|
|
|
|
2021-05-13 18:46:02 -07:00
|
|
|
if peers.is_empty() {
|
2021-06-22 14:59:06 -07:00
|
|
|
warn!(
|
|
|
|
"no initial peers in the network config. \
|
|
|
|
Hint: you must configure at least one peer IP or DNS seeder to run Zebra, \
|
2023-06-06 01:28:14 -07:00
|
|
|
give it some previously cached peer IP addresses on disk, \
|
2021-06-22 14:59:06 -07:00
|
|
|
or make sure Zebra's listener port gets inbound connections."
|
|
|
|
);
|
2021-05-13 18:46:02 -07:00
|
|
|
return HashSet::new();
|
|
|
|
}
|
|
|
|
|
2021-02-17 13:09:02 -08:00
|
|
|
loop {
|
2021-02-25 15:06:27 -08:00
|
|
|
// We retry each peer individually, as well as retrying if there are
|
|
|
|
// no peers in the combined list. DNS failures are correlated, so all
|
|
|
|
// peers can fail DNS, leaving Zebra with a small list of custom IP
|
|
|
|
// address peers. Individual retries avoid this issue.
|
2021-02-17 13:09:02 -08:00
|
|
|
let peer_addresses = peers
|
|
|
|
.iter()
|
2023-04-03 19:28:58 -07:00
|
|
|
.map(|s| Config::resolve_host(s, MAX_SINGLE_SEED_PEER_DNS_RETRIES))
|
2021-02-17 13:09:02 -08:00
|
|
|
.collect::<futures::stream::FuturesUnordered<_>>()
|
|
|
|
.concat()
|
|
|
|
.await;
|
|
|
|
|
|
|
|
if peer_addresses.is_empty() {
|
|
|
|
tracing::info!(
|
|
|
|
?peers,
|
|
|
|
?peer_addresses,
|
|
|
|
"empty peer list after DNS resolution, retrying after {} seconds",
|
2022-02-14 08:00:31 -08:00
|
|
|
DNS_LOOKUP_TIMEOUT.as_secs(),
|
2021-02-17 13:09:02 -08:00
|
|
|
);
|
2022-02-14 08:00:31 -08:00
|
|
|
tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
|
2021-02-17 13:09:02 -08:00
|
|
|
} else {
|
|
|
|
return peer_addresses;
|
|
|
|
}
|
|
|
|
}
|
2019-10-24 13:28:42 -07:00
|
|
|
}
|
|
|
|
|
2021-02-25 15:06:27 -08:00
|
|
|
/// Resolves `host` into zero or more IP addresses, retrying up to
|
|
|
|
/// `max_retries` times.
|
|
|
|
///
|
|
|
|
/// If DNS continues to fail, returns an empty list of addresses.
|
2023-06-06 01:28:14 -07:00
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// If a configured address is an invalid [`SocketAddr`] or DNS name.
|
2023-05-14 08:06:07 -07:00
|
|
|
async fn resolve_host(host: &str, max_retries: usize) -> HashSet<PeerSocketAddr> {
|
2023-04-03 19:28:58 -07:00
|
|
|
for retries in 0..=max_retries {
|
|
|
|
if let Ok(addresses) = Config::resolve_host_once(host).await {
|
|
|
|
return addresses;
|
|
|
|
}
|
|
|
|
|
|
|
|
if retries < max_retries {
|
|
|
|
tracing::info!(
|
|
|
|
?host,
|
|
|
|
previous_attempts = ?(retries + 1),
|
|
|
|
"Waiting {DNS_LOOKUP_TIMEOUT:?} to retry seed peer DNS resolution",
|
|
|
|
);
|
|
|
|
tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
|
|
|
|
} else {
|
|
|
|
tracing::info!(
|
|
|
|
?host,
|
|
|
|
attempts = ?(retries + 1),
|
|
|
|
"Seed peer DNS resolution failed, checking for addresses from other seed peers",
|
|
|
|
);
|
|
|
|
}
|
2021-02-25 15:06:27 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
HashSet::new()
|
|
|
|
}
|
|
|
|
|
2021-02-02 18:20:26 -08:00
|
|
|
/// Resolves `host` into zero or more IP addresses.
|
|
|
|
///
|
|
|
|
/// If `host` is a DNS name, performs DNS resolution with a timeout of a few seconds.
|
2021-02-25 15:06:27 -08:00
|
|
|
/// If DNS resolution fails or times out, returns an error.
|
2023-06-06 01:28:14 -07:00
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// If a configured address is an invalid [`SocketAddr`] or DNS name.
|
2023-05-14 08:06:07 -07:00
|
|
|
async fn resolve_host_once(host: &str) -> Result<HashSet<PeerSocketAddr>, BoxError> {
|
2021-02-02 18:20:26 -08:00
|
|
|
let fut = tokio::net::lookup_host(host);
|
2022-02-14 08:00:31 -08:00
|
|
|
let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
|
2021-02-02 18:20:26 -08:00
|
|
|
|
|
|
|
match fut.await {
|
2021-09-29 11:08:20 -07:00
|
|
|
Ok(Ok(ip_addrs)) => {
|
2023-05-14 08:06:07 -07:00
|
|
|
let ip_addrs: Vec<PeerSocketAddr> = ip_addrs.map(canonical_peer_addr).collect();
|
2021-09-29 11:08:20 -07:00
|
|
|
|
2022-12-08 15:56:01 -08:00
|
|
|
// This log is needed for user debugging, but it's annoying during tests.
|
|
|
|
#[cfg(not(test))]
|
2021-09-29 11:08:20 -07:00
|
|
|
info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
|
2022-12-08 15:56:01 -08:00
|
|
|
#[cfg(test)]
|
|
|
|
debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
|
2021-09-29 11:08:20 -07:00
|
|
|
|
|
|
|
for ip in &ip_addrs {
|
|
|
|
// Count each initial peer, recording the seed config and resolved IP address.
|
|
|
|
//
|
|
|
|
// If an IP is returned by multiple seeds,
|
|
|
|
// each duplicate adds 1 to the initial peer count.
|
|
|
|
// (But we only make one initial connection attempt to each IP.)
|
|
|
|
metrics::counter!(
|
|
|
|
"zcash.net.peers.initial",
|
|
|
|
1,
|
|
|
|
"seed" => host.to_string(),
|
|
|
|
"remote_ip" => ip.to_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(ip_addrs.into_iter().collect())
|
|
|
|
}
|
2023-06-06 01:28:14 -07:00
|
|
|
Ok(Err(e)) if e.kind() == ErrorKind::InvalidInput => {
|
|
|
|
// TODO: add testnet/mainnet ports, like we do with the listener address
|
|
|
|
panic!(
|
|
|
|
"Invalid peer IP address in Zebra config: addresses must have ports:\n\
|
|
|
|
resolving {host:?} returned {e:?}"
|
|
|
|
);
|
|
|
|
}
|
2021-02-02 18:20:26 -08:00
|
|
|
Ok(Err(e)) => {
|
2021-09-29 11:08:20 -07:00
|
|
|
tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
|
2021-02-25 15:06:27 -08:00
|
|
|
Err(e.into())
|
2021-02-02 18:20:26 -08:00
|
|
|
}
|
|
|
|
Err(e) => {
|
2021-09-29 11:08:20 -07:00
|
|
|
tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
|
2021-02-25 15:06:27 -08:00
|
|
|
Err(e.into())
|
2021-02-02 18:20:26 -08:00
|
|
|
}
|
2019-10-24 13:28:42 -07:00
|
|
|
}
|
|
|
|
}
|
2023-06-06 01:28:14 -07:00
|
|
|
|
|
|
|
/// Returns the addresses in the peer list cache file, if available.
|
|
|
|
pub async fn load_peer_cache(&self) -> io::Result<HashSet<PeerSocketAddr>> {
|
|
|
|
let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(self.network) else {
|
|
|
|
return Ok(HashSet::new());
|
|
|
|
};
|
|
|
|
|
|
|
|
let peer_list = match fs::read_to_string(&peer_cache_file).await {
|
|
|
|
Ok(peer_list) => peer_list,
|
|
|
|
Err(peer_list_error) => {
|
|
|
|
// We expect that the cache will be missing for new Zebra installs
|
|
|
|
if peer_list_error.kind() == ErrorKind::NotFound {
|
|
|
|
return Ok(HashSet::new());
|
|
|
|
} else {
|
|
|
|
info!(
|
|
|
|
?peer_list_error,
|
|
|
|
"could not load cached peer list, using default seed peers"
|
|
|
|
);
|
|
|
|
return Err(peer_list_error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Skip and log addresses that don't parse, and automatically deduplicate using the HashSet.
|
|
|
|
// (These issues shouldn't happen unless users modify the file.)
|
|
|
|
let peer_list: HashSet<PeerSocketAddr> = peer_list
|
|
|
|
.lines()
|
|
|
|
.filter_map(|peer| {
|
|
|
|
peer.parse()
|
|
|
|
.map_err(|peer_parse_error| {
|
|
|
|
info!(
|
|
|
|
?peer_parse_error,
|
|
|
|
"invalid peer address in cached peer list, skipping"
|
|
|
|
);
|
|
|
|
peer_parse_error
|
|
|
|
})
|
|
|
|
.ok()
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
// This log is needed for user debugging, but it's annoying during tests.
|
|
|
|
#[cfg(not(test))]
|
|
|
|
info!(
|
|
|
|
cached_ip_count = ?peer_list.len(),
|
|
|
|
?peer_cache_file,
|
|
|
|
"loaded cached peer IP addresses"
|
|
|
|
);
|
|
|
|
#[cfg(test)]
|
|
|
|
debug!(
|
|
|
|
cached_ip_count = ?peer_list.len(),
|
|
|
|
?peer_cache_file,
|
|
|
|
"loaded cached peer IP addresses"
|
|
|
|
);
|
|
|
|
|
|
|
|
for ip in &peer_list {
|
|
|
|
// Count each initial peer, recording the cache file and loaded IP address.
|
|
|
|
//
|
|
|
|
// If an IP is returned by DNS seeders and the cache,
|
|
|
|
// each duplicate adds 1 to the initial peer count.
|
|
|
|
// (But we only make one initial connection attempt to each IP.)
|
|
|
|
metrics::counter!(
|
|
|
|
"zcash.net.peers.initial",
|
|
|
|
1,
|
|
|
|
"cache" => peer_cache_file.display().to_string(),
|
|
|
|
"remote_ip" => ip.to_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(peer_list)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Atomically writes a new `peer_list` to the peer list cache file, if configured.
|
|
|
|
/// If the list is empty, keeps the previous cache file.
|
|
|
|
///
|
|
|
|
/// Also creates the peer cache directory, if it doesn't already exist.
|
|
|
|
///
|
|
|
|
/// Atomic writes avoid corrupting the cache if Zebra panics or crashes, or if multiple Zebra
|
|
|
|
/// instances try to read and write the same cache file.
|
|
|
|
pub async fn update_peer_cache(&self, peer_list: HashSet<PeerSocketAddr>) -> io::Result<()> {
|
|
|
|
let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(self.network) else {
|
|
|
|
return Ok(());
|
|
|
|
};
|
|
|
|
|
|
|
|
if peer_list.is_empty() {
|
|
|
|
info!(
|
|
|
|
?peer_cache_file,
|
|
|
|
"cacheable peer list was empty, keeping previous cache"
|
|
|
|
);
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Turn IP addresses into strings
|
|
|
|
let mut peer_list: Vec<String> = peer_list
|
|
|
|
.iter()
|
|
|
|
.take(MAX_PEER_DISK_CACHE_SIZE)
|
|
|
|
.map(|redacted_peer| redacted_peer.remove_socket_addr_privacy().to_string())
|
|
|
|
.collect();
|
|
|
|
// # Privacy
|
|
|
|
//
|
|
|
|
// Sort to destroy any peer order, which could leak peer connection times.
|
|
|
|
// (Currently the HashSet argument does this as well.)
|
|
|
|
peer_list.sort();
|
|
|
|
// Make a newline-separated list
|
|
|
|
let peer_data = peer_list.join("\n");
|
|
|
|
|
|
|
|
// Write to a temporary file, so the cache is not corrupted if Zebra shuts down or crashes
|
|
|
|
// at the same time.
|
|
|
|
//
|
|
|
|
// # Concurrency
|
|
|
|
//
|
|
|
|
// We want to use async code to avoid blocking the tokio executor on filesystem operations,
|
|
|
|
// but `tempfile` is implemented using non-asyc methods. So we wrap its filesystem
|
|
|
|
// operations in `tokio::spawn_blocking()`.
|
|
|
|
//
|
|
|
|
// TODO: split this out into an atomic_write_to_tmp_file() method if we need to re-use it
|
|
|
|
|
|
|
|
// Create the peer cache directory if needed
|
|
|
|
let peer_cache_dir = peer_cache_file
|
|
|
|
.parent()
|
|
|
|
.expect("cache path always has a network directory")
|
|
|
|
.to_owned();
|
|
|
|
tokio::fs::create_dir_all(&peer_cache_dir).await?;
|
|
|
|
|
|
|
|
// Give the temporary file a similar name to the permanent cache file,
|
|
|
|
// but hide it in directory listings.
|
|
|
|
let mut tmp_peer_cache_prefix: OsString = ".tmp.".into();
|
|
|
|
tmp_peer_cache_prefix.push(
|
|
|
|
peer_cache_file
|
|
|
|
.file_name()
|
|
|
|
.expect("cache file always has a file name"),
|
|
|
|
);
|
|
|
|
|
|
|
|
// Create the temporary file.
|
|
|
|
// Do blocking filesystem operations on a dedicated thread.
|
2023-06-19 11:17:39 -07:00
|
|
|
let span = Span::current();
|
2023-06-06 01:28:14 -07:00
|
|
|
let tmp_peer_cache_file = tokio::task::spawn_blocking(move || {
|
2023-06-19 11:17:39 -07:00
|
|
|
span.in_scope(move || {
|
|
|
|
// Put the temporary file in the same directory as the permanent file,
|
|
|
|
// so atomic filesystem operations are possible.
|
|
|
|
tempfile::Builder::new()
|
|
|
|
.prefix(&tmp_peer_cache_prefix)
|
|
|
|
.tempfile_in(peer_cache_dir)
|
|
|
|
})
|
2023-06-06 01:28:14 -07:00
|
|
|
})
|
|
|
|
.await
|
|
|
|
.expect("unexpected panic creating temporary peer cache file")?;
|
|
|
|
|
|
|
|
// Write the list to the file asynchronously, by extracting the inner file, using it,
|
|
|
|
// then combining it back into a type that will correctly drop the file on error.
|
|
|
|
let (tmp_peer_cache_file, tmp_peer_cache_path) = tmp_peer_cache_file.into_parts();
|
|
|
|
let mut tmp_peer_cache_file = tokio::fs::File::from_std(tmp_peer_cache_file);
|
|
|
|
tmp_peer_cache_file.write_all(peer_data.as_bytes()).await?;
|
|
|
|
|
|
|
|
let tmp_peer_cache_file =
|
|
|
|
NamedTempFile::from_parts(tmp_peer_cache_file, tmp_peer_cache_path);
|
|
|
|
|
|
|
|
// Atomically replace the current cache with the temporary cache.
|
|
|
|
// Do blocking filesystem operations on a dedicated thread.
|
2023-06-19 11:17:39 -07:00
|
|
|
let span = Span::current();
|
2023-06-06 01:28:14 -07:00
|
|
|
tokio::task::spawn_blocking(move || {
|
2023-06-19 11:17:39 -07:00
|
|
|
span.in_scope(move || {
|
|
|
|
let result = tmp_peer_cache_file.persist(&peer_cache_file);
|
2023-06-06 01:28:14 -07:00
|
|
|
|
2023-06-19 11:17:39 -07:00
|
|
|
// Drops the temp file if needed
|
|
|
|
match result {
|
|
|
|
Ok(_temp_file) => {
|
|
|
|
info!(
|
|
|
|
cached_ip_count = ?peer_list.len(),
|
|
|
|
?peer_cache_file,
|
|
|
|
"updated cached peer IP addresses"
|
2023-06-06 01:28:14 -07:00
|
|
|
);
|
|
|
|
|
2023-06-19 11:17:39 -07:00
|
|
|
for ip in &peer_list {
|
|
|
|
metrics::counter!(
|
|
|
|
"zcash.net.peers.cache",
|
|
|
|
1,
|
|
|
|
"cache" => peer_cache_file.display().to_string(),
|
|
|
|
"remote_ip" => ip.to_string()
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
Err(error) => Err(error.error),
|
2023-06-06 01:28:14 -07:00
|
|
|
}
|
2023-06-19 11:17:39 -07:00
|
|
|
})
|
2023-06-06 01:28:14 -07:00
|
|
|
})
|
|
|
|
.await
|
|
|
|
.expect("unexpected panic making temporary peer cache file permanent")
|
|
|
|
}
|
2019-10-24 13:28:42 -07:00
|
|
|
}
|
|
|
|
|
2019-10-08 13:57:24 -07:00
|
|
|
impl Default for Config {
|
|
|
|
fn default() -> Config {
|
2019-10-25 20:54:44 -07:00
|
|
|
let mainnet_peers = [
|
|
|
|
"dnsseed.z.cash:8233",
|
|
|
|
"dnsseed.str4d.xyz:8233",
|
2020-06-10 14:08:09 -07:00
|
|
|
"mainnet.seeder.zfnd.org:8233",
|
2020-06-10 18:20:50 -07:00
|
|
|
"mainnet.is.yolo.money:8233",
|
2019-10-25 20:54:44 -07:00
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.map(|&s| String::from(s))
|
|
|
|
.collect();
|
|
|
|
|
2020-06-10 14:08:09 -07:00
|
|
|
let testnet_peers = [
|
|
|
|
"dnsseed.testnet.z.cash:18233",
|
|
|
|
"testnet.seeder.zfnd.org:18233",
|
2020-06-10 18:20:50 -07:00
|
|
|
"testnet.is.yolo.money:18233",
|
2020-06-10 14:08:09 -07:00
|
|
|
]
|
|
|
|
.iter()
|
|
|
|
.map(|&s| String::from(s))
|
|
|
|
.collect();
|
2019-10-25 20:54:44 -07:00
|
|
|
|
2019-10-08 13:57:24 -07:00
|
|
|
Config {
|
2020-06-18 22:44:59 -07:00
|
|
|
listen_addr: "0.0.0.0:8233"
|
2019-10-16 18:29:45 -07:00
|
|
|
.parse()
|
|
|
|
.expect("Hardcoded address should be parseable"),
|
2019-10-16 15:16:29 -07:00
|
|
|
network: Network::Mainnet,
|
2019-10-25 20:54:44 -07:00
|
|
|
initial_mainnet_peers: mainnet_peers,
|
|
|
|
initial_testnet_peers: testnet_peers,
|
2023-06-06 01:28:14 -07:00
|
|
|
cache_dir: CacheDir::default(),
|
2022-02-14 08:00:31 -08:00
|
|
|
crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
|
2020-09-08 03:04:01 -07:00
|
|
|
|
2022-02-14 08:00:31 -08:00
|
|
|
// # Security
|
2020-09-08 03:04:01 -07:00
|
|
|
//
|
2022-02-14 08:00:31 -08:00
|
|
|
// The default peerset target size should be large enough to ensure
|
|
|
|
// nodes have a reliable set of peers.
|
2020-09-08 03:04:01 -07:00
|
|
|
//
|
2022-02-14 08:00:31 -08:00
|
|
|
// But Zebra should only make a small number of initial outbound connections,
|
|
|
|
// so that idle peers don't use too many connection slots.
|
|
|
|
peerset_initial_target_size: 25,
|
2023-06-19 22:11:45 -07:00
|
|
|
max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
|
2019-10-08 13:57:24 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-21 16:14:29 -07:00
|
|
|
|
|
|
|
impl<'de> Deserialize<'de> for Config {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
{
|
|
|
|
#[derive(Deserialize)]
|
|
|
|
#[serde(deny_unknown_fields, default)]
|
|
|
|
struct DConfig {
|
|
|
|
listen_addr: String,
|
|
|
|
network: Network,
|
2022-06-26 17:07:37 -07:00
|
|
|
initial_mainnet_peers: IndexSet<String>,
|
|
|
|
initial_testnet_peers: IndexSet<String>,
|
2023-06-06 01:28:14 -07:00
|
|
|
cache_dir: CacheDir,
|
2021-04-21 16:14:29 -07:00
|
|
|
peerset_initial_target_size: usize,
|
2022-06-13 23:21:24 -07:00
|
|
|
#[serde(alias = "new_peer_interval", with = "humantime_serde")]
|
2021-04-21 16:14:29 -07:00
|
|
|
crawl_new_peer_interval: Duration,
|
2023-06-19 22:11:45 -07:00
|
|
|
max_connections_per_ip: Option<usize>,
|
2021-04-21 16:14:29 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for DConfig {
|
|
|
|
fn default() -> Self {
|
|
|
|
let config = Config::default();
|
|
|
|
Self {
|
2023-04-26 18:36:34 -07:00
|
|
|
listen_addr: "0.0.0.0".to_string(),
|
2021-04-21 16:14:29 -07:00
|
|
|
network: config.network,
|
|
|
|
initial_mainnet_peers: config.initial_mainnet_peers,
|
|
|
|
initial_testnet_peers: config.initial_testnet_peers,
|
2023-06-06 01:28:14 -07:00
|
|
|
cache_dir: config.cache_dir,
|
2021-04-21 16:14:29 -07:00
|
|
|
peerset_initial_target_size: config.peerset_initial_target_size,
|
|
|
|
crawl_new_peer_interval: config.crawl_new_peer_interval,
|
2023-06-19 22:11:45 -07:00
|
|
|
max_connections_per_ip: Some(DEFAULT_MAX_CONNS_PER_IP),
|
2021-04-21 16:14:29 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-19 22:11:45 -07:00
|
|
|
let DConfig {
|
|
|
|
listen_addr,
|
|
|
|
network,
|
|
|
|
initial_mainnet_peers,
|
|
|
|
initial_testnet_peers,
|
|
|
|
cache_dir,
|
|
|
|
peerset_initial_target_size,
|
|
|
|
crawl_new_peer_interval,
|
|
|
|
max_connections_per_ip,
|
|
|
|
} = DConfig::deserialize(deserializer)?;
|
|
|
|
|
|
|
|
let listen_addr = match listen_addr.parse::<SocketAddr>() {
|
2021-04-21 16:14:29 -07:00
|
|
|
Ok(socket) => Ok(socket),
|
2023-06-19 22:11:45 -07:00
|
|
|
Err(_) => match listen_addr.parse::<IpAddr>() {
|
|
|
|
Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
|
2021-04-21 16:14:29 -07:00
|
|
|
Err(err) => Err(de::Error::custom(format!(
|
2022-12-07 17:05:57 -08:00
|
|
|
"{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
|
2021-04-21 16:14:29 -07:00
|
|
|
))),
|
|
|
|
},
|
|
|
|
}?;
|
|
|
|
|
|
|
|
Ok(Config {
|
2021-06-21 19:16:59 -07:00
|
|
|
listen_addr: canonical_socket_addr(listen_addr),
|
2023-06-19 22:11:45 -07:00
|
|
|
network,
|
|
|
|
initial_mainnet_peers,
|
|
|
|
initial_testnet_peers,
|
|
|
|
cache_dir,
|
|
|
|
peerset_initial_target_size,
|
|
|
|
crawl_new_peer_interval,
|
|
|
|
max_connections_per_ip: max_connections_per_ip.unwrap_or(DEFAULT_MAX_CONNS_PER_IP),
|
2021-04-21 16:14:29 -07:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|