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