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 constants::magics,
20 subsidy::TESTNET_INITIAL_NSM_VALUE_BALANCE,
21 testnet::{
22 self, ConfiguredActivationHeights, ConfiguredCheckpoints, ConfiguredFundingStreams,
23 ConfiguredLockboxDisbursement, RegtestParameters,
24 },
25 Magic, Network, NetworkKind,
26 },
27 work::difficulty::U256,
28};
29
30use crate::{
31 constants::{
32 DEFAULT_CRAWL_NEW_PEER_INTERVAL, DEFAULT_MAX_CONNS_PER_IP,
33 DEFAULT_PEERSET_INITIAL_TARGET_SIZE, DNS_LOOKUP_TIMEOUT, INBOUND_PEER_LIMIT_MULTIPLIER,
34 MAX_PEER_DISK_CACHE_SIZE, OUTBOUND_PEER_LIMIT_MULTIPLIER,
35 },
36 protocol::external::{canonical_peer_addr, canonical_socket_addr},
37 BoxError, PeerSocketAddr,
38};
39
40mod cache_dir;
41
42#[cfg(test)]
43mod tests;
44
45pub use cache_dir::CacheDir;
46
47const MAX_SINGLE_SEED_PEER_DNS_RETRIES: usize = 0;
57
58const DEFAULT_MAINNET_PEERS: [&str; 5] = [
60 "dnsseed.str4d.xyz:8233",
61 "dnsseed.z.cash:8233",
62 "mainnet.seeder.shieldedinfra.net:8233",
63 "mainnet.seeder.zfnd.org:8233",
64 "seeder.zec.rocks:8233",
65];
66const DEFAULT_TESTNET_PEERS: [&str; 3] = [
67 "dnsseed.testnet.z.cash:18233",
68 "seeder.testnet.zec.rocks:18233",
69 "testnet.seeder.zfnd.org:18233",
70];
71
72#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
74#[serde(deny_unknown_fields, default, into = "DConfig")]
75pub struct Config {
76 pub listen_addr: SocketAddr,
99
100 pub external_addr: Option<SocketAddr>,
107
108 pub network: Network,
110
111 pub initial_mainnet_peers: IndexSet<String>,
114
115 pub initial_testnet_peers: IndexSet<String>,
118
119 pub cache_dir: CacheDir,
172
173 pub peerset_initial_target_size: usize,
182
183 #[serde(with = "humantime_serde")]
191 pub crawl_new_peer_interval: Duration,
192
193 pub max_connections_per_ip: usize,
216}
217
218impl Config {
219 pub fn peerset_outbound_connection_limit(&self) -> usize {
243 self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
244 }
245
246 pub fn peerset_inbound_connection_limit(&self) -> usize {
254 self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
255 }
256
257 pub fn peerset_total_connection_limit(&self) -> usize {
260 self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
261 }
262
263 pub fn initial_peer_hostnames(&self) -> IndexSet<String> {
265 match &self.network {
266 Network::Mainnet => self.initial_mainnet_peers.clone(),
267 Network::Testnet(params) if params.is_regtest() => self
268 .initial_testnet_peers
269 .iter()
270 .filter(|peer| !is_default_initial_peer(peer))
271 .cloned()
272 .collect(),
273 Network::Testnet(_params) => self.initial_testnet_peers.clone(),
274 }
275 }
276
277 pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
284 let dns_peers =
286 Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
287
288 if self.network.is_regtest() {
289 dns_peers
291 .into_iter()
292 .filter(PeerSocketAddr::is_localhost)
293 .collect()
294 } else {
295 let disk_peers = self.load_peer_cache().await.unwrap_or_default();
297
298 dns_peers.into_iter().chain(disk_peers).collect()
299 }
300 }
301
302 async fn resolve_peers(peers: &HashSet<String>) -> HashSet<PeerSocketAddr> {
308 use futures::stream::StreamExt;
309
310 if peers.is_empty() {
311 warn!(
312 "no initial peers in the network config. \
313 Hint: you must configure at least one peer IP or DNS seeder to run Zebra, \
314 give it some previously cached peer IP addresses on disk, \
315 or make sure Zebra's listener port gets inbound connections."
316 );
317 return HashSet::new();
318 }
319
320 loop {
321 let peer_addresses = peers
326 .iter()
327 .map(|s| Config::resolve_host(s, MAX_SINGLE_SEED_PEER_DNS_RETRIES))
328 .collect::<futures::stream::FuturesUnordered<_>>()
329 .concat()
330 .await;
331
332 if peer_addresses.is_empty() {
333 tracing::info!(
334 ?peers,
335 ?peer_addresses,
336 "empty peer list after DNS resolution, retrying after {} seconds",
337 DNS_LOOKUP_TIMEOUT.as_secs(),
338 );
339 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
340 } else {
341 return peer_addresses;
342 }
343 }
344 }
345
346 async fn resolve_host(host: &str, max_retries: usize) -> HashSet<PeerSocketAddr> {
355 for retries in 0..=max_retries {
356 if let Ok(addresses) = Config::resolve_host_once(host).await {
357 return addresses;
358 }
359
360 if retries < max_retries {
361 tracing::info!(
362 ?host,
363 previous_attempts = ?(retries + 1),
364 "Waiting {DNS_LOOKUP_TIMEOUT:?} to retry seed peer DNS resolution",
365 );
366 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
367 } else {
368 tracing::info!(
369 ?host,
370 attempts = ?(retries + 1),
371 "Seed peer DNS resolution failed, checking for addresses from other seed peers",
372 );
373 }
374 }
375
376 HashSet::new()
377 }
378
379 async fn resolve_host_once(host: &str) -> Result<HashSet<PeerSocketAddr>, BoxError> {
388 let fut = tokio::net::lookup_host(host);
389 let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
390
391 match fut.await {
392 Ok(Ok(ip_addrs)) => {
393 let ip_addrs: Vec<PeerSocketAddr> = ip_addrs.map(canonical_peer_addr).collect();
394
395 #[cfg(not(test))]
397 info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
398 #[cfg(test)]
399 debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
400
401 for ip in &ip_addrs {
402 metrics::counter!(
408 "zcash.net.peers.initial",
409 "seed" => host.to_string(),
410 "remote_ip" => ip.to_string()
411 )
412 .increment(1);
413 }
414
415 Ok(ip_addrs.into_iter().collect())
416 }
417 Ok(Err(e)) if e.kind() == ErrorKind::InvalidInput => {
418 panic!(
420 "Invalid peer IP address in Zebra config: addresses must have ports:\n\
421 resolving {host:?} returned {e:?}"
422 );
423 }
424 Ok(Err(e)) => {
425 tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
426 Err(e.into())
427 }
428 Err(e) => {
429 tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
430 Err(e.into())
431 }
432 }
433 }
434
435 pub async fn load_peer_cache(&self) -> io::Result<HashSet<PeerSocketAddr>> {
437 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
438 return Ok(HashSet::new());
439 };
440
441 let peer_list = match fs::read_to_string(&peer_cache_file).await {
442 Ok(peer_list) => peer_list,
443 Err(peer_list_error) => {
444 if peer_list_error.kind() == ErrorKind::NotFound {
446 return Ok(HashSet::new());
447 } else {
448 info!(
449 ?peer_list_error,
450 "could not load cached peer list, using default seed peers"
451 );
452 return Err(peer_list_error);
453 }
454 }
455 };
456
457 let peer_list: HashSet<PeerSocketAddr> = peer_list
460 .lines()
461 .filter_map(|peer| {
462 peer.parse()
463 .map_err(|peer_parse_error| {
464 info!(
465 ?peer_parse_error,
466 "invalid peer address in cached peer list, skipping"
467 );
468 peer_parse_error
469 })
470 .ok()
471 })
472 .collect();
473
474 #[cfg(not(test))]
476 info!(
477 cached_ip_count = ?peer_list.len(),
478 ?peer_cache_file,
479 "loaded cached peer IP addresses"
480 );
481 #[cfg(test)]
482 debug!(
483 cached_ip_count = ?peer_list.len(),
484 ?peer_cache_file,
485 "loaded cached peer IP addresses"
486 );
487
488 for ip in &peer_list {
489 metrics::counter!(
495 "zcash.net.peers.initial",
496 "cache" => peer_cache_file.display().to_string(),
497 "remote_ip" => ip.to_string()
498 )
499 .increment(1);
500 }
501
502 Ok(peer_list)
503 }
504
505 pub async fn update_peer_cache(&self, peer_list: HashSet<PeerSocketAddr>) -> io::Result<()> {
513 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
514 return Ok(());
515 };
516
517 if peer_list.is_empty() {
518 info!(
519 ?peer_cache_file,
520 "cacheable peer list was empty, keeping previous cache"
521 );
522 return Ok(());
523 }
524
525 let mut peer_list: Vec<String> = peer_list
527 .iter()
528 .take(MAX_PEER_DISK_CACHE_SIZE)
529 .map(|redacted_peer| redacted_peer.remove_socket_addr_privacy().to_string())
530 .collect();
531 peer_list.sort();
536 let peer_data = peer_list.join("\n");
538
539 let span = Span::current();
542 let write_result = tokio::task::spawn_blocking(move || {
543 span.in_scope(move || atomic_write(peer_cache_file, peer_data.as_bytes()))
544 })
545 .await
546 .expect("could not write the peer cache file")?;
547
548 match write_result {
549 Ok(peer_cache_file) => {
550 info!(
551 cached_ip_count = ?peer_list.len(),
552 ?peer_cache_file,
553 "updated cached peer IP addresses"
554 );
555
556 for ip in &peer_list {
557 metrics::counter!(
558 "zcash.net.peers.cache",
559 "cache" => peer_cache_file.display().to_string(),
560 "remote_ip" => ip.to_string()
561 )
562 .increment(1);
563 }
564
565 Ok(())
566 }
567 Err(error) => Err(error.error),
568 }
569 }
570}
571
572impl Default for Config {
573 fn default() -> Config {
574 let mainnet_peers = DEFAULT_MAINNET_PEERS
575 .into_iter()
576 .map(String::from)
577 .collect();
578 let testnet_peers = DEFAULT_TESTNET_PEERS
579 .into_iter()
580 .map(String::from)
581 .collect();
582
583 Config {
584 listen_addr: "[::]:8233"
585 .parse()
586 .expect("Hardcoded address should be parseable"),
587 external_addr: None,
588 network: Network::Mainnet,
589 initial_mainnet_peers: mainnet_peers,
590 initial_testnet_peers: testnet_peers,
591 cache_dir: CacheDir::default(),
592 crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
593
594 peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
602 max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
603 }
604 }
605}
606
607#[derive(Serialize, Deserialize)]
608#[serde(deny_unknown_fields)]
609struct DTestnetParameters {
610 network_name: Option<String>,
611 network_magic: Option<[u8; 4]>,
612 slow_start_interval: Option<u32>,
613 target_difficulty_limit: Option<String>,
614 disable_pow: Option<bool>,
615 genesis_hash: Option<String>,
616 activation_heights: Option<ConfiguredActivationHeights>,
617 nsm_reissuance_height: Option<zebra_chain::block::Height>,
619 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
620 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
621 funding_streams: Option<Vec<ConfiguredFundingStreams>>,
624 pre_blossom_halving_interval: Option<u32>,
628 lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
629 #[serde(default)]
630 checkpoints: ConfiguredCheckpoints,
631 extend_funding_stream_addresses_as_required: Option<bool>,
634 temporary_orchard_disabling_soft_fork_height: Option<u32>,
639 should_allow_unshielded_coinbase_spends: Option<bool>,
641
642 initial_nsm_value_balance:
645 Option<zebra_chain::amount::Amount<zebra_chain::amount::NonNegative>>,
646}
647
648#[derive(Serialize, Deserialize)]
650#[serde(untagged)]
651enum DNetwork {
652 DefaultForKind(NetworkKind),
653 ConfiguredRegtest {
654 params: Box<DTestnetParameters>,
655
656 #[serde(default, skip_serializing)]
657 regtest: Option<bool>,
658 },
659 ConfiguredTestnet(Box<DTestnetParameters>),
660}
661
662impl Default for DNetwork {
663 fn default() -> Self {
664 DNetwork::DefaultForKind(NetworkKind::Mainnet)
665 }
666}
667
668#[derive(Serialize, Deserialize)]
669#[serde(deny_unknown_fields, default)]
670struct DConfig {
671 listen_addr: String,
672 external_addr: Option<String>,
673 network: DNetwork,
674
675 #[serde(default, skip_serializing_if = "Option::is_none")]
677 testnet_parameters: Option<DTestnetParameters>,
678
679 initial_mainnet_peers: IndexSet<String>,
680 initial_testnet_peers: IndexSet<String>,
681 cache_dir: CacheDir,
682 peerset_initial_target_size: usize,
683 #[serde(alias = "new_peer_interval", with = "humantime_serde")]
684 crawl_new_peer_interval: Duration,
685 max_connections_per_ip: Option<usize>,
686}
687
688impl Default for DConfig {
689 fn default() -> Self {
690 let config = Config::default();
691 Self {
692 listen_addr: "[::]".to_string(),
693 external_addr: None,
694 network: Default::default(),
695 testnet_parameters: None,
696 initial_mainnet_peers: config.initial_mainnet_peers,
697 initial_testnet_peers: config.initial_testnet_peers,
698 cache_dir: config.cache_dir,
699 peerset_initial_target_size: config.peerset_initial_target_size,
700 crawl_new_peer_interval: config.crawl_new_peer_interval,
701 max_connections_per_ip: Some(config.max_connections_per_ip),
702 }
703 }
704}
705
706impl From<Arc<testnet::Parameters>> for DTestnetParameters {
707 fn from(params: Arc<testnet::Parameters>) -> Self {
708 Self {
709 network_name: Some(params.network_name().to_string()),
710 network_magic: Some(params.network_magic().0),
711 slow_start_interval: Some(params.slow_start_interval().0),
712 target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
713 disable_pow: Some(params.disable_pow()),
714 genesis_hash: Some(params.genesis_hash().to_string()),
715 activation_heights: Some(params.activation_heights().into()),
716 nsm_reissuance_height: params.configured_nsm_reissuance_height(),
717 pre_nu6_funding_streams: None,
718 post_nu6_funding_streams: None,
719 initial_nsm_value_balance: params.configured_initial_nsm_value_balance(),
720 funding_streams: Some(params.funding_streams().iter().map(Into::into).collect()),
721 pre_blossom_halving_interval: Some(
722 params
723 .pre_blossom_halving_interval()
724 .try_into()
725 .expect("should convert"),
726 ),
727 lockbox_disbursements: Some(
728 params
729 .lockbox_disbursements()
730 .into_iter()
731 .map(Into::into)
732 .collect(),
733 ),
734 checkpoints: if params.checkpoints() == testnet::Parameters::default().checkpoints() {
735 ConfiguredCheckpoints::Default(true)
736 } else {
737 params.checkpoints().into()
738 },
739 extend_funding_stream_addresses_as_required: None,
740 temporary_orchard_disabling_soft_fork_height: params
741 .temporary_orchard_disabling_soft_fork_height()
742 .map(|height| height.0),
743 should_allow_unshielded_coinbase_spends: params
744 .is_regtest()
745 .then(|| params.should_allow_unshielded_coinbase_spends()),
746 }
747 }
748}
749
750impl From<Config> for DConfig {
751 fn from(
752 Config {
753 listen_addr,
754 external_addr,
755 network,
756 initial_mainnet_peers,
757 initial_testnet_peers,
758 cache_dir,
759 peerset_initial_target_size,
760 crawl_new_peer_interval,
761 max_connections_per_ip,
762 }: Config,
763 ) -> Self {
764 let dnetwork = match network.kind() {
765 NetworkKind::Testnet => match network
766 .parameters()
767 .filter(|params| !params.is_default_testnet())
768 .map(Into::into)
769 {
770 Some(params) => DNetwork::ConfiguredTestnet(Box::new(params)),
771 None => DNetwork::DefaultForKind(NetworkKind::Testnet),
772 },
773
774 NetworkKind::Regtest => match network.parameters().map(Into::into) {
775 Some(params) => DNetwork::ConfiguredRegtest {
776 params: Box::new(params),
777 regtest: Some(true),
778 },
779 None => DNetwork::DefaultForKind(NetworkKind::Regtest),
780 },
781
782 other_kind => DNetwork::DefaultForKind(other_kind),
783 };
784
785 DConfig {
786 listen_addr: listen_addr.to_string(),
787 external_addr: external_addr.map(|addr| addr.to_string()),
788 network: dnetwork,
789 testnet_parameters: None,
790 initial_mainnet_peers,
791 initial_testnet_peers,
792 cache_dir,
793 peerset_initial_target_size,
794 crawl_new_peer_interval,
795 max_connections_per_ip: Some(max_connections_per_ip),
796 }
797 }
798}
799
800impl<'de> Deserialize<'de> for Config {
801 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
802 where
803 D: Deserializer<'de>,
804 {
805 let DConfig {
806 listen_addr,
807 external_addr,
808 network: dnetwork,
809 testnet_parameters,
810 initial_mainnet_peers,
811 initial_testnet_peers,
812 cache_dir,
813 peerset_initial_target_size,
814 crawl_new_peer_interval,
815 max_connections_per_ip,
816 } = DConfig::deserialize(deserializer)?;
817
818 let network = match (dnetwork, testnet_parameters) {
819 (DNetwork::ConfiguredTestnet(params), _) => {
820 build_configured_testnet::<D>(*params, &initial_testnet_peers)?
821 }
822 (DNetwork::ConfiguredRegtest { params, .. }, _) => testnet::Parameters::new_regtest(
823 build_regtest_params(*params).map_err(de::Error::custom)?,
824 )
825 .map(Network::new_configured_testnet)
826 .map_err(de::Error::custom)?,
827 (DNetwork::DefaultForKind(NetworkKind::Mainnet), _) => Network::Mainnet,
828 (DNetwork::DefaultForKind(NetworkKind::Testnet), Some(params)) => {
829 build_configured_testnet::<D>(params, &initial_testnet_peers)?
830 }
831 (DNetwork::DefaultForKind(NetworkKind::Testnet), None) => {
832 Network::new_default_testnet()
833 }
834 (DNetwork::DefaultForKind(NetworkKind::Regtest), Some(params)) => {
835 testnet::Parameters::new_regtest(
836 build_regtest_params(params).map_err(de::Error::custom)?,
837 )
838 .map(Network::new_configured_testnet)
839 .map_err(de::Error::custom)?
840 }
841 (DNetwork::DefaultForKind(NetworkKind::Regtest), None) => {
842 Network::new_regtest(Default::default())
843 }
844 };
845
846 let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
847 Ok(socket) => Ok(socket),
848 Err(_) => match listen_addr.parse::<IpAddr>() {
849 Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
850 Err(err) => Err(de::Error::custom(format!(
851 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
852 ))),
853 },
854 }?;
855
856 let external_socket_addr = if let Some(address) = &external_addr {
857 match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
858 Ok(socket) => Ok(Some(socket)),
859 Err(_) => match address.parse::<IpAddr>() {
860 Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
861 Err(err) => Err(de::Error::custom(format!(
862 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
863 ))),
864 },
865 }?
866 } else {
867 None
868 };
869
870 let [max_connections_per_ip, peerset_initial_target_size] = [
871 ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
872 ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
875 ].map(|(field_name, non_zero_config_field, default_config_value)| {
876 if non_zero_config_field == Some(0) {
877 warn!(
878 ?field_name,
879 ?non_zero_config_field,
880 "{field_name} should be greater than 0, using default value of {default_config_value} instead"
881 );
882 }
883
884 non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
885 });
886
887 Ok(Config {
888 listen_addr: canonical_socket_addr(listen_addr),
889 external_addr: external_socket_addr,
890 network,
891 initial_mainnet_peers,
892 initial_testnet_peers,
893 cache_dir,
894 peerset_initial_target_size,
895 crawl_new_peer_interval,
896 max_connections_per_ip,
897 })
898 }
899}
900
901fn is_default_initial_peer(peer: &str) -> bool {
903 fn hostname(peer: &str) -> &str {
904 peer.rsplit_once(':').map_or(peer, |(host, _)| host)
905 }
906 let peer_host = hostname(peer).trim_end_matches('.');
907 DEFAULT_MAINNET_PEERS
908 .iter()
909 .chain(&DEFAULT_TESTNET_PEERS)
910 .any(|default| peer_host.eq_ignore_ascii_case(hostname(default)))
911}
912
913fn build_configured_testnet<'de, D>(
914 params: DTestnetParameters,
915 initial_testnet_peers: &IndexSet<String>,
916) -> Result<Network, D::Error>
917where
918 D: Deserializer<'de>,
919{
920 let DTestnetParameters {
921 network_name,
922 network_magic,
923 slow_start_interval,
924 target_difficulty_limit,
925 disable_pow,
926 genesis_hash,
927 activation_heights,
928 nsm_reissuance_height,
929 pre_nu6_funding_streams,
930 post_nu6_funding_streams,
931 funding_streams,
932 pre_blossom_halving_interval,
933 lockbox_disbursements,
934 checkpoints,
935 extend_funding_stream_addresses_as_required,
936 temporary_orchard_disabling_soft_fork_height,
937 should_allow_unshielded_coinbase_spends,
938 initial_nsm_value_balance,
939 } = params;
940
941 if should_allow_unshielded_coinbase_spends.is_some() {
943 return Err(de::Error::custom(
944 "should_allow_unshielded_coinbase_spends is only supported on Regtest",
945 ));
946 }
947
948 let mut params_builder = testnet::Parameters::build();
949
950 if let Some(initial_nsm_value_balance) = initial_nsm_value_balance.filter(|seed| {
952 network_magic.map(Magic).unwrap_or(magics::TESTNET) != magics::TESTNET
953 || u64::from(*seed) != TESTNET_INITIAL_NSM_VALUE_BALANCE
954 }) {
955 params_builder = params_builder.with_initial_nsm_value_balance(initial_nsm_value_balance);
956 }
957
958 if let Some(network_name) = network_name.clone() {
959 params_builder = params_builder
960 .with_network_name(network_name)
961 .map_err(de::Error::custom)?
962 }
963
964 if let Some(network_magic) = network_magic {
965 params_builder = params_builder
966 .with_network_magic(Magic(network_magic))
967 .map_err(de::Error::custom)?;
968 }
969
970 if let Some(genesis_hash) = genesis_hash {
971 params_builder = params_builder
972 .with_genesis_hash(genesis_hash)
973 .map_err(de::Error::custom)?;
974 }
975
976 if let Some(slow_start_interval) = slow_start_interval {
977 params_builder = params_builder
978 .with_slow_start_interval(slow_start_interval.try_into().map_err(de::Error::custom)?);
979 }
980
981 if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
982 params_builder = params_builder
983 .with_target_difficulty_limit(
984 target_difficulty_limit
985 .parse::<U256>()
986 .map_err(de::Error::custom)?,
987 )
988 .map_err(de::Error::custom)?;
989 }
990
991 if let Some(disable_pow) = disable_pow {
992 params_builder = params_builder.with_disable_pow(disable_pow);
993 }
994
995 if let Some(activation_heights) = activation_heights {
997 params_builder = params_builder
998 .with_activation_heights(activation_heights)
999 .map_err(de::Error::custom)?
1000 }
1001
1002 if let Some(height) = nsm_reissuance_height {
1003 params_builder = params_builder.with_nsm_reissuance_height(height);
1004 }
1005
1006 if let Some(halving_interval) = pre_blossom_halving_interval {
1007 params_builder = params_builder
1008 .with_halving_interval(halving_interval.into())
1009 .map_err(de::Error::custom)?
1010 }
1011
1012 if let Some(funding_streams) = merge_funding_streams(
1014 funding_streams,
1015 pre_nu6_funding_streams,
1016 post_nu6_funding_streams,
1017 )
1018 .map_err(de::Error::custom)?
1019 {
1020 params_builder = params_builder.with_funding_streams(funding_streams);
1021 }
1022
1023 if let Some(lockbox_disbursements) = lockbox_disbursements {
1024 params_builder = params_builder.with_lockbox_disbursements(lockbox_disbursements);
1025 }
1026
1027 params_builder = params_builder
1028 .with_checkpoints(checkpoints)
1029 .map_err(de::Error::custom)?;
1030
1031 if let Some(true) = extend_funding_stream_addresses_as_required {
1032 params_builder = params_builder
1033 .extend_funding_streams()
1034 .map_err(de::Error::custom)?;
1035 }
1036
1037 if let Some(height) = temporary_orchard_disabling_soft_fork_height {
1039 params_builder = params_builder.with_temporary_orchard_disabling_soft_fork_height(
1040 height.try_into().map_err(de::Error::custom)?,
1041 );
1042 }
1043
1044 if network_name.is_none() && params_builder == testnet::Parameters::build() {
1046 return Ok(Network::new_default_testnet());
1047 }
1048
1049 if !params_builder.is_compatible_with_default_parameters() {
1050 if initial_testnet_peers
1052 .iter()
1053 .any(|peer| is_default_initial_peer(peer))
1054 {
1055 return Err(de::Error::custom(
1056 "cannot use default initial peers with incompatible testnet; \
1057 for public Testnet, remove explicit consensus overrides from \
1058 [network.testnet_parameters] to use the updated public defaults, including NU7; \
1059 for a custom network, configure its own initial_testnet_peers and \
1060 network_magic distinct from public Testnet",
1061 ));
1062 }
1063
1064 if network_magic.map(Magic).unwrap_or(magics::TESTNET) == magics::TESTNET {
1067 return Err(de::Error::custom(
1068 "incompatible testnet parameters require network_magic distinct from public Testnet; \
1069 for public Testnet, remove explicit consensus overrides from \
1070 [network.testnet_parameters] (including activation_heights and funding_streams) \
1071 to use the updated public defaults, including NU7; for a custom network, configure its own \
1072 network_magic and initial_testnet_peers",
1073 ));
1074 }
1075 }
1076
1077 params_builder.to_network().map_err(de::Error::custom)
1078}
1079
1080fn build_regtest_params(params: DTestnetParameters) -> Result<RegtestParameters, &'static str> {
1081 let DTestnetParameters {
1082 activation_heights,
1083 nsm_reissuance_height,
1084 pre_nu6_funding_streams,
1085 post_nu6_funding_streams,
1086 funding_streams,
1087 lockbox_disbursements,
1088 checkpoints,
1089 extend_funding_stream_addresses_as_required,
1090 should_allow_unshielded_coinbase_spends,
1091 initial_nsm_value_balance,
1092 ..
1093 } = params;
1094
1095 Ok(RegtestParameters {
1096 activation_heights: activation_heights.unwrap_or_default(),
1097 nsm_reissuance_height,
1098 funding_streams: merge_funding_streams(
1099 funding_streams,
1100 pre_nu6_funding_streams,
1101 post_nu6_funding_streams,
1102 )?,
1103 lockbox_disbursements,
1104 checkpoints: Some(checkpoints),
1105 extend_funding_stream_addresses_as_required,
1106 should_allow_unshielded_coinbase_spends,
1107 initial_nsm_value_balance,
1108 })
1109}
1110
1111fn merge_funding_streams(
1113 funding_streams: Option<Vec<ConfiguredFundingStreams>>,
1114 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
1115 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
1116) -> Result<Option<Vec<ConfiguredFundingStreams>>, &'static str> {
1117 let has_legacy_streams =
1118 pre_nu6_funding_streams.is_some() || post_nu6_funding_streams.is_some();
1119 if funding_streams.as_ref().is_some_and(Vec::is_empty) && has_legacy_streams {
1120 return Err(
1121 "funding_streams = [] cannot be combined with pre_nu6_funding_streams or post_nu6_funding_streams",
1122 );
1123 }
1124
1125 let specified = funding_streams.is_some() || has_legacy_streams;
1126 let mut funding_streams = funding_streams.unwrap_or_default();
1127 if let Some(mut post_nu6) = post_nu6_funding_streams {
1128 if post_nu6.height_range.is_none() || post_nu6.recipients.is_none() {
1130 let default_parameters = testnet::Parameters::default();
1131 let defaults = &default_parameters.funding_streams()[1];
1132 post_nu6
1133 .height_range
1134 .get_or_insert_with(|| defaults.height_range().clone());
1135 if post_nu6.recipients.is_none() {
1136 post_nu6.recipients = ConfiguredFundingStreams::from(defaults).recipients;
1137 }
1138 }
1139 funding_streams.insert(0, post_nu6);
1140 }
1141 if let Some(pre_nu6) = pre_nu6_funding_streams {
1142 funding_streams.insert(0, pre_nu6);
1143 }
1144
1145 Ok(specified.then_some(funding_streams))
1146}