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 /// 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 ]
553 .iter()
554 .map(|&s| String::from(s))
555 .collect();
556
557 let testnet_peers = [
558 "dnsseed.testnet.z.cash:18233",
559 "testnet.seeder.zfnd.org:18233",
560 ]
561 .iter()
562 .map(|&s| String::from(s))
563 .collect();
564
565 Config {
566 listen_addr: "[::]:8233"
567 .parse()
568 .expect("Hardcoded address should be parseable"),
569 external_addr: None,
570 network: Network::Mainnet,
571 initial_mainnet_peers: mainnet_peers,
572 initial_testnet_peers: testnet_peers,
573 cache_dir: CacheDir::default(),
574 crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
575
576 // # Security
577 //
578 // The default peerset target size should be large enough to ensure
579 // nodes have a reliable set of peers.
580 //
581 // But Zebra should only make a small number of initial outbound connections,
582 // so that idle peers don't use too many connection slots.
583 peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
584 max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
585 }
586 }
587}
588
589#[derive(Serialize, Deserialize)]
590#[serde(deny_unknown_fields)]
591struct DTestnetParameters {
592 network_name: Option<String>,
593 network_magic: Option<[u8; 4]>,
594 slow_start_interval: Option<u32>,
595 target_difficulty_limit: Option<String>,
596 disable_pow: Option<bool>,
597 genesis_hash: Option<String>,
598 activation_heights: Option<ConfiguredActivationHeights>,
599 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
600 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
601 funding_streams: Option<Vec<ConfiguredFundingStreams>>,
602 pre_blossom_halving_interval: Option<u32>,
603 lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
604 #[serde(default)]
605 checkpoints: ConfiguredCheckpoints,
606 /// If `true`, automatically repeats configured funding stream addresses to fill
607 /// all required periods.
608 extend_funding_stream_addresses_as_required: Option<bool>,
609}
610
611#[derive(Serialize, Deserialize)]
612#[serde(deny_unknown_fields, default)]
613struct DConfig {
614 listen_addr: String,
615 external_addr: Option<String>,
616 network: NetworkKind,
617 testnet_parameters: Option<DTestnetParameters>,
618 initial_mainnet_peers: IndexSet<String>,
619 initial_testnet_peers: IndexSet<String>,
620 cache_dir: CacheDir,
621 peerset_initial_target_size: usize,
622 #[serde(alias = "new_peer_interval", with = "humantime_serde")]
623 crawl_new_peer_interval: Duration,
624 max_connections_per_ip: Option<usize>,
625}
626
627impl Default for DConfig {
628 fn default() -> Self {
629 let config = Config::default();
630 Self {
631 listen_addr: "[::]".to_string(),
632 external_addr: None,
633 network: Default::default(),
634 testnet_parameters: None,
635 initial_mainnet_peers: config.initial_mainnet_peers,
636 initial_testnet_peers: config.initial_testnet_peers,
637 cache_dir: config.cache_dir,
638 peerset_initial_target_size: config.peerset_initial_target_size,
639 crawl_new_peer_interval: config.crawl_new_peer_interval,
640 max_connections_per_ip: Some(config.max_connections_per_ip),
641 }
642 }
643}
644
645impl From<Arc<testnet::Parameters>> for DTestnetParameters {
646 fn from(params: Arc<testnet::Parameters>) -> Self {
647 Self {
648 network_name: Some(params.network_name().to_string()),
649 network_magic: Some(params.network_magic().0),
650 slow_start_interval: Some(params.slow_start_interval().0),
651 target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
652 disable_pow: Some(params.disable_pow()),
653 genesis_hash: Some(params.genesis_hash().to_string()),
654 activation_heights: Some(params.activation_heights().into()),
655 pre_nu6_funding_streams: None,
656 post_nu6_funding_streams: None,
657 funding_streams: Some(params.funding_streams().iter().map(Into::into).collect()),
658 pre_blossom_halving_interval: Some(
659 params
660 .pre_blossom_halving_interval()
661 .try_into()
662 .expect("should convert"),
663 ),
664 lockbox_disbursements: Some(
665 params
666 .lockbox_disbursements()
667 .into_iter()
668 .map(Into::into)
669 .collect(),
670 ),
671 checkpoints: if params.checkpoints() == testnet::Parameters::default().checkpoints() {
672 ConfiguredCheckpoints::Default(true)
673 } else {
674 params.checkpoints().into()
675 },
676 extend_funding_stream_addresses_as_required: None,
677 }
678 }
679}
680
681impl From<Config> for DConfig {
682 fn from(
683 Config {
684 listen_addr,
685 external_addr,
686 network,
687 initial_mainnet_peers,
688 initial_testnet_peers,
689 cache_dir,
690 peerset_initial_target_size,
691 crawl_new_peer_interval,
692 max_connections_per_ip,
693 }: Config,
694 ) -> Self {
695 let testnet_parameters = network
696 .parameters()
697 .filter(|params| !params.is_default_testnet())
698 .map(Into::into);
699
700 DConfig {
701 listen_addr: listen_addr.to_string(),
702 external_addr: external_addr.map(|addr| addr.to_string()),
703 network: network.into(),
704 testnet_parameters,
705 initial_mainnet_peers,
706 initial_testnet_peers,
707 cache_dir,
708 peerset_initial_target_size,
709 crawl_new_peer_interval,
710 max_connections_per_ip: Some(max_connections_per_ip),
711 }
712 }
713}
714
715impl<'de> Deserialize<'de> for Config {
716 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
717 where
718 D: Deserializer<'de>,
719 {
720 let DConfig {
721 listen_addr,
722 external_addr,
723 network: network_kind,
724 testnet_parameters,
725 initial_mainnet_peers,
726 initial_testnet_peers,
727 cache_dir,
728 peerset_initial_target_size,
729 crawl_new_peer_interval,
730 max_connections_per_ip,
731 } = DConfig::deserialize(deserializer)?;
732
733 /// Accepts an [`IndexSet`] of initial peers,
734 ///
735 /// Returns true if any of them are the default Testnet or Mainnet initial peers.
736 fn contains_default_initial_peers(initial_peers: &IndexSet<String>) -> bool {
737 let Config {
738 initial_mainnet_peers: mut default_initial_peers,
739 initial_testnet_peers: default_initial_testnet_peers,
740 ..
741 } = Config::default();
742 default_initial_peers.extend(default_initial_testnet_peers);
743
744 initial_peers
745 .intersection(&default_initial_peers)
746 .next()
747 .is_some()
748 }
749
750 let network = match (network_kind, testnet_parameters) {
751 (NetworkKind::Mainnet, _) => Network::Mainnet,
752 (NetworkKind::Testnet, None) => Network::new_default_testnet(),
753 (NetworkKind::Regtest, testnet_parameters) => {
754 let params = testnet_parameters
755 .map(
756 |DTestnetParameters {
757 activation_heights,
758 pre_nu6_funding_streams,
759 post_nu6_funding_streams,
760 funding_streams,
761 lockbox_disbursements,
762 checkpoints,
763 extend_funding_stream_addresses_as_required,
764 ..
765 }| {
766 let mut funding_streams_vec = funding_streams.unwrap_or_default();
767 if let Some(funding_streams) = post_nu6_funding_streams {
768 funding_streams_vec.insert(0, funding_streams);
769 }
770 if let Some(funding_streams) = pre_nu6_funding_streams {
771 funding_streams_vec.insert(0, funding_streams);
772 }
773
774 RegtestParameters {
775 activation_heights: activation_heights.unwrap_or_default(),
776 funding_streams: Some(funding_streams_vec),
777 lockbox_disbursements,
778 checkpoints: Some(checkpoints),
779 extend_funding_stream_addresses_as_required,
780 }
781 },
782 )
783 .unwrap_or_default();
784
785 Network::new_regtest(params)
786 }
787 (
788 NetworkKind::Testnet,
789 Some(DTestnetParameters {
790 network_name,
791 network_magic,
792 slow_start_interval,
793 target_difficulty_limit,
794 disable_pow,
795 genesis_hash,
796 activation_heights,
797 pre_nu6_funding_streams,
798 post_nu6_funding_streams,
799 funding_streams,
800 pre_blossom_halving_interval,
801 lockbox_disbursements,
802 checkpoints,
803 extend_funding_stream_addresses_as_required,
804 }),
805 ) => {
806 let mut params_builder = testnet::Parameters::build();
807
808 if let Some(network_name) = network_name.clone() {
809 params_builder = params_builder.with_network_name(network_name)
810 }
811
812 if let Some(network_magic) = network_magic {
813 params_builder = params_builder.with_network_magic(Magic(network_magic));
814 }
815
816 if let Some(genesis_hash) = genesis_hash {
817 params_builder = params_builder.with_genesis_hash(genesis_hash);
818 }
819
820 if let Some(slow_start_interval) = slow_start_interval {
821 params_builder = params_builder.with_slow_start_interval(
822 slow_start_interval.try_into().map_err(de::Error::custom)?,
823 );
824 }
825
826 if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
827 params_builder = params_builder.with_target_difficulty_limit(
828 target_difficulty_limit
829 .parse::<U256>()
830 .map_err(de::Error::custom)?,
831 );
832 }
833
834 if let Some(disable_pow) = disable_pow {
835 params_builder = params_builder.with_disable_pow(disable_pow);
836 }
837
838 // Retain default Testnet activation heights unless there's an empty [testnet_parameters.activation_heights] section.
839 if let Some(activation_heights) = activation_heights {
840 params_builder = params_builder.with_activation_heights(activation_heights)
841 }
842
843 if let Some(halving_interval) = pre_blossom_halving_interval {
844 params_builder = params_builder.with_halving_interval(halving_interval.into())
845 }
846
847 // Set configured funding streams after setting any parameters that affect the funding stream address period.
848 let mut funding_streams_vec = funding_streams.unwrap_or_default();
849
850 if let Some(funding_streams) = post_nu6_funding_streams {
851 funding_streams_vec.insert(0, funding_streams);
852 }
853
854 if let Some(funding_streams) = pre_nu6_funding_streams {
855 funding_streams_vec.insert(0, funding_streams);
856 }
857
858 if !funding_streams_vec.is_empty() {
859 params_builder = params_builder.with_funding_streams(funding_streams_vec);
860 }
861
862 if let Some(lockbox_disbursements) = lockbox_disbursements {
863 params_builder =
864 params_builder.with_lockbox_disbursements(lockbox_disbursements);
865 }
866
867 params_builder = params_builder.with_checkpoints(checkpoints);
868
869 if let Some(true) = extend_funding_stream_addresses_as_required {
870 params_builder = params_builder.extend_funding_streams();
871 }
872
873 // Return an error if the initial testnet peers includes any of the default initial Mainnet or Testnet
874 // peers and the configured network parameters are incompatible with the default public Testnet.
875 if !params_builder.is_compatible_with_default_parameters()
876 && contains_default_initial_peers(&initial_testnet_peers)
877 {
878 return Err(de::Error::custom(
879 "cannot use default initials peers with incompatible testnet",
880 ));
881 };
882
883 // Return the default Testnet if no network name was configured and all parameters match the default Testnet
884 if network_name.is_none() && params_builder == testnet::Parameters::build() {
885 Network::new_default_testnet()
886 } else {
887 params_builder.to_network()
888 }
889 }
890 };
891
892 let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
893 Ok(socket) => Ok(socket),
894 Err(_) => match listen_addr.parse::<IpAddr>() {
895 Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
896 Err(err) => Err(de::Error::custom(format!(
897 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
898 ))),
899 },
900 }?;
901
902 let external_socket_addr = if let Some(address) = &external_addr {
903 match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
904 Ok(socket) => Ok(Some(socket)),
905 Err(_) => match address.parse::<IpAddr>() {
906 Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
907 Err(err) => Err(de::Error::custom(format!(
908 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
909 ))),
910 },
911 }?
912 } else {
913 None
914 };
915
916 let [max_connections_per_ip, peerset_initial_target_size] = [
917 ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
918 // If we want Zebra to operate with no network,
919 // we should implement a `zebrad` command that doesn't use `zebra-network`.
920 ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
921 ].map(|(field_name, non_zero_config_field, default_config_value)| {
922 if non_zero_config_field == Some(0) {
923 warn!(
924 ?field_name,
925 ?non_zero_config_field,
926 "{field_name} should be greater than 0, using default value of {default_config_value} instead"
927 );
928 }
929
930 non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
931 });
932
933 Ok(Config {
934 listen_addr: canonical_socket_addr(listen_addr),
935 external_addr: external_socket_addr,
936 network,
937 initial_mainnet_peers,
938 initial_testnet_peers,
939 cache_dir,
940 peerset_initial_target_size,
941 crawl_new_peer_interval,
942 max_connections_per_ip,
943 })
944 }
945}