1use 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
45const MAX_SINGLE_SEED_PEER_DNS_RETRIES: usize = 0;
55
56#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
58#[serde(deny_unknown_fields, default, into = "DConfig")]
59pub struct Config {
60 pub listen_addr: SocketAddr,
83
84 pub external_addr: Option<SocketAddr>,
91
92 pub network: Network,
94
95 pub initial_mainnet_peers: IndexSet<String>,
98
99 pub initial_testnet_peers: IndexSet<String>,
102
103 pub cache_dir: CacheDir,
156
157 pub peerset_initial_target_size: usize,
166
167 #[serde(with = "humantime_serde")]
175 pub crawl_new_peer_interval: Duration,
176
177 pub max_connections_per_ip: usize,
200}
201
202impl Config {
203 pub fn peerset_outbound_connection_limit(&self) -> usize {
227 self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
228 }
229
230 pub fn peerset_inbound_connection_limit(&self) -> usize {
238 self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
239 }
240
241 pub fn peerset_total_connection_limit(&self) -> usize {
244 self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
245 }
246
247 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 pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
262 let dns_peers =
264 Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
265
266 if self.network.is_regtest() {
267 dns_peers
269 .into_iter()
270 .filter(PeerSocketAddr::is_localhost)
271 .collect()
272 } else {
273 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 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 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 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 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 #[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 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 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 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 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 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 #[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 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 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 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 peer_list.sort();
514 let peer_data = peer_list.join("\n");
516
517 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 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 extend_funding_stream_addresses_as_required: Option<bool>,
616 temporary_orchard_disabling_soft_fork_height: Option<u32>,
621 should_allow_unshielded_coinbase_spends: Option<bool>,
623}
624
625#[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 #[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 ("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
870fn 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 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 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 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 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 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 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}