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,
196}
197
198impl Config {
199 pub fn peerset_outbound_connection_limit(&self) -> usize {
223 self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
224 }
225
226 pub fn peerset_inbound_connection_limit(&self) -> usize {
234 self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
235 }
236
237 pub fn peerset_total_connection_limit(&self) -> usize {
240 self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
241 }
242
243 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 pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
258 let dns_peers =
260 Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
261
262 if self.network.is_regtest() {
263 dns_peers
265 .into_iter()
266 .filter(PeerSocketAddr::is_localhost)
267 .collect()
268 } else {
269 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 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 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 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 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 #[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 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 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 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 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 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 #[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 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 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 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 peer_list.sort();
510 let peer_data = peer_list.join("\n");
512
513 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.str4d.xyz:8233",
550 "dnsseed.z.cash:8233",
551 "mainnet.seeder.shieldedinfra.net:8233",
552 "mainnet.seeder.zfnd.org:8233",
553 "seeder.zec.rocks:8233",
554 ]
555 .iter()
556 .map(|&s| String::from(s))
557 .collect();
558
559 let testnet_peers = [
560 "dnsseed.testnet.z.cash:18233",
561 "seeder.testnet.zec.rocks:18233",
562 "testnet.seeder.zfnd.org:18233",
563 ]
564 .iter()
565 .map(|&s| String::from(s))
566 .collect();
567
568 Config {
569 listen_addr: "[::]:8233"
570 .parse()
571 .expect("Hardcoded address should be parseable"),
572 external_addr: None,
573 network: Network::Mainnet,
574 initial_mainnet_peers: mainnet_peers,
575 initial_testnet_peers: testnet_peers,
576 cache_dir: CacheDir::default(),
577 crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
578
579 peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
587 max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
588 }
589 }
590}
591
592#[derive(Serialize, Deserialize)]
593#[serde(deny_unknown_fields)]
594struct DTestnetParameters {
595 network_name: Option<String>,
596 network_magic: Option<[u8; 4]>,
597 slow_start_interval: Option<u32>,
598 target_difficulty_limit: Option<String>,
599 disable_pow: Option<bool>,
600 genesis_hash: Option<String>,
601 activation_heights: Option<ConfiguredActivationHeights>,
602 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
603 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
604 funding_streams: Option<Vec<ConfiguredFundingStreams>>,
605 pre_blossom_halving_interval: Option<u32>,
606 lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
607 #[serde(default)]
608 checkpoints: ConfiguredCheckpoints,
609 extend_funding_stream_addresses_as_required: Option<bool>,
612 temporary_orchard_disabling_soft_fork_height: Option<u32>,
617 should_allow_unshielded_coinbase_spends: Option<bool>,
619}
620
621#[derive(Serialize, Deserialize)]
623#[serde(untagged)]
624enum DNetwork {
625 DefaultForKind(NetworkKind),
626 ConfiguredRegtest {
627 params: Box<DTestnetParameters>,
628
629 #[serde(default, skip_serializing)]
630 regtest: Option<bool>,
631 },
632 ConfiguredTestnet(Box<DTestnetParameters>),
633}
634
635impl Default for DNetwork {
636 fn default() -> Self {
637 DNetwork::DefaultForKind(NetworkKind::Mainnet)
638 }
639}
640
641#[derive(Serialize, Deserialize)]
642#[serde(deny_unknown_fields, default)]
643struct DConfig {
644 listen_addr: String,
645 external_addr: Option<String>,
646 network: DNetwork,
647
648 #[serde(default, skip_serializing_if = "Option::is_none")]
650 testnet_parameters: Option<DTestnetParameters>,
651
652 initial_mainnet_peers: IndexSet<String>,
653 initial_testnet_peers: IndexSet<String>,
654 cache_dir: CacheDir,
655 peerset_initial_target_size: usize,
656 #[serde(alias = "new_peer_interval", with = "humantime_serde")]
657 crawl_new_peer_interval: Duration,
658 max_connections_per_ip: Option<usize>,
659}
660
661impl Default for DConfig {
662 fn default() -> Self {
663 let config = Config::default();
664 Self {
665 listen_addr: "[::]".to_string(),
666 external_addr: None,
667 network: Default::default(),
668 testnet_parameters: None,
669 initial_mainnet_peers: config.initial_mainnet_peers,
670 initial_testnet_peers: config.initial_testnet_peers,
671 cache_dir: config.cache_dir,
672 peerset_initial_target_size: config.peerset_initial_target_size,
673 crawl_new_peer_interval: config.crawl_new_peer_interval,
674 max_connections_per_ip: Some(config.max_connections_per_ip),
675 }
676 }
677}
678
679impl From<Arc<testnet::Parameters>> for DTestnetParameters {
680 fn from(params: Arc<testnet::Parameters>) -> Self {
681 Self {
682 network_name: Some(params.network_name().to_string()),
683 network_magic: Some(params.network_magic().0),
684 slow_start_interval: Some(params.slow_start_interval().0),
685 target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
686 disable_pow: Some(params.disable_pow()),
687 genesis_hash: Some(params.genesis_hash().to_string()),
688 activation_heights: Some(params.activation_heights().into()),
689 pre_nu6_funding_streams: None,
690 post_nu6_funding_streams: None,
691 funding_streams: Some(params.funding_streams().iter().map(Into::into).collect()),
692 pre_blossom_halving_interval: Some(
693 params
694 .pre_blossom_halving_interval()
695 .try_into()
696 .expect("should convert"),
697 ),
698 lockbox_disbursements: Some(
699 params
700 .lockbox_disbursements()
701 .into_iter()
702 .map(Into::into)
703 .collect(),
704 ),
705 checkpoints: if params.checkpoints() == testnet::Parameters::default().checkpoints() {
706 ConfiguredCheckpoints::Default(true)
707 } else {
708 params.checkpoints().into()
709 },
710 extend_funding_stream_addresses_as_required: None,
711 temporary_orchard_disabling_soft_fork_height: params
712 .temporary_orchard_disabling_soft_fork_height()
713 .map(|height| height.0),
714 should_allow_unshielded_coinbase_spends: params
715 .is_regtest()
716 .then(|| params.should_allow_unshielded_coinbase_spends()),
717 }
718 }
719}
720
721impl From<Config> for DConfig {
722 fn from(
723 Config {
724 listen_addr,
725 external_addr,
726 network,
727 initial_mainnet_peers,
728 initial_testnet_peers,
729 cache_dir,
730 peerset_initial_target_size,
731 crawl_new_peer_interval,
732 max_connections_per_ip,
733 }: Config,
734 ) -> Self {
735 let dnetwork = match network.kind() {
736 NetworkKind::Testnet => match network
737 .parameters()
738 .filter(|params| !params.is_default_testnet())
739 .map(Into::into)
740 {
741 Some(params) => DNetwork::ConfiguredTestnet(Box::new(params)),
742 None => DNetwork::DefaultForKind(NetworkKind::Testnet),
743 },
744
745 NetworkKind::Regtest => match network.parameters().map(Into::into) {
746 Some(params) => DNetwork::ConfiguredRegtest {
747 params: Box::new(params),
748 regtest: Some(true),
749 },
750 None => DNetwork::DefaultForKind(NetworkKind::Regtest),
751 },
752
753 other_kind => DNetwork::DefaultForKind(other_kind),
754 };
755
756 DConfig {
757 listen_addr: listen_addr.to_string(),
758 external_addr: external_addr.map(|addr| addr.to_string()),
759 network: dnetwork,
760 testnet_parameters: None,
761 initial_mainnet_peers,
762 initial_testnet_peers,
763 cache_dir,
764 peerset_initial_target_size,
765 crawl_new_peer_interval,
766 max_connections_per_ip: Some(max_connections_per_ip),
767 }
768 }
769}
770
771impl<'de> Deserialize<'de> for Config {
772 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
773 where
774 D: Deserializer<'de>,
775 {
776 let DConfig {
777 listen_addr,
778 external_addr,
779 network: dnetwork,
780 testnet_parameters,
781 initial_mainnet_peers,
782 initial_testnet_peers,
783 cache_dir,
784 peerset_initial_target_size,
785 crawl_new_peer_interval,
786 max_connections_per_ip,
787 } = DConfig::deserialize(deserializer)?;
788
789 let network = match (dnetwork, testnet_parameters) {
790 (DNetwork::ConfiguredTestnet(params), _) => {
791 build_configured_testnet::<D>(*params, &initial_testnet_peers)?
792 }
793 (DNetwork::ConfiguredRegtest { params, .. }, _) => {
794 Network::new_regtest(build_regtest_params(*params))
795 }
796 (DNetwork::DefaultForKind(NetworkKind::Mainnet), _) => Network::Mainnet,
797 (DNetwork::DefaultForKind(NetworkKind::Testnet), Some(params)) => {
798 build_configured_testnet::<D>(params, &initial_testnet_peers)?
799 }
800 (DNetwork::DefaultForKind(NetworkKind::Testnet), None) => {
801 Network::new_default_testnet()
802 }
803 (DNetwork::DefaultForKind(NetworkKind::Regtest), Some(params)) => {
804 Network::new_regtest(build_regtest_params(params))
805 }
806 (DNetwork::DefaultForKind(NetworkKind::Regtest), None) => {
807 Network::new_regtest(Default::default())
808 }
809 };
810
811 let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
812 Ok(socket) => Ok(socket),
813 Err(_) => match listen_addr.parse::<IpAddr>() {
814 Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
815 Err(err) => Err(de::Error::custom(format!(
816 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
817 ))),
818 },
819 }?;
820
821 let external_socket_addr = if let Some(address) = &external_addr {
822 match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
823 Ok(socket) => Ok(Some(socket)),
824 Err(_) => match address.parse::<IpAddr>() {
825 Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
826 Err(err) => Err(de::Error::custom(format!(
827 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
828 ))),
829 },
830 }?
831 } else {
832 None
833 };
834
835 let [max_connections_per_ip, peerset_initial_target_size] = [
836 ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
837 ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
840 ].map(|(field_name, non_zero_config_field, default_config_value)| {
841 if non_zero_config_field == Some(0) {
842 warn!(
843 ?field_name,
844 ?non_zero_config_field,
845 "{field_name} should be greater than 0, using default value of {default_config_value} instead"
846 );
847 }
848
849 non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
850 });
851
852 Ok(Config {
853 listen_addr: canonical_socket_addr(listen_addr),
854 external_addr: external_socket_addr,
855 network,
856 initial_mainnet_peers,
857 initial_testnet_peers,
858 cache_dir,
859 peerset_initial_target_size,
860 crawl_new_peer_interval,
861 max_connections_per_ip,
862 })
863 }
864}
865
866fn contains_default_initial_peers(initial_peers: &IndexSet<String>) -> bool {
870 let Config {
871 initial_mainnet_peers: mut default_initial_peers,
872 initial_testnet_peers: default_initial_testnet_peers,
873 ..
874 } = Config::default();
875 default_initial_peers.extend(default_initial_testnet_peers);
876
877 initial_peers
878 .intersection(&default_initial_peers)
879 .next()
880 .is_some()
881}
882
883fn build_configured_testnet<'de, D>(
884 params: DTestnetParameters,
885 initial_testnet_peers: &IndexSet<String>,
886) -> Result<Network, D::Error>
887where
888 D: Deserializer<'de>,
889{
890 let DTestnetParameters {
891 network_name,
892 network_magic,
893 slow_start_interval,
894 target_difficulty_limit,
895 disable_pow,
896 genesis_hash,
897 activation_heights,
898 pre_nu6_funding_streams,
899 post_nu6_funding_streams,
900 funding_streams,
901 pre_blossom_halving_interval,
902 lockbox_disbursements,
903 checkpoints,
904 extend_funding_stream_addresses_as_required,
905 temporary_orchard_disabling_soft_fork_height,
906 should_allow_unshielded_coinbase_spends,
907 } = params;
908
909 if should_allow_unshielded_coinbase_spends.is_some() {
911 return Err(de::Error::custom(
912 "should_allow_unshielded_coinbase_spends is only supported on Regtest",
913 ));
914 }
915
916 let mut params_builder = testnet::Parameters::build();
917
918 if let Some(network_name) = network_name.clone() {
919 params_builder = params_builder
920 .with_network_name(network_name)
921 .map_err(de::Error::custom)?
922 }
923
924 if let Some(network_magic) = network_magic {
925 params_builder = params_builder
926 .with_network_magic(Magic(network_magic))
927 .map_err(de::Error::custom)?;
928 }
929
930 if let Some(genesis_hash) = genesis_hash {
931 params_builder = params_builder
932 .with_genesis_hash(genesis_hash)
933 .map_err(de::Error::custom)?;
934 }
935
936 if let Some(slow_start_interval) = slow_start_interval {
937 params_builder = params_builder
938 .with_slow_start_interval(slow_start_interval.try_into().map_err(de::Error::custom)?);
939 }
940
941 if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
942 params_builder = params_builder
943 .with_target_difficulty_limit(
944 target_difficulty_limit
945 .parse::<U256>()
946 .map_err(de::Error::custom)?,
947 )
948 .map_err(de::Error::custom)?;
949 }
950
951 if let Some(disable_pow) = disable_pow {
952 params_builder = params_builder.with_disable_pow(disable_pow);
953 }
954
955 if let Some(activation_heights) = activation_heights {
957 params_builder = params_builder
958 .with_activation_heights(activation_heights)
959 .map_err(de::Error::custom)?
960 }
961
962 if let Some(halving_interval) = pre_blossom_halving_interval {
963 params_builder = params_builder
964 .with_halving_interval(halving_interval.into())
965 .map_err(de::Error::custom)?
966 }
967
968 let mut funding_streams_vec = funding_streams.unwrap_or_default();
970
971 if let Some(funding_streams) = post_nu6_funding_streams {
972 funding_streams_vec.insert(0, funding_streams);
973 }
974
975 if let Some(funding_streams) = pre_nu6_funding_streams {
976 funding_streams_vec.insert(0, funding_streams);
977 }
978
979 if !funding_streams_vec.is_empty() {
980 params_builder = params_builder.with_funding_streams(funding_streams_vec);
981 }
982
983 if let Some(lockbox_disbursements) = lockbox_disbursements {
984 params_builder = params_builder.with_lockbox_disbursements(lockbox_disbursements);
985 }
986
987 params_builder = params_builder
988 .with_checkpoints(checkpoints)
989 .map_err(de::Error::custom)?;
990
991 if let Some(true) = extend_funding_stream_addresses_as_required {
992 params_builder = params_builder.extend_funding_streams();
993 }
994
995 if let Some(height) = temporary_orchard_disabling_soft_fork_height {
997 params_builder = params_builder.with_temporary_orchard_disabling_soft_fork_height(
998 height.try_into().map_err(de::Error::custom)?,
999 );
1000 }
1001
1002 if !params_builder.is_compatible_with_default_parameters()
1005 && contains_default_initial_peers(initial_testnet_peers)
1006 {
1007 return Err(de::Error::custom(
1008 "cannot use default initials peers with incompatible testnet",
1009 ));
1010 };
1011
1012 if network_name.is_none() && params_builder == testnet::Parameters::build() {
1014 Ok(Network::new_default_testnet())
1015 } else {
1016 Ok(params_builder.to_network().map_err(de::Error::custom)?)
1017 }
1018}
1019
1020fn build_regtest_params(params: DTestnetParameters) -> RegtestParameters {
1021 let DTestnetParameters {
1022 activation_heights,
1023 pre_nu6_funding_streams,
1024 post_nu6_funding_streams,
1025 funding_streams,
1026 lockbox_disbursements,
1027 checkpoints,
1028 extend_funding_stream_addresses_as_required,
1029 should_allow_unshielded_coinbase_spends,
1030 ..
1031 } = params;
1032
1033 let mut funding_streams_vec = funding_streams.unwrap_or_default();
1034
1035 if let Some(funding_streams) = post_nu6_funding_streams {
1036 funding_streams_vec.insert(0, funding_streams);
1037 }
1038
1039 if let Some(funding_streams) = pre_nu6_funding_streams {
1040 funding_streams_vec.insert(0, funding_streams);
1041 }
1042
1043 RegtestParameters {
1044 activation_heights: activation_heights.unwrap_or_default(),
1045 funding_streams: Some(funding_streams_vec),
1046 lockbox_disbursements,
1047 checkpoints: Some(checkpoints),
1048 extend_funding_stream_addresses_as_required,
1049 should_allow_unshielded_coinbase_spends,
1050 }
1051}