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}