zebra_network/config.rs
1//! Configuration for Zebra's network communication.
2
3use std::{
4 collections::HashSet,
5 io::{self, ErrorKind},
6 net::{IpAddr, SocketAddr},
7 sync::Arc,
8 time::Duration,
9};
10
11use indexmap::IndexSet;
12use serde::{de, Deserialize, Deserializer};
13use tokio::fs;
14
15use tracing::Span;
16use zebra_chain::{
17 common::atomic_write,
18 parameters::{
19 testnet::{self, ConfiguredActivationHeights, ConfiguredFundingStreams},
20 Magic, Network, NetworkKind,
21 },
22 work::difficulty::U256,
23};
24
25use crate::{
26 constants::{
27 DEFAULT_CRAWL_NEW_PEER_INTERVAL, DEFAULT_MAX_CONNS_PER_IP,
28 DEFAULT_PEERSET_INITIAL_TARGET_SIZE, DNS_LOOKUP_TIMEOUT, INBOUND_PEER_LIMIT_MULTIPLIER,
29 MAX_PEER_DISK_CACHE_SIZE, OUTBOUND_PEER_LIMIT_MULTIPLIER,
30 },
31 protocol::external::{canonical_peer_addr, canonical_socket_addr},
32 BoxError, PeerSocketAddr,
33};
34
35mod cache_dir;
36
37#[cfg(test)]
38mod tests;
39
40pub use cache_dir::CacheDir;
41
42/// The number of times Zebra will retry each initial peer's DNS resolution,
43/// before checking if any other initial peers have returned addresses.
44///
45/// After doing this number of retries of a failed single peer, Zebra will
46/// check if it has enough peer addresses from other seed peers. If it has
47/// enough addresses, it won't retry this peer again.
48///
49/// If the number of retries is `0`, other peers are checked after every successful
50/// or failed DNS attempt.
51const MAX_SINGLE_SEED_PEER_DNS_RETRIES: usize = 0;
52
53/// Configuration for networking code.
54#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
55#[serde(deny_unknown_fields, default, into = "DConfig")]
56pub struct Config {
57 /// The address on which this node should listen for connections.
58 ///
59 /// Can be `address:port` or just `address`. If there is no configured
60 /// port, Zebra will use the default port for the configured `network`.
61 ///
62 /// `address` can be an IP address or a DNS name. DNS names are
63 /// only resolved once, when Zebra starts up.
64 ///
65 /// If a specific listener address is configured, Zebra will advertise
66 /// it to other nodes. But by default, Zebra uses an unspecified address
67 /// ("0.0.0.0" or "\[::\]"), which is not advertised to other nodes.
68 ///
69 /// Zebra does not currently support:
70 /// - [Advertising a different external IP address #1890](https://github.com/ZcashFoundation/zebra/issues/1890), or
71 /// - [Auto-discovering its own external IP address #1893](https://github.com/ZcashFoundation/zebra/issues/1893).
72 ///
73 /// However, other Zebra instances compensate for unspecified or incorrect
74 /// listener addresses by adding the external IP addresses of peers to
75 /// their address books.
76 pub listen_addr: SocketAddr,
77
78 /// The external address of this node if any.
79 ///
80 /// Zebra bind to `listen_addr` but this can be an internal address if the node
81 /// is behind a firewall, load balancer or NAT. This field can be used to
82 /// advertise a different address to peers making it possible to receive inbound
83 /// connections and contribute to the P2P network from behind a firewall, load balancer, or NAT.
84 pub external_addr: Option<SocketAddr>,
85
86 /// The network to connect to.
87 pub network: Network,
88
89 /// A list of initial peers for the peerset when operating on
90 /// mainnet.
91 pub initial_mainnet_peers: IndexSet<String>,
92
93 /// A list of initial peers for the peerset when operating on
94 /// testnet.
95 pub initial_testnet_peers: IndexSet<String>,
96
97 /// An optional root directory for storing cached peer address data.
98 ///
99 /// # Configuration
100 ///
101 /// Set to:
102 /// - `true` to read and write peer addresses to disk using the default cache path,
103 /// - `false` to disable reading and writing peer addresses to disk,
104 /// - `'/custom/cache/directory'` to read and write peer addresses to a custom directory.
105 ///
106 /// By default, all Zebra instances run by the same user will share a single peer cache.
107 /// If you use a custom cache path, you might also want to change `state.cache_dir`.
108 ///
109 /// # Functionality
110 ///
111 /// The peer cache is a list of the addresses of some recently useful peers.
112 ///
113 /// For privacy reasons, the cache does *not* include any other information about peers,
114 /// such as when they were connected to the node.
115 ///
116 /// Deleting or modifying the peer cache can impact your node's:
117 /// - reliability: if DNS or the Zcash DNS seeders are unavailable or broken
118 /// - security: if DNS is compromised with malicious peers
119 ///
120 /// If you delete it, Zebra will replace it with a fresh set of peers from the DNS seeders.
121 ///
122 /// # Defaults
123 ///
124 /// The default directory is platform dependent, based on
125 /// [`dirs::cache_dir()`](https://docs.rs/dirs/3.0.1/dirs/fn.cache_dir.html):
126 ///
127 /// |Platform | Value | Example |
128 /// | ------- | ----------------------------------------------- | ------------------------------------ |
129 /// | Linux | `$XDG_CACHE_HOME/zebra` or `$HOME/.cache/zebra` | `/home/alice/.cache/zebra` |
130 /// | macOS | `$HOME/Library/Caches/zebra` | `/Users/Alice/Library/Caches/zebra` |
131 /// | Windows | `{FOLDERID_LocalAppData}\zebra` | `C:\Users\Alice\AppData\Local\zebra` |
132 /// | Other | `std::env::current_dir()/cache/zebra` | `/cache/zebra` |
133 ///
134 /// # Security
135 ///
136 /// If you are running Zebra with elevated permissions ("root"), create the
137 /// directory for this file before running Zebra, and make sure the Zebra user
138 /// account has exclusive access to that directory, and other users can't modify
139 /// its parent directories.
140 ///
141 /// # Implementation Details
142 ///
143 /// Each network has a separate peer list, which is updated regularly from the current
144 /// address book. These lists are stored in `network/mainnet.peers` and
145 /// `network/testnet.peers` files, underneath the `cache_dir` path.
146 ///
147 /// Previous peer lists are automatically loaded at startup, and used to populate the
148 /// initial peer set and address book.
149 pub cache_dir: CacheDir,
150
151 /// The initial target size for the peer set.
152 ///
153 /// Also used to limit the number of inbound and outbound connections made by Zebra,
154 /// and the size of the cached peer list.
155 ///
156 /// If you have a slow network connection, and Zebra is having trouble
157 /// syncing, try reducing the peer set size. You can also reduce the peer
158 /// set size to reduce Zebra's bandwidth usage.
159 pub peerset_initial_target_size: usize,
160
161 /// How frequently we attempt to crawl the network to discover new peer
162 /// addresses.
163 ///
164 /// Zebra asks its connected peers for more peer addresses:
165 /// - regularly, every time `crawl_new_peer_interval` elapses, and
166 /// - if the peer set is busy, and there aren't any peer addresses for the
167 /// next connection attempt.
168 #[serde(with = "humantime_serde")]
169 pub crawl_new_peer_interval: Duration,
170
171 /// The maximum number of peer connections Zebra will keep for a given IP address
172 /// before it drops any additional peer connections with that IP.
173 ///
174 /// The default and minimum value are 1.
175 ///
176 /// # Security
177 ///
178 /// Increasing this config above 1 reduces Zebra's network security.
179 ///
180 /// If this config is greater than 1, Zebra can initiate multiple outbound handshakes to the same
181 /// IP address.
182 ///
183 /// This config does not currently limit the number of inbound connections that Zebra will accept
184 /// from the same IP address.
185 ///
186 /// If Zebra makes multiple inbound or outbound connections to the same IP, they will be dropped
187 /// after the handshake, but before adding them to the peer set. The total numbers of inbound and
188 /// outbound connections are also limited to a multiple of `peerset_initial_target_size`.
189 pub max_connections_per_ip: usize,
190}
191
192impl Config {
193 /// The maximum number of outbound connections that Zebra will open at the same time.
194 /// When this limit is reached, Zebra stops opening outbound connections.
195 ///
196 /// # Security
197 ///
198 /// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
199 ///
200 /// # Performance
201 ///
202 /// Zebra's peer set should be limited to a reasonable size,
203 /// to avoid queueing too many in-flight block downloads.
204 /// A large queue of in-flight block downloads can choke a
205 /// constrained local network connection.
206 ///
207 /// We assume that Zebra nodes have at least 10 Mbps bandwidth.
208 /// Therefore, a maximum-sized block can take up to 2 seconds to
209 /// download. So the initial outbound peer set adds up to 100 seconds worth
210 /// of blocks to the queue. If Zebra has reached its outbound peer limit,
211 /// that adds an extra 200 seconds of queued blocks.
212 ///
213 /// But the peer set for slow nodes is typically much smaller, due to
214 /// the handshake RTT timeout. And Zebra responds to inbound request
215 /// overloads by dropping peer connections.
216 pub fn peerset_outbound_connection_limit(&self) -> usize {
217 self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
218 }
219
220 /// The maximum number of inbound connections that Zebra will accept at the same time.
221 /// When this limit is reached, Zebra drops new inbound connections,
222 /// without handshaking on them.
223 ///
224 /// # Security
225 ///
226 /// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
227 pub fn peerset_inbound_connection_limit(&self) -> usize {
228 self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
229 }
230
231 /// The maximum number of inbound and outbound connections that Zebra will have
232 /// at the same time.
233 pub fn peerset_total_connection_limit(&self) -> usize {
234 self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
235 }
236
237 /// Returns the initial seed peer hostnames for the configured network.
238 pub fn initial_peer_hostnames(&self) -> IndexSet<String> {
239 match &self.network {
240 Network::Mainnet => self.initial_mainnet_peers.clone(),
241 Network::Testnet(_params) => self.initial_testnet_peers.clone(),
242 }
243 }
244
245 /// Resolve initial seed peer IP addresses, based on the configured network,
246 /// and load cached peers from disk, if available.
247 ///
248 /// # Panics
249 ///
250 /// If a configured address is an invalid [`SocketAddr`] or DNS name.
251 pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
252 // TODO: do DNS and disk in parallel if startup speed becomes important
253 let dns_peers =
254 Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
255
256 if self.network.is_regtest() {
257 // Only return local peer addresses and skip loading the peer cache on Regtest.
258 dns_peers
259 .into_iter()
260 .filter(PeerSocketAddr::is_localhost)
261 .collect()
262 } else {
263 // Ignore disk errors because the cache is optional and the method already logs them.
264 let disk_peers = self.load_peer_cache().await.unwrap_or_default();
265
266 dns_peers.into_iter().chain(disk_peers).collect()
267 }
268 }
269
270 /// Concurrently resolves `peers` into zero or more IP addresses, with a
271 /// timeout of a few seconds on each DNS request.
272 ///
273 /// If DNS resolution fails or times out for all peers, continues retrying
274 /// until at least one peer is found.
275 async fn resolve_peers(peers: &HashSet<String>) -> HashSet<PeerSocketAddr> {
276 use futures::stream::StreamExt;
277
278 if peers.is_empty() {
279 warn!(
280 "no initial peers in the network config. \
281 Hint: you must configure at least one peer IP or DNS seeder to run Zebra, \
282 give it some previously cached peer IP addresses on disk, \
283 or make sure Zebra's listener port gets inbound connections."
284 );
285 return HashSet::new();
286 }
287
288 loop {
289 // We retry each peer individually, as well as retrying if there are
290 // no peers in the combined list. DNS failures are correlated, so all
291 // peers can fail DNS, leaving Zebra with a small list of custom IP
292 // address peers. Individual retries avoid this issue.
293 let peer_addresses = peers
294 .iter()
295 .map(|s| Config::resolve_host(s, MAX_SINGLE_SEED_PEER_DNS_RETRIES))
296 .collect::<futures::stream::FuturesUnordered<_>>()
297 .concat()
298 .await;
299
300 if peer_addresses.is_empty() {
301 tracing::info!(
302 ?peers,
303 ?peer_addresses,
304 "empty peer list after DNS resolution, retrying after {} seconds",
305 DNS_LOOKUP_TIMEOUT.as_secs(),
306 );
307 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
308 } else {
309 return peer_addresses;
310 }
311 }
312 }
313
314 /// Resolves `host` into zero or more IP addresses, retrying up to
315 /// `max_retries` times.
316 ///
317 /// If DNS continues to fail, returns an empty list of addresses.
318 ///
319 /// # Panics
320 ///
321 /// If a configured address is an invalid [`SocketAddr`] or DNS name.
322 async fn resolve_host(host: &str, max_retries: usize) -> HashSet<PeerSocketAddr> {
323 for retries in 0..=max_retries {
324 if let Ok(addresses) = Config::resolve_host_once(host).await {
325 return addresses;
326 }
327
328 if retries < max_retries {
329 tracing::info!(
330 ?host,
331 previous_attempts = ?(retries + 1),
332 "Waiting {DNS_LOOKUP_TIMEOUT:?} to retry seed peer DNS resolution",
333 );
334 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
335 } else {
336 tracing::info!(
337 ?host,
338 attempts = ?(retries + 1),
339 "Seed peer DNS resolution failed, checking for addresses from other seed peers",
340 );
341 }
342 }
343
344 HashSet::new()
345 }
346
347 /// Resolves `host` into zero or more IP addresses.
348 ///
349 /// If `host` is a DNS name, performs DNS resolution with a timeout of a few seconds.
350 /// If DNS resolution fails or times out, returns an error.
351 ///
352 /// # Panics
353 ///
354 /// If a configured address is an invalid [`SocketAddr`] or DNS name.
355 async fn resolve_host_once(host: &str) -> Result<HashSet<PeerSocketAddr>, BoxError> {
356 let fut = tokio::net::lookup_host(host);
357 let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
358
359 match fut.await {
360 Ok(Ok(ip_addrs)) => {
361 let ip_addrs: Vec<PeerSocketAddr> = ip_addrs.map(canonical_peer_addr).collect();
362
363 // This log is needed for user debugging, but it's annoying during tests.
364 #[cfg(not(test))]
365 info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
366 #[cfg(test)]
367 debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
368
369 for ip in &ip_addrs {
370 // Count each initial peer, recording the seed config and resolved IP address.
371 //
372 // If an IP is returned by multiple seeds,
373 // each duplicate adds 1 to the initial peer count.
374 // (But we only make one initial connection attempt to each IP.)
375 metrics::counter!(
376 "zcash.net.peers.initial",
377 "seed" => host.to_string(),
378 "remote_ip" => ip.to_string()
379 )
380 .increment(1);
381 }
382
383 Ok(ip_addrs.into_iter().collect())
384 }
385 Ok(Err(e)) if e.kind() == ErrorKind::InvalidInput => {
386 // TODO: add testnet/mainnet ports, like we do with the listener address
387 panic!(
388 "Invalid peer IP address in Zebra config: addresses must have ports:\n\
389 resolving {host:?} returned {e:?}"
390 );
391 }
392 Ok(Err(e)) => {
393 tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
394 Err(e.into())
395 }
396 Err(e) => {
397 tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
398 Err(e.into())
399 }
400 }
401 }
402
403 /// Returns the addresses in the peer list cache file, if available.
404 pub async fn load_peer_cache(&self) -> io::Result<HashSet<PeerSocketAddr>> {
405 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
406 return Ok(HashSet::new());
407 };
408
409 let peer_list = match fs::read_to_string(&peer_cache_file).await {
410 Ok(peer_list) => peer_list,
411 Err(peer_list_error) => {
412 // We expect that the cache will be missing for new Zebra installs
413 if peer_list_error.kind() == ErrorKind::NotFound {
414 return Ok(HashSet::new());
415 } else {
416 info!(
417 ?peer_list_error,
418 "could not load cached peer list, using default seed peers"
419 );
420 return Err(peer_list_error);
421 }
422 }
423 };
424
425 // Skip and log addresses that don't parse, and automatically deduplicate using the HashSet.
426 // (These issues shouldn't happen unless users modify the file.)
427 let peer_list: HashSet<PeerSocketAddr> = peer_list
428 .lines()
429 .filter_map(|peer| {
430 peer.parse()
431 .map_err(|peer_parse_error| {
432 info!(
433 ?peer_parse_error,
434 "invalid peer address in cached peer list, skipping"
435 );
436 peer_parse_error
437 })
438 .ok()
439 })
440 .collect();
441
442 // This log is needed for user debugging, but it's annoying during tests.
443 #[cfg(not(test))]
444 info!(
445 cached_ip_count = ?peer_list.len(),
446 ?peer_cache_file,
447 "loaded cached peer IP addresses"
448 );
449 #[cfg(test)]
450 debug!(
451 cached_ip_count = ?peer_list.len(),
452 ?peer_cache_file,
453 "loaded cached peer IP addresses"
454 );
455
456 for ip in &peer_list {
457 // Count each initial peer, recording the cache file and loaded IP address.
458 //
459 // If an IP is returned by DNS seeders and the cache,
460 // each duplicate adds 1 to the initial peer count.
461 // (But we only make one initial connection attempt to each IP.)
462 metrics::counter!(
463 "zcash.net.peers.initial",
464 "cache" => peer_cache_file.display().to_string(),
465 "remote_ip" => ip.to_string()
466 )
467 .increment(1);
468 }
469
470 Ok(peer_list)
471 }
472
473 /// Atomically writes a new `peer_list` to the peer list cache file, if configured.
474 /// If the list is empty, keeps the previous cache file.
475 ///
476 /// Also creates the peer cache directory, if it doesn't already exist.
477 ///
478 /// Atomic writes avoid corrupting the cache if Zebra panics or crashes, or if multiple Zebra
479 /// instances try to read and write the same cache file.
480 pub async fn update_peer_cache(&self, peer_list: HashSet<PeerSocketAddr>) -> io::Result<()> {
481 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
482 return Ok(());
483 };
484
485 if peer_list.is_empty() {
486 info!(
487 ?peer_cache_file,
488 "cacheable peer list was empty, keeping previous cache"
489 );
490 return Ok(());
491 }
492
493 // Turn IP addresses into strings
494 let mut peer_list: Vec<String> = peer_list
495 .iter()
496 .take(MAX_PEER_DISK_CACHE_SIZE)
497 .map(|redacted_peer| redacted_peer.remove_socket_addr_privacy().to_string())
498 .collect();
499 // # Privacy
500 //
501 // Sort to destroy any peer order, which could leak peer connection times.
502 // (Currently the HashSet argument does this as well.)
503 peer_list.sort();
504 // Make a newline-separated list
505 let peer_data = peer_list.join("\n");
506
507 // Write the peer cache file atomically so the cache is not corrupted if Zebra shuts down
508 // or crashes.
509 let span = Span::current();
510 let write_result = tokio::task::spawn_blocking(move || {
511 span.in_scope(move || atomic_write(peer_cache_file, peer_data.as_bytes()))
512 })
513 .await
514 .expect("could not write the peer cache file")?;
515
516 match write_result {
517 Ok(peer_cache_file) => {
518 info!(
519 cached_ip_count = ?peer_list.len(),
520 ?peer_cache_file,
521 "updated cached peer IP addresses"
522 );
523
524 for ip in &peer_list {
525 metrics::counter!(
526 "zcash.net.peers.cache",
527 "cache" => peer_cache_file.display().to_string(),
528 "remote_ip" => ip.to_string()
529 )
530 .increment(1);
531 }
532
533 Ok(())
534 }
535 Err(error) => Err(error.error),
536 }
537 }
538}
539
540impl Default for Config {
541 fn default() -> Config {
542 let mainnet_peers = [
543 "dnsseed.z.cash:8233",
544 "dnsseed.str4d.xyz:8233",
545 "mainnet.seeder.zfnd.org:8233",
546 "mainnet.is.yolo.money:8233",
547 ]
548 .iter()
549 .map(|&s| String::from(s))
550 .collect();
551
552 let testnet_peers = [
553 "dnsseed.testnet.z.cash:18233",
554 "testnet.seeder.zfnd.org:18233",
555 "testnet.is.yolo.money:18233",
556 ]
557 .iter()
558 .map(|&s| String::from(s))
559 .collect();
560
561 Config {
562 listen_addr: "0.0.0.0:8233"
563 .parse()
564 .expect("Hardcoded address should be parseable"),
565 external_addr: None,
566 network: Network::Mainnet,
567 initial_mainnet_peers: mainnet_peers,
568 initial_testnet_peers: testnet_peers,
569 cache_dir: CacheDir::default(),
570 crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
571
572 // # Security
573 //
574 // The default peerset target size should be large enough to ensure
575 // nodes have a reliable set of peers.
576 //
577 // But Zebra should only make a small number of initial outbound connections,
578 // so that idle peers don't use too many connection slots.
579 peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
580 max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
581 }
582 }
583}
584
585#[derive(Serialize, Deserialize)]
586#[serde(deny_unknown_fields)]
587struct DTestnetParameters {
588 network_name: Option<String>,
589 network_magic: Option<[u8; 4]>,
590 slow_start_interval: Option<u32>,
591 target_difficulty_limit: Option<String>,
592 disable_pow: Option<bool>,
593 genesis_hash: Option<String>,
594 activation_heights: Option<ConfiguredActivationHeights>,
595 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
596 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
597 pre_blossom_halving_interval: Option<u32>,
598}
599
600#[derive(Serialize, Deserialize)]
601#[serde(deny_unknown_fields, default)]
602struct DConfig {
603 listen_addr: String,
604 external_addr: Option<String>,
605 network: NetworkKind,
606 testnet_parameters: Option<DTestnetParameters>,
607 initial_mainnet_peers: IndexSet<String>,
608 initial_testnet_peers: IndexSet<String>,
609 cache_dir: CacheDir,
610 peerset_initial_target_size: usize,
611 #[serde(alias = "new_peer_interval", with = "humantime_serde")]
612 crawl_new_peer_interval: Duration,
613 max_connections_per_ip: Option<usize>,
614}
615
616impl Default for DConfig {
617 fn default() -> Self {
618 let config = Config::default();
619 Self {
620 listen_addr: "0.0.0.0".to_string(),
621 external_addr: None,
622 network: Default::default(),
623 testnet_parameters: None,
624 initial_mainnet_peers: config.initial_mainnet_peers,
625 initial_testnet_peers: config.initial_testnet_peers,
626 cache_dir: config.cache_dir,
627 peerset_initial_target_size: config.peerset_initial_target_size,
628 crawl_new_peer_interval: config.crawl_new_peer_interval,
629 max_connections_per_ip: Some(config.max_connections_per_ip),
630 }
631 }
632}
633
634impl From<Arc<testnet::Parameters>> for DTestnetParameters {
635 fn from(params: Arc<testnet::Parameters>) -> Self {
636 Self {
637 network_name: Some(params.network_name().to_string()),
638 network_magic: Some(params.network_magic().0),
639 slow_start_interval: Some(params.slow_start_interval().0),
640 target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
641 disable_pow: Some(params.disable_pow()),
642 genesis_hash: Some(params.genesis_hash().to_string()),
643 activation_heights: Some(params.activation_heights().into()),
644 pre_nu6_funding_streams: Some(params.pre_nu6_funding_streams().into()),
645 post_nu6_funding_streams: Some(params.post_nu6_funding_streams().into()),
646 pre_blossom_halving_interval: Some(
647 params
648 .pre_blossom_halving_interval()
649 .try_into()
650 .expect("should convert"),
651 ),
652 }
653 }
654}
655
656impl From<Config> for DConfig {
657 fn from(
658 Config {
659 listen_addr,
660 external_addr,
661 network,
662 initial_mainnet_peers,
663 initial_testnet_peers,
664 cache_dir,
665 peerset_initial_target_size,
666 crawl_new_peer_interval,
667 max_connections_per_ip,
668 }: Config,
669 ) -> Self {
670 let testnet_parameters = network
671 .parameters()
672 .filter(|params| !params.is_default_testnet() && !params.is_regtest())
673 .map(Into::into);
674
675 DConfig {
676 listen_addr: listen_addr.to_string(),
677 external_addr: external_addr.map(|addr| addr.to_string()),
678 network: network.into(),
679 testnet_parameters,
680 initial_mainnet_peers,
681 initial_testnet_peers,
682 cache_dir,
683 peerset_initial_target_size,
684 crawl_new_peer_interval,
685 max_connections_per_ip: Some(max_connections_per_ip),
686 }
687 }
688}
689
690impl<'de> Deserialize<'de> for Config {
691 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
692 where
693 D: Deserializer<'de>,
694 {
695 let DConfig {
696 listen_addr,
697 external_addr,
698 network: network_kind,
699 testnet_parameters,
700 initial_mainnet_peers,
701 initial_testnet_peers,
702 cache_dir,
703 peerset_initial_target_size,
704 crawl_new_peer_interval,
705 max_connections_per_ip,
706 } = DConfig::deserialize(deserializer)?;
707
708 /// Accepts an [`IndexSet`] of initial peers,
709 ///
710 /// Returns true if any of them are the default Testnet or Mainnet initial peers.
711 fn contains_default_initial_peers(initial_peers: &IndexSet<String>) -> bool {
712 let Config {
713 initial_mainnet_peers: mut default_initial_peers,
714 initial_testnet_peers: default_initial_testnet_peers,
715 ..
716 } = Config::default();
717 default_initial_peers.extend(default_initial_testnet_peers);
718
719 initial_peers
720 .intersection(&default_initial_peers)
721 .next()
722 .is_some()
723 }
724
725 let network = match (network_kind, testnet_parameters) {
726 (NetworkKind::Mainnet, _) => Network::Mainnet,
727 (NetworkKind::Testnet, None) => Network::new_default_testnet(),
728 (NetworkKind::Regtest, testnet_parameters) => {
729 let configured_activation_heights = testnet_parameters
730 .and_then(|params| params.activation_heights)
731 .unwrap_or_default();
732
733 Network::new_regtest(configured_activation_heights)
734 }
735 (
736 NetworkKind::Testnet,
737 Some(DTestnetParameters {
738 network_name,
739 network_magic,
740 slow_start_interval,
741 target_difficulty_limit,
742 disable_pow,
743 genesis_hash,
744 activation_heights,
745 pre_nu6_funding_streams,
746 post_nu6_funding_streams,
747 pre_blossom_halving_interval,
748 }),
749 ) => {
750 let mut params_builder = testnet::Parameters::build();
751
752 if let Some(network_name) = network_name.clone() {
753 params_builder = params_builder.with_network_name(network_name)
754 }
755
756 if let Some(network_magic) = network_magic {
757 params_builder = params_builder.with_network_magic(Magic(network_magic));
758 }
759
760 if let Some(genesis_hash) = genesis_hash {
761 params_builder = params_builder.with_genesis_hash(genesis_hash);
762 }
763
764 if let Some(slow_start_interval) = slow_start_interval {
765 params_builder = params_builder.with_slow_start_interval(
766 slow_start_interval.try_into().map_err(de::Error::custom)?,
767 );
768 }
769
770 if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
771 params_builder = params_builder.with_target_difficulty_limit(
772 target_difficulty_limit
773 .parse::<U256>()
774 .map_err(de::Error::custom)?,
775 );
776 }
777
778 if let Some(disable_pow) = disable_pow {
779 params_builder = params_builder.with_disable_pow(disable_pow);
780 }
781
782 // Retain default Testnet activation heights unless there's an empty [testnet_parameters.activation_heights] section.
783 if let Some(activation_heights) = activation_heights.clone() {
784 params_builder = params_builder.with_activation_heights(activation_heights)
785 }
786
787 if let Some(halving_interval) = pre_blossom_halving_interval {
788 params_builder = params_builder.with_halving_interval(halving_interval.into())
789 }
790
791 // Set configured funding streams after setting any parameters that affect the funding stream address period.
792
793 if let Some(funding_streams) = pre_nu6_funding_streams {
794 params_builder = params_builder.with_pre_nu6_funding_streams(funding_streams);
795 }
796
797 if let Some(funding_streams) = post_nu6_funding_streams {
798 params_builder = params_builder.with_post_nu6_funding_streams(funding_streams);
799 }
800
801 // Return an error if the initial testnet peers includes any of the default initial Mainnet or Testnet
802 // peers and the configured network parameters are incompatible with the default public Testnet.
803 if !params_builder.is_compatible_with_default_parameters()
804 && contains_default_initial_peers(&initial_testnet_peers)
805 {
806 return Err(de::Error::custom(
807 "cannot use default initials peers with incompatible testnet",
808 ));
809 };
810
811 // Return the default Testnet if no network name was configured and all parameters match the default Testnet
812 if network_name.is_none() && params_builder == testnet::Parameters::build() {
813 Network::new_default_testnet()
814 } else {
815 params_builder.to_network()
816 }
817 }
818 };
819
820 let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
821 Ok(socket) => Ok(socket),
822 Err(_) => match listen_addr.parse::<IpAddr>() {
823 Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
824 Err(err) => Err(de::Error::custom(format!(
825 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
826 ))),
827 },
828 }?;
829
830 let external_socket_addr = if let Some(address) = &external_addr {
831 match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
832 Ok(socket) => Ok(Some(socket)),
833 Err(_) => match address.parse::<IpAddr>() {
834 Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
835 Err(err) => Err(de::Error::custom(format!(
836 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
837 ))),
838 },
839 }?
840 } else {
841 None
842 };
843
844 let [max_connections_per_ip, peerset_initial_target_size] = [
845 ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
846 // If we want Zebra to operate with no network,
847 // we should implement a `zebrad` command that doesn't use `zebra-network`.
848 ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
849 ].map(|(field_name, non_zero_config_field, default_config_value)| {
850 if non_zero_config_field == Some(0) {
851 warn!(
852 ?field_name,
853 ?non_zero_config_field,
854 "{field_name} should be greater than 0, using default value of {default_config_value} instead"
855 );
856 }
857
858 non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
859 });
860
861 Ok(Config {
862 listen_addr: canonical_socket_addr(listen_addr),
863 external_addr: external_socket_addr,
864 network,
865 initial_mainnet_peers,
866 initial_testnet_peers,
867 cache_dir,
868 peerset_initial_target_size,
869 crawl_new_peer_interval,
870 max_connections_per_ip,
871 })
872 }
873}