1use std::{
4 collections::HashSet,
5 fmt,
6 io::{self, ErrorKind},
7 net::{IpAddr, SocketAddr},
8 path::{Path, PathBuf},
9 str::FromStr,
10 sync::Arc,
11 time::Duration,
12};
13
14use indexmap::IndexSet;
15use iroh::SecretKey;
16use rand::rngs::OsRng;
17use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
18use tokio::fs;
19
20use tracing::Span;
21use zakura_chain::{
22 common::atomic_write,
23 parameters::{
24 testnet::{
25 self, ConfiguredActivationHeights, ConfiguredCheckpoints, ConfiguredFundingStreams,
26 ConfiguredLockboxDisbursement, RegtestParameters,
27 },
28 Magic, Network, NetworkKind,
29 },
30 work::difficulty::U256,
31};
32
33use crate::{
34 constants::{
35 DEFAULT_CRAWL_NEW_PEER_INTERVAL, DEFAULT_MAX_CONNS_PER_IP,
36 DEFAULT_PEERSET_INITIAL_TARGET_SIZE, DNS_LOOKUP_TIMEOUT, INBOUND_PEER_LIMIT_MULTIPLIER,
37 MAX_PEER_DISK_CACHE_SIZE, OUTBOUND_PEER_LIMIT_MULTIPLIER,
38 },
39 protocol::external::{canonical_peer_addr, canonical_socket_addr},
40 zakura::ZakuraConfig,
41 BoxError, PeerSocketAddr,
42};
43
44mod cache_dir;
45
46#[cfg(test)]
47mod tests;
48
49pub use cache_dir::CacheDir;
50
51pub(crate) use cache_dir::{
52 default_network_identity_dir, zakura_node_secret_key_file_path as zakura_secret_key_file_path,
53};
54
55#[derive(Clone, Deserialize, Eq, PartialEq)]
57#[serde(transparent)]
58pub struct ZakuraNodeSecretKey(String);
59
60impl ZakuraNodeSecretKey {
61 pub fn expose_secret(&self) -> &str {
66 &self.0
67 }
68}
69
70impl fmt::Debug for ZakuraNodeSecretKey {
71 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72 f.write_str("ZakuraNodeSecretKey([redacted])")
73 }
74}
75
76impl serde::Serialize for ZakuraNodeSecretKey {
77 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
78 where
79 S: Serializer,
80 {
81 serializer.serialize_str("[redacted]")
82 }
83}
84
85#[derive(Clone, Debug, thiserror::Error)]
87pub enum ZakuraSecretKeyError {
88 #[error("configured zakura_node_secret_key is not a valid iroh secret key")]
90 InvalidConfigured,
91}
92
93const MAX_SINGLE_SEED_PEER_DNS_RETRIES: usize = 0;
103
104#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
109#[serde(rename_all = "lowercase")]
110pub enum P2pStack {
111 #[default]
113 Default,
114
115 Legacy,
117
118 Zakura,
120
121 Dual,
124}
125
126impl P2pStack {
127 pub fn resolve(self, network: &Network) -> P2pStack {
134 match self {
135 P2pStack::Default if matches!(network, Network::Mainnet) => P2pStack::Legacy,
136 P2pStack::Default => P2pStack::Dual,
137 resolved => resolved,
138 }
139 }
140
141 fn runs_legacy(self) -> bool {
145 matches!(self, P2pStack::Legacy | P2pStack::Dual)
146 }
147
148 fn runs_zakura(self) -> bool {
152 matches!(self, P2pStack::Zakura | P2pStack::Dual)
153 }
154}
155
156#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
158#[serde(deny_unknown_fields, default, into = "DConfig")]
159pub struct Config {
160 pub listen_addr: SocketAddr,
183
184 pub external_addr: Option<SocketAddr>,
191
192 pub network: Network,
194
195 pub initial_mainnet_peers: IndexSet<String>,
198
199 pub initial_testnet_peers: IndexSet<String>,
202
203 pub cache_dir: CacheDir,
256
257 pub identity_dir: PathBuf,
266
267 pub zakura_node_secret_key: Option<ZakuraNodeSecretKey>,
276
277 pub p2p_stack: P2pStack,
290
291 pub zakura: ZakuraConfig,
297
298 pub peerset_initial_target_size: usize,
307
308 #[serde(with = "humantime_serde")]
316 pub crawl_new_peer_interval: Duration,
317
318 pub max_connections_per_ip: usize,
339}
340
341impl Config {
342 pub fn peerset_outbound_connection_limit(&self) -> usize {
366 self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
367 }
368
369 pub fn peerset_inbound_connection_limit(&self) -> usize {
377 self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
378 }
379
380 pub fn peerset_total_connection_limit(&self) -> usize {
383 self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
384 }
385
386 pub fn initial_peer_hostnames(&self) -> IndexSet<String> {
388 match &self.network {
389 Network::Mainnet => self.initial_mainnet_peers.clone(),
390 Network::Testnet(_params) => self.initial_testnet_peers.clone(),
391 }
392 }
393
394 pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
401 let dns_peers =
403 Config::resolve_peers(&self.initial_peer_hostnames().iter().cloned().collect()).await;
404
405 if self.network.is_regtest() {
406 dns_peers
408 .into_iter()
409 .filter(PeerSocketAddr::is_localhost)
410 .collect()
411 } else {
412 let disk_peers = self.load_peer_cache().await.unwrap_or_default();
414
415 dns_peers.into_iter().chain(disk_peers).collect()
416 }
417 }
418
419 async fn resolve_peers(peers: &HashSet<String>) -> HashSet<PeerSocketAddr> {
425 use futures::stream::StreamExt;
426
427 if peers.is_empty() {
428 warn!(
429 "no initial peers in the network config. \
430 Hint: you must configure at least one peer IP or DNS seeder to run Zakura, \
431 give it some previously cached peer IP addresses on disk, \
432 or make sure Zakura's listener port gets inbound connections."
433 );
434 return HashSet::new();
435 }
436
437 loop {
438 let peer_addresses = peers
443 .iter()
444 .map(|s| Config::resolve_host(s, MAX_SINGLE_SEED_PEER_DNS_RETRIES))
445 .collect::<futures::stream::FuturesUnordered<_>>()
446 .concat()
447 .await;
448
449 if peer_addresses.is_empty() {
450 tracing::info!(
451 ?peers,
452 ?peer_addresses,
453 "empty peer list after DNS resolution, retrying after {} seconds",
454 DNS_LOOKUP_TIMEOUT.as_secs(),
455 );
456 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
457 } else {
458 return peer_addresses;
459 }
460 }
461 }
462
463 async fn resolve_host(host: &str, max_retries: usize) -> HashSet<PeerSocketAddr> {
472 for retries in 0..=max_retries {
473 if let Ok(addresses) = Config::resolve_host_once(host).await {
474 return addresses;
475 }
476
477 if retries < max_retries {
478 tracing::info!(
479 ?host,
480 previous_attempts = ?(retries + 1),
481 "Waiting {DNS_LOOKUP_TIMEOUT:?} to retry seed peer DNS resolution",
482 );
483 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
484 } else {
485 tracing::info!(
486 ?host,
487 attempts = ?(retries + 1),
488 "Seed peer DNS resolution failed, checking for addresses from other seed peers",
489 );
490 }
491 }
492
493 HashSet::new()
494 }
495
496 async fn resolve_host_once(host: &str) -> Result<HashSet<PeerSocketAddr>, BoxError> {
505 let fut = tokio::net::lookup_host(host);
506 let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
507
508 match fut.await {
509 Ok(Ok(ip_addrs)) => {
510 let ip_addrs: Vec<PeerSocketAddr> = ip_addrs.map(canonical_peer_addr).collect();
511
512 #[cfg(not(test))]
514 info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
515 #[cfg(test)]
516 debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
517
518 for ip in &ip_addrs {
519 metrics::counter!(
525 "zcash.net.peers.initial",
526 "seed" => host.to_string(),
527 "remote_ip" => ip.to_string()
528 )
529 .increment(1);
530 }
531
532 Ok(ip_addrs.into_iter().collect())
533 }
534 Ok(Err(e)) if e.kind() == ErrorKind::InvalidInput => {
535 panic!(
537 "Invalid peer IP address in Zakura config: addresses must have ports:\n\
538 resolving {host:?} returned {e:?}"
539 );
540 }
541 Ok(Err(e)) => {
542 tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
543 Err(e.into())
544 }
545 Err(e) => {
546 tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
547 Err(e.into())
548 }
549 }
550 }
551
552 pub async fn load_peer_cache(&self) -> io::Result<HashSet<PeerSocketAddr>> {
554 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
555 return Ok(HashSet::new());
556 };
557
558 let peer_list = match fs::read_to_string(&peer_cache_file).await {
559 Ok(peer_list) => peer_list,
560 Err(peer_list_error) => {
561 if peer_list_error.kind() == ErrorKind::NotFound {
563 return Ok(HashSet::new());
564 } else {
565 info!(
566 ?peer_list_error,
567 "could not load cached peer list, using default seed peers"
568 );
569 return Err(peer_list_error);
570 }
571 }
572 };
573
574 let peer_list: HashSet<PeerSocketAddr> = peer_list
577 .lines()
578 .filter_map(|peer| {
579 peer.parse()
580 .map_err(|peer_parse_error| {
581 info!(
582 ?peer_parse_error,
583 "invalid peer address in cached peer list, skipping"
584 );
585 peer_parse_error
586 })
587 .ok()
588 })
589 .collect();
590
591 #[cfg(not(test))]
593 info!(
594 cached_ip_count = ?peer_list.len(),
595 ?peer_cache_file,
596 "loaded cached peer IP addresses"
597 );
598 #[cfg(test)]
599 debug!(
600 cached_ip_count = ?peer_list.len(),
601 ?peer_cache_file,
602 "loaded cached peer IP addresses"
603 );
604
605 for ip in &peer_list {
606 metrics::counter!(
612 "zcash.net.peers.initial",
613 "cache" => peer_cache_file.display().to_string(),
614 "remote_ip" => ip.to_string()
615 )
616 .increment(1);
617 }
618
619 Ok(peer_list)
620 }
621
622 pub async fn update_peer_cache(&self, peer_list: HashSet<PeerSocketAddr>) -> io::Result<()> {
630 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
631 return Ok(());
632 };
633
634 if peer_list.is_empty() {
635 info!(
636 ?peer_cache_file,
637 "cacheable peer list was empty, keeping previous cache"
638 );
639 return Ok(());
640 }
641
642 let mut peer_list: Vec<String> = peer_list
644 .iter()
645 .take(MAX_PEER_DISK_CACHE_SIZE)
646 .map(|redacted_peer| redacted_peer.remove_socket_addr_privacy().to_string())
647 .collect();
648 peer_list.sort();
653 let peer_data = peer_list.join("\n");
655
656 let span = Span::current();
659 let write_result = tokio::task::spawn_blocking(move || {
660 span.in_scope(move || atomic_write(peer_cache_file, peer_data.as_bytes()))
661 })
662 .await
663 .expect("could not write the peer cache file")?;
664
665 match write_result {
666 Ok(peer_cache_file) => {
667 info!(
668 cached_ip_count = ?peer_list.len(),
669 ?peer_cache_file,
670 "updated cached peer IP addresses"
671 );
672
673 for ip in &peer_list {
674 metrics::counter!(
675 "zcash.net.peers.cache",
676 "cache" => peer_cache_file.display().to_string(),
677 "remote_ip" => ip.to_string()
678 )
679 .increment(1);
680 }
681
682 Ok(())
683 }
684 Err(error) => Err(error.error),
685 }
686 }
687
688 pub fn zakura_secret_key(&self) -> Result<SecretKey, ZakuraSecretKeyError> {
709 if let Some(secret) = &self.zakura_node_secret_key {
710 return SecretKey::from_str(secret.expose_secret())
711 .map_err(|_| ZakuraSecretKeyError::InvalidConfigured);
712 }
713
714 let key_file = zakura_secret_key_file_path(&self.identity_dir, &self.network);
715
716 Ok(load_or_generate_zakura_secret_key(&key_file))
717 }
718
719 pub fn legacy_p2p(&self) -> bool {
722 self.p2p_stack.resolve(&self.network).runs_legacy()
723 }
724
725 pub fn v2_p2p(&self) -> bool {
729 self.p2p_stack.resolve(&self.network).runs_zakura()
730 }
731
732 #[cfg(any(test, feature = "proptest-impl"))]
743 pub fn for_test(p2p_stack: P2pStack) -> Config {
744 assert_ne!(
745 p2p_stack,
746 P2pStack::Default,
747 "tests must pin an explicit P2P stack",
748 );
749
750 Config {
751 p2p_stack,
752 ..Config::default()
753 }
754 }
755}
756
757fn load_or_generate_zakura_secret_key(key_file: &Path) -> SecretKey {
763 match std::fs::read_to_string(key_file) {
764 Ok(contents) => match SecretKey::from_str(contents.trim()) {
765 Ok(secret_key) => return secret_key,
766 Err(_) => warn!(
767 ?key_file,
768 "ignoring unparsable Zakura node secret key file; regenerating a new identity"
769 ),
770 },
771 Err(error) if error.kind() == ErrorKind::NotFound => {}
772 Err(error) => warn!(
773 ?error,
774 ?key_file,
775 "could not read Zakura node secret key file; regenerating a new identity"
776 ),
777 }
778
779 let secret_key = SecretKey::generate(OsRng);
780 persist_zakura_secret_key(key_file, &secret_key);
781 secret_key
782}
783
784fn persist_zakura_secret_key(key_file: &Path, secret_key: &SecretKey) {
787 let encoded = hex::encode(secret_key.to_bytes());
788
789 match atomic_write(key_file.to_path_buf(), encoded.as_bytes()) {
790 Ok(Ok(path)) => {
791 if let Err(error) = restrict_secret_key_file_permissions(&path) {
792 warn!(
793 ?error,
794 ?path,
795 "persisted Zakura node secret key but could not restrict its permissions"
796 );
797 } else {
798 info!(?path, "persisted a new Zakura node secret key");
799 }
800 }
801 Ok(Err(error)) => warn!(
802 ?error,
803 ?key_file,
804 "could not persist Zakura node secret key; using an ephemeral identity this run"
805 ),
806 Err(error) => warn!(
807 ?error,
808 ?key_file,
809 "could not persist Zakura node secret key; using an ephemeral identity this run"
810 ),
811 }
812}
813
814#[cfg(unix)]
816fn restrict_secret_key_file_permissions(path: &Path) -> io::Result<()> {
817 use std::os::unix::fs::PermissionsExt;
818
819 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
820}
821
822#[cfg(not(unix))]
824fn restrict_secret_key_file_permissions(_path: &Path) -> io::Result<()> {
825 Ok(())
826}
827
828impl Default for Config {
829 fn default() -> Config {
830 let mainnet_peers = [
831 "dnsseed.str4d.xyz:8233",
832 "dnsseed.z.cash:8233",
833 "mainnet.seeder.shieldedinfra.net:8233",
834 "mainnet.seeder.zfnd.org:8233",
835 ]
836 .iter()
837 .map(|&s| String::from(s))
838 .collect();
839
840 let testnet_peers = [
841 "dnsseed.testnet.z.cash:18233",
842 "testnet.seeder.zfnd.org:18233",
843 ]
844 .iter()
845 .map(|&s| String::from(s))
846 .collect();
847
848 Config {
849 listen_addr: "[::]:8233"
850 .parse()
851 .expect("Hardcoded address should be parseable"),
852 external_addr: None,
853 network: Network::Mainnet,
854 initial_mainnet_peers: mainnet_peers,
855 initial_testnet_peers: testnet_peers,
856 cache_dir: CacheDir::default(),
857 identity_dir: default_network_identity_dir(),
858 zakura_node_secret_key: None,
859 p2p_stack: P2pStack::Default,
860 zakura: ZakuraConfig::default(),
861 crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
862
863 peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
871 max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
872 }
873 }
874}
875
876fn p2p_stack_from_config<'de, D>(
882 p2p_stack: Option<P2pStack>,
883 legacy_p2p: Option<bool>,
884 v2_p2p: Option<bool>,
885) -> Result<P2pStack, D::Error>
886where
887 D: Deserializer<'de>,
888{
889 match (p2p_stack, legacy_p2p, v2_p2p) {
890 (Some(_), Some(_), _) | (Some(_), _, Some(_)) => Err(de::Error::custom(
891 "network.p2p_stack can't be combined with the deprecated network.legacy_p2p or \
892 network.v2_p2p settings; use network.p2p_stack on its own",
893 )),
894
895 (Some(p2p_stack), None, None) => Ok(p2p_stack),
896
897 (None, None, None) => Ok(P2pStack::Default),
898
899 (None, legacy_p2p, v2_p2p) => match (legacy_p2p.unwrap_or(true), v2_p2p.unwrap_or(true)) {
900 (true, true) => Ok(P2pStack::Dual),
901 (true, false) => Ok(P2pStack::Legacy),
902 (false, true) => Ok(P2pStack::Zakura),
903 (false, false) => Err(de::Error::custom(
904 "network.legacy_p2p and network.v2_p2p are both false, which disables all \
905 peer-to-peer networking; set network.p2p_stack instead",
906 )),
907 },
908 }
909}
910
911#[derive(Serialize, Deserialize)]
912#[serde(deny_unknown_fields)]
913struct DTestnetParameters {
914 network_name: Option<String>,
915 network_magic: Option<[u8; 4]>,
916 slow_start_interval: Option<u32>,
917 target_difficulty_limit: Option<String>,
918 disable_pow: Option<bool>,
919 genesis_hash: Option<String>,
920 activation_heights: Option<ConfiguredActivationHeights>,
921 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
922 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
923 funding_streams: Option<Vec<ConfiguredFundingStreams>>,
924 pre_blossom_halving_interval: Option<u32>,
925 lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
926 #[serde(default)]
927 checkpoints: ConfiguredCheckpoints,
928 extend_funding_stream_addresses_as_required: Option<bool>,
931 temporary_orchard_disabling_soft_fork_height: Option<u32>,
936}
937
938#[derive(Serialize, Deserialize)]
940#[serde(untagged)]
941enum DNetwork {
942 DefaultForKind(NetworkKind),
943 ConfiguredRegtest {
944 params: Box<DTestnetParameters>,
945
946 #[serde(default, skip_serializing)]
947 regtest: Option<bool>,
948 },
949 ConfiguredTestnet(Box<DTestnetParameters>),
950}
951
952impl Default for DNetwork {
953 fn default() -> Self {
954 DNetwork::DefaultForKind(NetworkKind::Mainnet)
955 }
956}
957
958#[derive(Serialize, Deserialize)]
959#[serde(deny_unknown_fields, default)]
960struct DConfig {
961 listen_addr: String,
962 external_addr: Option<String>,
963 network: DNetwork,
964
965 #[serde(default, skip_serializing_if = "Option::is_none")]
967 testnet_parameters: Option<DTestnetParameters>,
968
969 initial_mainnet_peers: IndexSet<String>,
970 initial_testnet_peers: IndexSet<String>,
971 cache_dir: CacheDir,
972 identity_dir: PathBuf,
973 #[serde(default, skip_serializing)]
974 zakura_node_secret_key: Option<ZakuraNodeSecretKey>,
975 #[serde(default, skip_serializing_if = "Option::is_none")]
976 p2p_stack: Option<P2pStack>,
977 #[serde(default, skip_serializing)]
980 legacy_p2p: Option<bool>,
981 #[serde(default, alias = "enable_p2p_v2", skip_serializing)]
983 v2_p2p: Option<bool>,
984 zakura: ZakuraConfig,
985 peerset_initial_target_size: usize,
986 #[serde(alias = "new_peer_interval", with = "humantime_serde")]
987 crawl_new_peer_interval: Duration,
988 max_connections_per_ip: Option<usize>,
989}
990
991impl Default for DConfig {
992 fn default() -> Self {
993 let config = Config::default();
994 Self {
995 listen_addr: "[::]".to_string(),
996 external_addr: None,
997 network: Default::default(),
998 testnet_parameters: None,
999 initial_mainnet_peers: config.initial_mainnet_peers,
1000 initial_testnet_peers: config.initial_testnet_peers,
1001 cache_dir: config.cache_dir,
1002 identity_dir: config.identity_dir,
1003 zakura_node_secret_key: config.zakura_node_secret_key,
1004 p2p_stack: Some(config.p2p_stack),
1005 legacy_p2p: None,
1006 v2_p2p: None,
1007 zakura: config.zakura,
1008 peerset_initial_target_size: config.peerset_initial_target_size,
1009 crawl_new_peer_interval: config.crawl_new_peer_interval,
1010 max_connections_per_ip: Some(config.max_connections_per_ip),
1011 }
1012 }
1013}
1014
1015impl From<Arc<testnet::Parameters>> for DTestnetParameters {
1016 fn from(params: Arc<testnet::Parameters>) -> Self {
1017 Self {
1018 network_name: Some(params.network_name().to_string()),
1019 network_magic: Some(params.network_magic().0),
1020 slow_start_interval: Some(params.slow_start_interval().0),
1021 target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
1022 disable_pow: Some(params.disable_pow()),
1023 genesis_hash: Some(params.genesis_hash().to_string()),
1024 activation_heights: Some(params.activation_heights().into()),
1025 pre_nu6_funding_streams: None,
1026 post_nu6_funding_streams: None,
1027 funding_streams: Some(params.funding_streams().iter().map(Into::into).collect()),
1028 pre_blossom_halving_interval: Some(
1029 params
1030 .pre_blossom_halving_interval()
1031 .try_into()
1032 .expect("should convert"),
1033 ),
1034 lockbox_disbursements: Some(
1035 params
1036 .lockbox_disbursements()
1037 .into_iter()
1038 .map(Into::into)
1039 .collect(),
1040 ),
1041 checkpoints: if params.checkpoints() == testnet::Parameters::default().checkpoints() {
1042 ConfiguredCheckpoints::Default(true)
1043 } else {
1044 params.checkpoints().into()
1045 },
1046 extend_funding_stream_addresses_as_required: None,
1047 temporary_orchard_disabling_soft_fork_height: params
1048 .temporary_orchard_disabling_soft_fork_height()
1049 .map(|height| height.0),
1050 }
1051 }
1052}
1053
1054impl From<Config> for DConfig {
1055 fn from(
1056 Config {
1057 listen_addr,
1058 external_addr,
1059 network,
1060 initial_mainnet_peers,
1061 initial_testnet_peers,
1062 cache_dir,
1063 identity_dir,
1064 zakura_node_secret_key,
1065 p2p_stack,
1066 zakura,
1067 peerset_initial_target_size,
1068 crawl_new_peer_interval,
1069 max_connections_per_ip,
1070 }: Config,
1071 ) -> Self {
1072 let dnetwork = match network.kind() {
1073 NetworkKind::Testnet => match network
1074 .parameters()
1075 .filter(|params| !params.is_default_testnet())
1076 .map(Into::into)
1077 {
1078 Some(params) => DNetwork::ConfiguredTestnet(Box::new(params)),
1079 None => DNetwork::DefaultForKind(NetworkKind::Testnet),
1080 },
1081
1082 NetworkKind::Regtest => match network.parameters().map(Into::into) {
1083 Some(params) => DNetwork::ConfiguredRegtest {
1084 params: Box::new(params),
1085 regtest: Some(true),
1086 },
1087 None => DNetwork::DefaultForKind(NetworkKind::Regtest),
1088 },
1089
1090 other_kind => DNetwork::DefaultForKind(other_kind),
1091 };
1092
1093 DConfig {
1094 listen_addr: listen_addr.to_string(),
1095 external_addr: external_addr.map(|addr| addr.to_string()),
1096 network: dnetwork,
1097 testnet_parameters: None,
1098 initial_mainnet_peers,
1099 initial_testnet_peers,
1100 cache_dir,
1101 identity_dir,
1102 zakura_node_secret_key,
1103 p2p_stack: Some(p2p_stack),
1104 legacy_p2p: None,
1105 v2_p2p: None,
1106 zakura,
1107 peerset_initial_target_size,
1108 crawl_new_peer_interval,
1109 max_connections_per_ip: Some(max_connections_per_ip),
1110 }
1111 }
1112}
1113
1114impl<'de> Deserialize<'de> for Config {
1115 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1116 where
1117 D: Deserializer<'de>,
1118 {
1119 let DConfig {
1120 listen_addr,
1121 external_addr,
1122 network: dnetwork,
1123 testnet_parameters,
1124 initial_mainnet_peers,
1125 initial_testnet_peers,
1126 cache_dir,
1127 identity_dir,
1128 zakura_node_secret_key,
1129 p2p_stack,
1130 legacy_p2p,
1131 v2_p2p,
1132 zakura,
1133 peerset_initial_target_size,
1134 crawl_new_peer_interval,
1135 max_connections_per_ip,
1136 } = DConfig::deserialize(deserializer)?;
1137
1138 let p2p_stack = p2p_stack_from_config::<D>(p2p_stack, legacy_p2p, v2_p2p)?;
1139
1140 let network = match (dnetwork, testnet_parameters) {
1141 (DNetwork::ConfiguredTestnet(params), _) => {
1142 build_configured_testnet::<D>(*params, &initial_testnet_peers)?
1143 }
1144 (DNetwork::ConfiguredRegtest { params, .. }, _) => {
1145 Network::new_regtest(build_regtest_params(*params))
1146 }
1147 (DNetwork::DefaultForKind(NetworkKind::Mainnet), _) => Network::Mainnet,
1148 (DNetwork::DefaultForKind(NetworkKind::Testnet), Some(params)) => {
1149 build_configured_testnet::<D>(params, &initial_testnet_peers)?
1150 }
1151 (DNetwork::DefaultForKind(NetworkKind::Testnet), None) => {
1152 Network::new_default_testnet()
1153 }
1154 (DNetwork::DefaultForKind(NetworkKind::Regtest), Some(params)) => {
1155 Network::new_regtest(build_regtest_params(params))
1156 }
1157 (DNetwork::DefaultForKind(NetworkKind::Regtest), None) => {
1158 Network::new_regtest(Default::default())
1159 }
1160 };
1161
1162 let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
1163 Ok(socket) => Ok(socket),
1164 Err(_) => match listen_addr.parse::<IpAddr>() {
1165 Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
1166 Err(err) => Err(de::Error::custom(format!(
1167 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
1168 ))),
1169 },
1170 }?;
1171
1172 let external_socket_addr = if let Some(address) = &external_addr {
1173 match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
1174 Ok(socket) => Ok(Some(socket)),
1175 Err(_) => match address.parse::<IpAddr>() {
1176 Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
1177 Err(err) => Err(de::Error::custom(format!(
1178 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
1179 ))),
1180 },
1181 }?
1182 } else {
1183 None
1184 };
1185
1186 let [max_connections_per_ip, peerset_initial_target_size] = [
1187 ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
1188 ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
1191 ].map(|(field_name, non_zero_config_field, default_config_value)| {
1192 if non_zero_config_field == Some(0) {
1193 warn!(
1194 ?field_name,
1195 ?non_zero_config_field,
1196 "{field_name} should be greater than 0, using default value of {default_config_value} instead"
1197 );
1198 }
1199
1200 non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
1201 });
1202
1203 let mut zakura = zakura;
1205 zakura.apply_network_defaults(&network);
1206 let default_zakura_bootstrap_peers =
1207 ZakuraConfig::default_bootstrap_peers_for_network(&network);
1208 if zakura.bootstrap_peers.is_empty() {
1209 warn!(
1210 ?network,
1211 "no Zakura bootstrap peers configured; configure zakura.bootstrap_peers or make sure this node receives inbound Zakura connections"
1212 );
1213 } else if network.kind() != NetworkKind::Regtest
1214 && zakura.bootstrap_peers != default_zakura_bootstrap_peers
1215 {
1216 warn!(
1217 ?network,
1218 configured_zakura_bootstrap_peers = ?zakura.bootstrap_peers,
1219 ?default_zakura_bootstrap_peers,
1220 "configured Zakura bootstrap peers differ from the default peers for this network"
1221 );
1222 }
1223 if zakura_listens_on_loopback_with_non_loopback_bootstrap_peers(&zakura) {
1224 warn!(
1225 ?network,
1226 listen_addr = ?zakura.listen_addr,
1227 bootstrap_peers = ?zakura.bootstrap_peers,
1228 "configured Zakura listen_addr is loopback-only, but bootstrap peers use \
1229 non-loopback addresses; native Zakura dials may fail with \
1230 `Can't assign requested address`. Use 0.0.0.0:<port> or another routable \
1231 interface address for public Zakura peers"
1232 );
1233 }
1234 zakura
1235 .block_sync
1236 .clamp_inflight_block_bytes_to_request_floor();
1237 zakura.block_sync.clamp_reorder_lookahead_to_floor();
1238 zakura.block_sync.validate().map_err(|error| {
1239 de::Error::custom(format!("invalid zakura.block_sync config: {error}"))
1240 })?;
1241
1242 Ok(Config {
1243 listen_addr: canonical_socket_addr(listen_addr),
1244 external_addr: external_socket_addr,
1245 network,
1246 initial_mainnet_peers,
1247 initial_testnet_peers,
1248 cache_dir,
1249 identity_dir,
1250 zakura_node_secret_key,
1251 p2p_stack,
1252 zakura,
1253 peerset_initial_target_size,
1254 crawl_new_peer_interval,
1255 max_connections_per_ip,
1256 })
1257 }
1258}
1259
1260fn zakura_listens_on_loopback_with_non_loopback_bootstrap_peers(zakura: &ZakuraConfig) -> bool {
1261 let Some(listen_addr) = zakura.listen_addr else {
1262 return false;
1263 };
1264
1265 listen_addr.ip().is_loopback()
1266 && zakura
1267 .bootstrap_peers
1268 .iter()
1269 .filter_map(|peer| peer.rsplit_once('@'))
1270 .filter_map(|(_node_id, addr)| addr.parse::<SocketAddr>().ok())
1271 .any(|addr| !addr.ip().is_loopback())
1272}
1273
1274fn contains_default_initial_peers(initial_peers: &IndexSet<String>) -> bool {
1278 let Config {
1279 initial_mainnet_peers: mut default_initial_peers,
1280 initial_testnet_peers: default_initial_testnet_peers,
1281 ..
1282 } = Config::default();
1283 default_initial_peers.extend(default_initial_testnet_peers);
1284
1285 initial_peers
1286 .intersection(&default_initial_peers)
1287 .next()
1288 .is_some()
1289}
1290
1291fn build_configured_testnet<'de, D>(
1292 params: DTestnetParameters,
1293 initial_testnet_peers: &IndexSet<String>,
1294) -> Result<Network, D::Error>
1295where
1296 D: Deserializer<'de>,
1297{
1298 let DTestnetParameters {
1299 network_name,
1300 network_magic,
1301 slow_start_interval,
1302 target_difficulty_limit,
1303 disable_pow,
1304 genesis_hash,
1305 activation_heights,
1306 pre_nu6_funding_streams,
1307 post_nu6_funding_streams,
1308 funding_streams,
1309 pre_blossom_halving_interval,
1310 lockbox_disbursements,
1311 checkpoints,
1312 extend_funding_stream_addresses_as_required,
1313 temporary_orchard_disabling_soft_fork_height,
1314 } = params;
1315
1316 let mut params_builder = testnet::Parameters::build();
1317
1318 if let Some(network_name) = network_name.clone() {
1319 params_builder = params_builder
1320 .with_network_name(network_name)
1321 .map_err(de::Error::custom)?
1322 }
1323
1324 if let Some(network_magic) = network_magic {
1325 params_builder = params_builder
1326 .with_network_magic(Magic(network_magic))
1327 .map_err(de::Error::custom)?;
1328 }
1329
1330 if let Some(genesis_hash) = genesis_hash {
1331 params_builder = params_builder
1332 .with_genesis_hash(genesis_hash)
1333 .map_err(de::Error::custom)?;
1334 }
1335
1336 if let Some(slow_start_interval) = slow_start_interval {
1337 params_builder = params_builder
1338 .with_slow_start_interval(slow_start_interval.try_into().map_err(de::Error::custom)?);
1339 }
1340
1341 if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
1342 params_builder = params_builder
1343 .with_target_difficulty_limit(
1344 target_difficulty_limit
1345 .parse::<U256>()
1346 .map_err(de::Error::custom)?,
1347 )
1348 .map_err(de::Error::custom)?;
1349 }
1350
1351 if let Some(disable_pow) = disable_pow {
1352 params_builder = params_builder.with_disable_pow(disable_pow);
1353 }
1354
1355 if let Some(activation_heights) = activation_heights {
1357 params_builder = params_builder
1358 .with_activation_heights(activation_heights)
1359 .map_err(de::Error::custom)?
1360 }
1361
1362 if let Some(halving_interval) = pre_blossom_halving_interval {
1363 params_builder = params_builder
1364 .with_halving_interval(halving_interval.into())
1365 .map_err(de::Error::custom)?
1366 }
1367
1368 let mut funding_streams_vec = funding_streams.unwrap_or_default();
1370
1371 if let Some(funding_streams) = post_nu6_funding_streams {
1372 funding_streams_vec.insert(0, funding_streams);
1373 }
1374
1375 if let Some(funding_streams) = pre_nu6_funding_streams {
1376 funding_streams_vec.insert(0, funding_streams);
1377 }
1378
1379 if !funding_streams_vec.is_empty() {
1380 params_builder = params_builder.with_funding_streams(funding_streams_vec);
1381 }
1382
1383 if let Some(lockbox_disbursements) = lockbox_disbursements {
1384 params_builder = params_builder.with_lockbox_disbursements(lockbox_disbursements);
1385 }
1386
1387 params_builder = params_builder
1388 .with_checkpoints(checkpoints)
1389 .map_err(de::Error::custom)?;
1390
1391 if let Some(true) = extend_funding_stream_addresses_as_required {
1392 params_builder = params_builder.extend_funding_streams();
1393 }
1394
1395 if let Some(height) = temporary_orchard_disabling_soft_fork_height {
1397 params_builder = params_builder.with_temporary_orchard_disabling_soft_fork_height(
1398 height.try_into().map_err(de::Error::custom)?,
1399 );
1400 }
1401
1402 if !params_builder.is_compatible_with_default_parameters()
1405 && contains_default_initial_peers(initial_testnet_peers)
1406 {
1407 return Err(de::Error::custom(
1408 "cannot use default initials peers with incompatible testnet",
1409 ));
1410 };
1411
1412 if network_name.is_none() && params_builder == testnet::Parameters::build() {
1414 Ok(Network::new_default_testnet())
1415 } else {
1416 Ok(params_builder.to_network().map_err(de::Error::custom)?)
1417 }
1418}
1419
1420fn build_regtest_params(params: DTestnetParameters) -> RegtestParameters {
1421 let DTestnetParameters {
1422 activation_heights,
1423 pre_nu6_funding_streams,
1424 post_nu6_funding_streams,
1425 funding_streams,
1426 lockbox_disbursements,
1427 checkpoints,
1428 extend_funding_stream_addresses_as_required,
1429 ..
1430 } = params;
1431
1432 let mut funding_streams_vec = funding_streams.unwrap_or_default();
1433
1434 if let Some(funding_streams) = post_nu6_funding_streams {
1435 funding_streams_vec.insert(0, funding_streams);
1436 }
1437
1438 if let Some(funding_streams) = pre_nu6_funding_streams {
1439 funding_streams_vec.insert(0, funding_streams);
1440 }
1441
1442 RegtestParameters {
1443 activation_heights: activation_heights.unwrap_or_default(),
1444 funding_streams: Some(funding_streams_vec),
1445 lockbox_disbursements,
1446 checkpoints: Some(checkpoints),
1447 extend_funding_stream_addresses_as_required,
1448 }
1449}