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