Skip to main content

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, ConfiguredCheckpoints, 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    /// IPv6 connections are grouped by `/64` prefix, so the limit applies per
181    /// `/64` subnet rather than per individual address. IPv4 connections are
182    /// limited per address.
183    ///
184    /// The default and minimum value are 1.
185    ///
186    /// # Security
187    ///
188    /// Increasing this config above 1 reduces Zebra's network security.
189    ///
190    /// If this config is greater than 1, Zebra can initiate multiple outbound handshakes to the same
191    /// IP address.
192    ///
193    /// Inbound connection attempts sharing a connection limit key are also rate-limited
194    /// by this config within a short time window.
195    ///
196    /// If Zebra makes multiple inbound or outbound connections to the same IP, they will be dropped
197    /// after the handshake, but before adding them to the peer set. The total numbers of inbound and
198    /// outbound connections are also limited to a multiple of `peerset_initial_target_size`.
199    pub max_connections_per_ip: usize,
200}
201
202impl Config {
203    /// The maximum number of outbound connections that Zebra will open at the same time.
204    /// When this limit is reached, Zebra stops opening outbound connections.
205    ///
206    /// # Security
207    ///
208    /// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
209    ///
210    /// # Performance
211    ///
212    /// Zebra's peer set should be limited to a reasonable size,
213    /// to avoid queueing too many in-flight block downloads.
214    /// A large queue of in-flight block downloads can choke a
215    /// constrained local network connection.
216    ///
217    /// We assume that Zebra nodes have at least 10 Mbps bandwidth.
218    /// Therefore, a maximum-sized block can take up to 2 seconds to
219    /// download. So the initial outbound peer set adds up to 100 seconds worth
220    /// of blocks to the queue. If Zebra has reached its outbound peer limit,
221    /// that adds an extra 200 seconds of queued blocks.
222    ///
223    /// But the peer set for slow nodes is typically much smaller, due to
224    /// the handshake RTT timeout. And Zebra responds to inbound request
225    /// overloads by dropping peer connections.
226    pub fn peerset_outbound_connection_limit(&self) -> usize {
227        self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
228    }
229
230    /// The maximum number of inbound connections that Zebra will accept at the same time.
231    /// When this limit is reached, Zebra drops new inbound connections,
232    /// without handshaking on them.
233    ///
234    /// # Security
235    ///
236    /// See the note at [`INBOUND_PEER_LIMIT_MULTIPLIER`].
237    pub fn peerset_inbound_connection_limit(&self) -> usize {
238        self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
239    }
240
241    /// The maximum number of inbound and outbound connections that Zebra will have
242    /// at the same time.
243    pub fn peerset_total_connection_limit(&self) -> usize {
244        self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
245    }
246
247    /// Returns the initial seed peer hostnames for the configured network.
248    pub fn initial_peer_hostnames(&self) -> IndexSet<String> {
249        match &self.network {
250            Network::Mainnet => self.initial_mainnet_peers.clone(),
251            Network::Testnet(_params) => self.initial_testnet_peers.clone(),
252        }
253    }
254
255    /// Resolve initial seed peer IP addresses, based on the configured network,
256    /// and load cached peers from disk, if available.
257    ///
258    /// # Panics
259    ///
260    /// If a configured address is an invalid [`SocketAddr`] or DNS name.
261    pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
262        // TODO: do DNS and disk in parallel if startup speed becomes important
263        let dns_peers =
264            Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
265
266        if self.network.is_regtest() {
267            // Only return local peer addresses and skip loading the peer cache on Regtest.
268            dns_peers
269                .into_iter()
270                .filter(PeerSocketAddr::is_localhost)
271                .collect()
272        } else {
273            // Ignore disk errors because the cache is optional and the method already logs them.
274            let disk_peers = self.load_peer_cache().await.unwrap_or_default();
275
276            dns_peers.into_iter().chain(disk_peers).collect()
277        }
278    }
279
280    /// Concurrently resolves `peers` into zero or more IP addresses, with a
281    /// timeout of a few seconds on each DNS request.
282    ///
283    /// If DNS resolution fails or times out for all peers, continues retrying
284    /// until at least one peer is found.
285    async fn resolve_peers(peers: &HashSet<String>) -> HashSet<PeerSocketAddr> {
286        use futures::stream::StreamExt;
287
288        if peers.is_empty() {
289            warn!(
290                "no initial peers in the network config. \
291                 Hint: you must configure at least one peer IP or DNS seeder to run Zebra, \
292                 give it some previously cached peer IP addresses on disk, \
293                 or make sure Zebra's listener port gets inbound connections."
294            );
295            return HashSet::new();
296        }
297
298        loop {
299            // We retry each peer individually, as well as retrying if there are
300            // no peers in the combined list. DNS failures are correlated, so all
301            // peers can fail DNS, leaving Zebra with a small list of custom IP
302            // address peers. Individual retries avoid this issue.
303            let peer_addresses = peers
304                .iter()
305                .map(|s| Config::resolve_host(s, MAX_SINGLE_SEED_PEER_DNS_RETRIES))
306                .collect::<futures::stream::FuturesUnordered<_>>()
307                .concat()
308                .await;
309
310            if peer_addresses.is_empty() {
311                tracing::info!(
312                    ?peers,
313                    ?peer_addresses,
314                    "empty peer list after DNS resolution, retrying after {} seconds",
315                    DNS_LOOKUP_TIMEOUT.as_secs(),
316                );
317                tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
318            } else {
319                return peer_addresses;
320            }
321        }
322    }
323
324    /// Resolves `host` into zero or more IP addresses, retrying up to
325    /// `max_retries` times.
326    ///
327    /// If DNS continues to fail, returns an empty list of addresses.
328    ///
329    /// # Panics
330    ///
331    /// If a configured address is an invalid [`SocketAddr`] or DNS name.
332    async fn resolve_host(host: &str, max_retries: usize) -> HashSet<PeerSocketAddr> {
333        for retries in 0..=max_retries {
334            if let Ok(addresses) = Config::resolve_host_once(host).await {
335                return addresses;
336            }
337
338            if retries < max_retries {
339                tracing::info!(
340                    ?host,
341                    previous_attempts = ?(retries + 1),
342                    "Waiting {DNS_LOOKUP_TIMEOUT:?} to retry seed peer DNS resolution",
343                );
344                tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
345            } else {
346                tracing::info!(
347                    ?host,
348                    attempts = ?(retries + 1),
349                    "Seed peer DNS resolution failed, checking for addresses from other seed peers",
350                );
351            }
352        }
353
354        HashSet::new()
355    }
356
357    /// Resolves `host` into zero or more IP addresses.
358    ///
359    /// If `host` is a DNS name, performs DNS resolution with a timeout of a few seconds.
360    /// If DNS resolution fails or times out, returns an error.
361    ///
362    /// # Panics
363    ///
364    /// If a configured address is an invalid [`SocketAddr`] or DNS name.
365    async fn resolve_host_once(host: &str) -> Result<HashSet<PeerSocketAddr>, BoxError> {
366        let fut = tokio::net::lookup_host(host);
367        let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
368
369        match fut.await {
370            Ok(Ok(ip_addrs)) => {
371                let ip_addrs: Vec<PeerSocketAddr> = ip_addrs.map(canonical_peer_addr).collect();
372
373                // This log is needed for user debugging, but it's annoying during tests.
374                #[cfg(not(test))]
375                info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
376                #[cfg(test)]
377                debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
378
379                for ip in &ip_addrs {
380                    // Count each initial peer, recording the seed config and resolved IP address.
381                    //
382                    // If an IP is returned by multiple seeds,
383                    // each duplicate adds 1 to the initial peer count.
384                    // (But we only make one initial connection attempt to each IP.)
385                    metrics::counter!(
386                        "zcash.net.peers.initial",
387                        "seed" => host.to_string(),
388                        "remote_ip" => ip.to_string()
389                    )
390                    .increment(1);
391                }
392
393                Ok(ip_addrs.into_iter().collect())
394            }
395            Ok(Err(e)) if e.kind() == ErrorKind::InvalidInput => {
396                // TODO: add testnet/mainnet ports, like we do with the listener address
397                panic!(
398                    "Invalid peer IP address in Zebra config: addresses must have ports:\n\
399                     resolving {host:?} returned {e:?}"
400                );
401            }
402            Ok(Err(e)) => {
403                tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
404                Err(e.into())
405            }
406            Err(e) => {
407                tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
408                Err(e.into())
409            }
410        }
411    }
412
413    /// Returns the addresses in the peer list cache file, if available.
414    pub async fn load_peer_cache(&self) -> io::Result<HashSet<PeerSocketAddr>> {
415        let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
416            return Ok(HashSet::new());
417        };
418
419        let peer_list = match fs::read_to_string(&peer_cache_file).await {
420            Ok(peer_list) => peer_list,
421            Err(peer_list_error) => {
422                // We expect that the cache will be missing for new Zebra installs
423                if peer_list_error.kind() == ErrorKind::NotFound {
424                    return Ok(HashSet::new());
425                } else {
426                    info!(
427                        ?peer_list_error,
428                        "could not load cached peer list, using default seed peers"
429                    );
430                    return Err(peer_list_error);
431                }
432            }
433        };
434
435        // Skip and log addresses that don't parse, and automatically deduplicate using the HashSet.
436        // (These issues shouldn't happen unless users modify the file.)
437        let peer_list: HashSet<PeerSocketAddr> = peer_list
438            .lines()
439            .filter_map(|peer| {
440                peer.parse()
441                    .map_err(|peer_parse_error| {
442                        info!(
443                            ?peer_parse_error,
444                            "invalid peer address in cached peer list, skipping"
445                        );
446                        peer_parse_error
447                    })
448                    .ok()
449            })
450            .collect();
451
452        // This log is needed for user debugging, but it's annoying during tests.
453        #[cfg(not(test))]
454        info!(
455            cached_ip_count = ?peer_list.len(),
456            ?peer_cache_file,
457            "loaded cached peer IP addresses"
458        );
459        #[cfg(test)]
460        debug!(
461            cached_ip_count = ?peer_list.len(),
462            ?peer_cache_file,
463            "loaded cached peer IP addresses"
464        );
465
466        for ip in &peer_list {
467            // Count each initial peer, recording the cache file and loaded IP address.
468            //
469            // If an IP is returned by DNS seeders and the cache,
470            // each duplicate adds 1 to the initial peer count.
471            // (But we only make one initial connection attempt to each IP.)
472            metrics::counter!(
473                "zcash.net.peers.initial",
474                "cache" => peer_cache_file.display().to_string(),
475                "remote_ip" => ip.to_string()
476            )
477            .increment(1);
478        }
479
480        Ok(peer_list)
481    }
482
483    /// Atomically writes a new `peer_list` to the peer list cache file, if configured.
484    /// If the list is empty, keeps the previous cache file.
485    ///
486    /// Also creates the peer cache directory, if it doesn't already exist.
487    ///
488    /// Atomic writes avoid corrupting the cache if Zebra panics or crashes, or if multiple Zebra
489    /// instances try to read and write the same cache file.
490    pub async fn update_peer_cache(&self, peer_list: HashSet<PeerSocketAddr>) -> io::Result<()> {
491        let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
492            return Ok(());
493        };
494
495        if peer_list.is_empty() {
496            info!(
497                ?peer_cache_file,
498                "cacheable peer list was empty, keeping previous cache"
499            );
500            return Ok(());
501        }
502
503        // Turn IP addresses into strings
504        let mut peer_list: Vec<String> = peer_list
505            .iter()
506            .take(MAX_PEER_DISK_CACHE_SIZE)
507            .map(|redacted_peer| redacted_peer.remove_socket_addr_privacy().to_string())
508            .collect();
509        // # Privacy
510        //
511        // Sort to destroy any peer order, which could leak peer connection times.
512        // (Currently the HashSet argument does this as well.)
513        peer_list.sort();
514        // Make a newline-separated list
515        let peer_data = peer_list.join("\n");
516
517        // Write the peer cache file atomically so the cache is not corrupted if Zebra shuts down
518        // or crashes.
519        let span = Span::current();
520        let write_result = tokio::task::spawn_blocking(move || {
521            span.in_scope(move || atomic_write(peer_cache_file, peer_data.as_bytes()))
522        })
523        .await
524        .expect("could not write the peer cache file")?;
525
526        match write_result {
527            Ok(peer_cache_file) => {
528                info!(
529                    cached_ip_count = ?peer_list.len(),
530                    ?peer_cache_file,
531                    "updated cached peer IP addresses"
532                );
533
534                for ip in &peer_list {
535                    metrics::counter!(
536                        "zcash.net.peers.cache",
537                        "cache" => peer_cache_file.display().to_string(),
538                        "remote_ip" => ip.to_string()
539                    )
540                    .increment(1);
541                }
542
543                Ok(())
544            }
545            Err(error) => Err(error.error),
546        }
547    }
548}
549
550impl Default for Config {
551    fn default() -> Config {
552        let mainnet_peers = [
553            "dnsseed.str4d.xyz:8233",
554            "dnsseed.z.cash:8233",
555            "mainnet.seeder.shieldedinfra.net:8233",
556            "mainnet.seeder.zfnd.org:8233",
557            "seeder.zec.rocks:8233",
558        ]
559        .iter()
560        .map(|&s| String::from(s))
561        .collect();
562
563        let testnet_peers = [
564            "dnsseed.testnet.z.cash:18233",
565            "seeder.testnet.zec.rocks:18233",
566            "testnet.seeder.zfnd.org:18233",
567        ]
568        .iter()
569        .map(|&s| String::from(s))
570        .collect();
571
572        Config {
573            listen_addr: "[::]:8233"
574                .parse()
575                .expect("Hardcoded address should be parseable"),
576            external_addr: None,
577            network: Network::Mainnet,
578            initial_mainnet_peers: mainnet_peers,
579            initial_testnet_peers: testnet_peers,
580            cache_dir: CacheDir::default(),
581            crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
582
583            // # Security
584            //
585            // The default peerset target size should be large enough to ensure
586            // nodes have a reliable set of peers.
587            //
588            // But Zebra should only make a small number of initial outbound connections,
589            // so that idle peers don't use too many connection slots.
590            peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
591            max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
592        }
593    }
594}
595
596#[derive(Serialize, Deserialize)]
597#[serde(deny_unknown_fields)]
598struct DTestnetParameters {
599    network_name: Option<String>,
600    network_magic: Option<[u8; 4]>,
601    slow_start_interval: Option<u32>,
602    target_difficulty_limit: Option<String>,
603    disable_pow: Option<bool>,
604    genesis_hash: Option<String>,
605    activation_heights: Option<ConfiguredActivationHeights>,
606    pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
607    post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
608    funding_streams: Option<Vec<ConfiguredFundingStreams>>,
609    pre_blossom_halving_interval: Option<u32>,
610    lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
611    #[serde(default)]
612    checkpoints: ConfiguredCheckpoints,
613    /// If `true`, automatically repeats configured funding stream addresses to fill
614    /// all required periods.
615    extend_funding_stream_addresses_as_required: Option<bool>,
616    /// Height at which the soft fork that temporarily disables Orchard actions activates.
617    ///
618    /// If unset, the default activation height for the network is used; the soft fork
619    /// cannot be disabled via configuration.
620    temporary_orchard_disabling_soft_fork_height: Option<u32>,
621    /// Regtest only: whether to allow coinbase spends to have transparent outputs.
622    should_allow_unshielded_coinbase_spends: Option<bool>,
623}
624
625/// Network configuration used during deserialization.
626#[derive(Serialize, Deserialize)]
627#[serde(untagged)]
628enum DNetwork {
629    DefaultForKind(NetworkKind),
630    ConfiguredRegtest {
631        params: Box<DTestnetParameters>,
632
633        #[serde(default, skip_serializing)]
634        regtest: Option<bool>,
635    },
636    ConfiguredTestnet(Box<DTestnetParameters>),
637}
638
639impl Default for DNetwork {
640    fn default() -> Self {
641        DNetwork::DefaultForKind(NetworkKind::Mainnet)
642    }
643}
644
645#[derive(Serialize, Deserialize)]
646#[serde(deny_unknown_fields, default)]
647struct DConfig {
648    listen_addr: String,
649    external_addr: Option<String>,
650    network: DNetwork,
651
652    /// Legacy testnet parameters, kept for backwards compatibility.
653    #[serde(default, skip_serializing_if = "Option::is_none")]
654    testnet_parameters: Option<DTestnetParameters>,
655
656    initial_mainnet_peers: IndexSet<String>,
657    initial_testnet_peers: IndexSet<String>,
658    cache_dir: CacheDir,
659    peerset_initial_target_size: usize,
660    #[serde(alias = "new_peer_interval", with = "humantime_serde")]
661    crawl_new_peer_interval: Duration,
662    max_connections_per_ip: Option<usize>,
663}
664
665impl Default for DConfig {
666    fn default() -> Self {
667        let config = Config::default();
668        Self {
669            listen_addr: "[::]".to_string(),
670            external_addr: None,
671            network: Default::default(),
672            testnet_parameters: None,
673            initial_mainnet_peers: config.initial_mainnet_peers,
674            initial_testnet_peers: config.initial_testnet_peers,
675            cache_dir: config.cache_dir,
676            peerset_initial_target_size: config.peerset_initial_target_size,
677            crawl_new_peer_interval: config.crawl_new_peer_interval,
678            max_connections_per_ip: Some(config.max_connections_per_ip),
679        }
680    }
681}
682
683impl From<Arc<testnet::Parameters>> for DTestnetParameters {
684    fn from(params: Arc<testnet::Parameters>) -> Self {
685        Self {
686            network_name: Some(params.network_name().to_string()),
687            network_magic: Some(params.network_magic().0),
688            slow_start_interval: Some(params.slow_start_interval().0),
689            target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
690            disable_pow: Some(params.disable_pow()),
691            genesis_hash: Some(params.genesis_hash().to_string()),
692            activation_heights: Some(params.activation_heights().into()),
693            pre_nu6_funding_streams: None,
694            post_nu6_funding_streams: None,
695            funding_streams: Some(params.funding_streams().iter().map(Into::into).collect()),
696            pre_blossom_halving_interval: Some(
697                params
698                    .pre_blossom_halving_interval()
699                    .try_into()
700                    .expect("should convert"),
701            ),
702            lockbox_disbursements: Some(
703                params
704                    .lockbox_disbursements()
705                    .into_iter()
706                    .map(Into::into)
707                    .collect(),
708            ),
709            checkpoints: if params.checkpoints() == testnet::Parameters::default().checkpoints() {
710                ConfiguredCheckpoints::Default(true)
711            } else {
712                params.checkpoints().into()
713            },
714            extend_funding_stream_addresses_as_required: None,
715            temporary_orchard_disabling_soft_fork_height: params
716                .temporary_orchard_disabling_soft_fork_height()
717                .map(|height| height.0),
718            should_allow_unshielded_coinbase_spends: params
719                .is_regtest()
720                .then(|| params.should_allow_unshielded_coinbase_spends()),
721        }
722    }
723}
724
725impl From<Config> for DConfig {
726    fn from(
727        Config {
728            listen_addr,
729            external_addr,
730            network,
731            initial_mainnet_peers,
732            initial_testnet_peers,
733            cache_dir,
734            peerset_initial_target_size,
735            crawl_new_peer_interval,
736            max_connections_per_ip,
737        }: Config,
738    ) -> Self {
739        let dnetwork = match network.kind() {
740            NetworkKind::Testnet => match network
741                .parameters()
742                .filter(|params| !params.is_default_testnet())
743                .map(Into::into)
744            {
745                Some(params) => DNetwork::ConfiguredTestnet(Box::new(params)),
746                None => DNetwork::DefaultForKind(NetworkKind::Testnet),
747            },
748
749            NetworkKind::Regtest => match network.parameters().map(Into::into) {
750                Some(params) => DNetwork::ConfiguredRegtest {
751                    params: Box::new(params),
752                    regtest: Some(true),
753                },
754                None => DNetwork::DefaultForKind(NetworkKind::Regtest),
755            },
756
757            other_kind => DNetwork::DefaultForKind(other_kind),
758        };
759
760        DConfig {
761            listen_addr: listen_addr.to_string(),
762            external_addr: external_addr.map(|addr| addr.to_string()),
763            network: dnetwork,
764            testnet_parameters: None,
765            initial_mainnet_peers,
766            initial_testnet_peers,
767            cache_dir,
768            peerset_initial_target_size,
769            crawl_new_peer_interval,
770            max_connections_per_ip: Some(max_connections_per_ip),
771        }
772    }
773}
774
775impl<'de> Deserialize<'de> for Config {
776    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
777    where
778        D: Deserializer<'de>,
779    {
780        let DConfig {
781            listen_addr,
782            external_addr,
783            network: dnetwork,
784            testnet_parameters,
785            initial_mainnet_peers,
786            initial_testnet_peers,
787            cache_dir,
788            peerset_initial_target_size,
789            crawl_new_peer_interval,
790            max_connections_per_ip,
791        } = DConfig::deserialize(deserializer)?;
792
793        let network = match (dnetwork, testnet_parameters) {
794            (DNetwork::ConfiguredTestnet(params), _) => {
795                build_configured_testnet::<D>(*params, &initial_testnet_peers)?
796            }
797            (DNetwork::ConfiguredRegtest { params, .. }, _) => {
798                Network::new_regtest(build_regtest_params(*params))
799            }
800            (DNetwork::DefaultForKind(NetworkKind::Mainnet), _) => Network::Mainnet,
801            (DNetwork::DefaultForKind(NetworkKind::Testnet), Some(params)) => {
802                build_configured_testnet::<D>(params, &initial_testnet_peers)?
803            }
804            (DNetwork::DefaultForKind(NetworkKind::Testnet), None) => {
805                Network::new_default_testnet()
806            }
807            (DNetwork::DefaultForKind(NetworkKind::Regtest), Some(params)) => {
808                Network::new_regtest(build_regtest_params(params))
809            }
810            (DNetwork::DefaultForKind(NetworkKind::Regtest), None) => {
811                Network::new_regtest(Default::default())
812            }
813        };
814
815        let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
816            Ok(socket) => Ok(socket),
817            Err(_) => match listen_addr.parse::<IpAddr>() {
818                Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
819                Err(err) => Err(de::Error::custom(format!(
820                    "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
821                ))),
822            },
823        }?;
824
825        let external_socket_addr = if let Some(address) = &external_addr {
826            match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
827                Ok(socket) => Ok(Some(socket)),
828                Err(_) => match address.parse::<IpAddr>() {
829                    Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
830                    Err(err) => Err(de::Error::custom(format!(
831                        "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
832                    ))),
833                },
834            }?
835        } else {
836            None
837        };
838
839        let [max_connections_per_ip, peerset_initial_target_size] = [
840            ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
841            // If we want Zebra to operate with no network,
842            // we should implement a `zebrad` command that doesn't use `zebra-network`.
843            ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
844        ].map(|(field_name, non_zero_config_field, default_config_value)| {
845            if non_zero_config_field == Some(0) {
846                warn!(
847                    ?field_name,
848                    ?non_zero_config_field,
849                    "{field_name} should be greater than 0, using default value of {default_config_value} instead"
850                );
851            }
852
853            non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
854        });
855
856        Ok(Config {
857            listen_addr: canonical_socket_addr(listen_addr),
858            external_addr: external_socket_addr,
859            network,
860            initial_mainnet_peers,
861            initial_testnet_peers,
862            cache_dir,
863            peerset_initial_target_size,
864            crawl_new_peer_interval,
865            max_connections_per_ip,
866        })
867    }
868}
869
870/// Accepts an [`IndexSet`] of initial peers,
871///
872/// Returns true if any of them are the default Testnet or Mainnet initial peers.
873fn contains_default_initial_peers(initial_peers: &IndexSet<String>) -> bool {
874    let Config {
875        initial_mainnet_peers: mut default_initial_peers,
876        initial_testnet_peers: default_initial_testnet_peers,
877        ..
878    } = Config::default();
879    default_initial_peers.extend(default_initial_testnet_peers);
880
881    initial_peers
882        .intersection(&default_initial_peers)
883        .next()
884        .is_some()
885}
886
887fn build_configured_testnet<'de, D>(
888    params: DTestnetParameters,
889    initial_testnet_peers: &IndexSet<String>,
890) -> Result<Network, D::Error>
891where
892    D: Deserializer<'de>,
893{
894    let DTestnetParameters {
895        network_name,
896        network_magic,
897        slow_start_interval,
898        target_difficulty_limit,
899        disable_pow,
900        genesis_hash,
901        activation_heights,
902        pre_nu6_funding_streams,
903        post_nu6_funding_streams,
904        funding_streams,
905        pre_blossom_halving_interval,
906        lockbox_disbursements,
907        checkpoints,
908        extend_funding_stream_addresses_as_required,
909        temporary_orchard_disabling_soft_fork_height,
910        should_allow_unshielded_coinbase_spends,
911    } = params;
912
913    // This is a Regtest-only consensus knob, so reject it rather than silently ignoring it.
914    if should_allow_unshielded_coinbase_spends.is_some() {
915        return Err(de::Error::custom(
916            "should_allow_unshielded_coinbase_spends is only supported on Regtest",
917        ));
918    }
919
920    let mut params_builder = testnet::Parameters::build();
921
922    if let Some(network_name) = network_name.clone() {
923        params_builder = params_builder
924            .with_network_name(network_name)
925            .map_err(de::Error::custom)?
926    }
927
928    if let Some(network_magic) = network_magic {
929        params_builder = params_builder
930            .with_network_magic(Magic(network_magic))
931            .map_err(de::Error::custom)?;
932    }
933
934    if let Some(genesis_hash) = genesis_hash {
935        params_builder = params_builder
936            .with_genesis_hash(genesis_hash)
937            .map_err(de::Error::custom)?;
938    }
939
940    if let Some(slow_start_interval) = slow_start_interval {
941        params_builder = params_builder
942            .with_slow_start_interval(slow_start_interval.try_into().map_err(de::Error::custom)?);
943    }
944
945    if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
946        params_builder = params_builder
947            .with_target_difficulty_limit(
948                target_difficulty_limit
949                    .parse::<U256>()
950                    .map_err(de::Error::custom)?,
951            )
952            .map_err(de::Error::custom)?;
953    }
954
955    if let Some(disable_pow) = disable_pow {
956        params_builder = params_builder.with_disable_pow(disable_pow);
957    }
958
959    // Retain default Testnet activation heights unless there's an empty [testnet_parameters.activation_heights] section.
960    if let Some(activation_heights) = activation_heights {
961        params_builder = params_builder
962            .with_activation_heights(activation_heights)
963            .map_err(de::Error::custom)?
964    }
965
966    if let Some(halving_interval) = pre_blossom_halving_interval {
967        params_builder = params_builder
968            .with_halving_interval(halving_interval.into())
969            .map_err(de::Error::custom)?
970    }
971
972    // Set configured funding streams after setting any parameters that affect the funding stream address period.
973    let mut funding_streams_vec = funding_streams.unwrap_or_default();
974
975    if let Some(funding_streams) = post_nu6_funding_streams {
976        funding_streams_vec.insert(0, funding_streams);
977    }
978
979    if let Some(funding_streams) = pre_nu6_funding_streams {
980        funding_streams_vec.insert(0, funding_streams);
981    }
982
983    if !funding_streams_vec.is_empty() {
984        params_builder = params_builder.with_funding_streams(funding_streams_vec);
985    }
986
987    if let Some(lockbox_disbursements) = lockbox_disbursements {
988        params_builder = params_builder.with_lockbox_disbursements(lockbox_disbursements);
989    }
990
991    params_builder = params_builder
992        .with_checkpoints(checkpoints)
993        .map_err(de::Error::custom)?;
994
995    if let Some(true) = extend_funding_stream_addresses_as_required {
996        params_builder = params_builder.extend_funding_streams();
997    }
998
999    // Retain the default soft-fork activation height unless one is configured.
1000    if let Some(height) = temporary_orchard_disabling_soft_fork_height {
1001        params_builder = params_builder.with_temporary_orchard_disabling_soft_fork_height(
1002            height.try_into().map_err(de::Error::custom)?,
1003        );
1004    }
1005
1006    // Return an error if the initial testnet peers includes any of the default initial Mainnet or Testnet
1007    // peers and the configured network parameters are incompatible with the default public Testnet.
1008    if !params_builder.is_compatible_with_default_parameters()
1009        && contains_default_initial_peers(initial_testnet_peers)
1010    {
1011        return Err(de::Error::custom(
1012            "cannot use default initials peers with incompatible testnet",
1013        ));
1014    };
1015
1016    // Return the default Testnet if no network name was configured and all parameters match the default Testnet
1017    if network_name.is_none() && params_builder == testnet::Parameters::build() {
1018        Ok(Network::new_default_testnet())
1019    } else {
1020        Ok(params_builder.to_network().map_err(de::Error::custom)?)
1021    }
1022}
1023
1024fn build_regtest_params(params: DTestnetParameters) -> RegtestParameters {
1025    let DTestnetParameters {
1026        activation_heights,
1027        pre_nu6_funding_streams,
1028        post_nu6_funding_streams,
1029        funding_streams,
1030        lockbox_disbursements,
1031        checkpoints,
1032        extend_funding_stream_addresses_as_required,
1033        should_allow_unshielded_coinbase_spends,
1034        ..
1035    } = params;
1036
1037    let mut funding_streams_vec = funding_streams.unwrap_or_default();
1038
1039    if let Some(funding_streams) = post_nu6_funding_streams {
1040        funding_streams_vec.insert(0, funding_streams);
1041    }
1042
1043    if let Some(funding_streams) = pre_nu6_funding_streams {
1044        funding_streams_vec.insert(0, funding_streams);
1045    }
1046
1047    RegtestParameters {
1048        activation_heights: activation_heights.unwrap_or_default(),
1049        funding_streams: Some(funding_streams_vec),
1050        lockbox_disbursements,
1051        checkpoints: Some(checkpoints),
1052        extend_funding_stream_addresses_as_required,
1053        should_allow_unshielded_coinbase_spends,
1054    }
1055}