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