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_DIVISOR, 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 pub expose_peer_addresses: bool,
354}
355
356impl Config {
357 pub fn peerset_outbound_connection_limit(&self) -> usize {
381 self.peerset_initial_target_size * OUTBOUND_PEER_LIMIT_MULTIPLIER
382 / OUTBOUND_PEER_LIMIT_DIVISOR
383 }
384
385 pub fn peerset_inbound_connection_limit(&self) -> usize {
393 self.peerset_initial_target_size * INBOUND_PEER_LIMIT_MULTIPLIER
394 }
395
396 pub fn peerset_total_connection_limit(&self) -> usize {
399 self.peerset_outbound_connection_limit() + self.peerset_inbound_connection_limit()
400 }
401
402 pub fn initial_peer_hostnames(&self) -> IndexSet<String> {
404 match &self.network {
405 Network::Mainnet => self.initial_mainnet_peers.clone(),
406 Network::Testnet(_params) => self.initial_testnet_peers.clone(),
407 }
408 }
409
410 pub async fn initial_peers(&self) -> HashSet<PeerSocketAddr> {
417 let dns_peers = Config::resolve_peers(
419 &self.initial_peer_hostnames().iter().cloned().collect(),
420 self.expose_peer_addresses,
421 )
422 .await;
423
424 if self.network.is_regtest() {
425 dns_peers
427 .into_iter()
428 .filter(PeerSocketAddr::is_localhost)
429 .collect()
430 } else {
431 let disk_peers = self.load_peer_cache().await.unwrap_or_default();
433
434 dns_peers.into_iter().chain(disk_peers).collect()
435 }
436 }
437
438 async fn resolve_peers(
444 peers: &HashSet<String>,
445 expose_peer_addresses: bool,
446 ) -> HashSet<PeerSocketAddr> {
447 use futures::stream::StreamExt;
448
449 if peers.is_empty() {
450 warn!(
451 "no initial peers in the network config. \
452 Hint: you must configure at least one peer IP or DNS seeder to run Zakura, \
453 give it some previously cached peer IP addresses on disk, \
454 or make sure Zakura's listener port gets inbound connections."
455 );
456 return HashSet::new();
457 }
458
459 loop {
460 let peer_addresses = peers
465 .iter()
466 .map(|s| {
467 Config::resolve_host(s, MAX_SINGLE_SEED_PEER_DNS_RETRIES, expose_peer_addresses)
468 })
469 .collect::<futures::stream::FuturesUnordered<_>>()
470 .concat()
471 .await;
472
473 if peer_addresses.is_empty() {
474 tracing::info!(
475 ?peers,
476 ?peer_addresses,
477 "empty peer list after DNS resolution, retrying after {} seconds",
478 DNS_LOOKUP_TIMEOUT.as_secs(),
479 );
480 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
481 } else {
482 return peer_addresses;
483 }
484 }
485 }
486
487 async fn resolve_host(
496 host: &str,
497 max_retries: usize,
498 expose_peer_addresses: bool,
499 ) -> HashSet<PeerSocketAddr> {
500 for retries in 0..=max_retries {
501 if let Ok(addresses) = Config::resolve_host_once(host, expose_peer_addresses).await {
502 return addresses;
503 }
504
505 if retries < max_retries {
506 tracing::info!(
507 ?host,
508 previous_attempts = ?(retries + 1),
509 "Waiting {DNS_LOOKUP_TIMEOUT:?} to retry seed peer DNS resolution",
510 );
511 tokio::time::sleep(DNS_LOOKUP_TIMEOUT).await;
512 } else {
513 tracing::info!(
514 ?host,
515 attempts = ?(retries + 1),
516 "Seed peer DNS resolution failed, checking for addresses from other seed peers",
517 );
518 }
519 }
520
521 HashSet::new()
522 }
523
524 async fn resolve_host_once(
533 host: &str,
534 expose_peer_addresses: bool,
535 ) -> Result<HashSet<PeerSocketAddr>, BoxError> {
536 let fut = tokio::net::lookup_host(host);
537 let fut = tokio::time::timeout(DNS_LOOKUP_TIMEOUT, fut);
538
539 match fut.await {
540 Ok(Ok(ip_addrs)) => {
541 let ip_addrs: Vec<PeerSocketAddr> = ip_addrs.map(canonical_peer_addr).collect();
542
543 #[cfg(not(test))]
545 info!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
546 #[cfg(test)]
547 debug!(seed = ?host, remote_ip_count = ?ip_addrs.len(), "resolved seed peer IP addresses");
548
549 for ip in &ip_addrs {
550 metrics::counter!(
556 "zcash.net.peers.initial",
557 "seed" => host.to_string(),
558 "remote_ip" => ip.addr_label(expose_peer_addresses)
559 )
560 .increment(1);
561 }
562
563 Ok(ip_addrs.into_iter().collect())
564 }
565 Ok(Err(e)) if e.kind() == ErrorKind::InvalidInput => {
566 panic!(
568 "Invalid peer IP address in Zakura config: addresses must have ports:\n\
569 resolving {host:?} returned {e:?}"
570 );
571 }
572 Ok(Err(e)) => {
573 tracing::info!(?host, ?e, "DNS error resolving peer IP addresses");
574 Err(e.into())
575 }
576 Err(e) => {
577 tracing::info!(?host, ?e, "DNS timeout resolving peer IP addresses");
578 Err(e.into())
579 }
580 }
581 }
582
583 pub async fn load_peer_cache(&self) -> io::Result<HashSet<PeerSocketAddr>> {
585 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
586 return Ok(HashSet::new());
587 };
588
589 let peer_list = match fs::read_to_string(&peer_cache_file).await {
590 Ok(peer_list) => peer_list,
591 Err(peer_list_error) => {
592 if peer_list_error.kind() == ErrorKind::NotFound {
594 return Ok(HashSet::new());
595 } else {
596 info!(
597 ?peer_list_error,
598 "could not load cached peer list, using default seed peers"
599 );
600 return Err(peer_list_error);
601 }
602 }
603 };
604
605 let peer_list: HashSet<PeerSocketAddr> = peer_list
608 .lines()
609 .filter_map(|peer| {
610 peer.parse()
611 .map_err(|peer_parse_error| {
612 info!(
613 ?peer_parse_error,
614 "invalid peer address in cached peer list, skipping"
615 );
616 peer_parse_error
617 })
618 .ok()
619 })
620 .collect();
621
622 #[cfg(not(test))]
624 info!(
625 cached_ip_count = ?peer_list.len(),
626 ?peer_cache_file,
627 "loaded cached peer IP addresses"
628 );
629 #[cfg(test)]
630 debug!(
631 cached_ip_count = ?peer_list.len(),
632 ?peer_cache_file,
633 "loaded cached peer IP addresses"
634 );
635
636 for ip in &peer_list {
637 metrics::counter!(
643 "zcash.net.peers.initial",
644 "cache" => peer_cache_file.display().to_string(),
645 "remote_ip" => ip.addr_label(self.expose_peer_addresses)
646 )
647 .increment(1);
648 }
649
650 Ok(peer_list)
651 }
652
653 pub async fn update_peer_cache(&self, peer_list: HashSet<PeerSocketAddr>) -> io::Result<()> {
661 let Some(peer_cache_file) = self.cache_dir.peer_cache_file_path(&self.network) else {
662 return Ok(());
663 };
664
665 if peer_list.is_empty() {
666 info!(
667 ?peer_cache_file,
668 "cacheable peer list was empty, keeping previous cache"
669 );
670 return Ok(());
671 }
672
673 let selected_peers: Vec<PeerSocketAddr> = peer_list
674 .iter()
675 .take(MAX_PEER_DISK_CACHE_SIZE)
676 .copied()
677 .collect();
678
679 let mut peer_list: Vec<String> = selected_peers
681 .iter()
682 .map(|peer| peer.remove_socket_addr_privacy().to_string())
683 .collect();
684 peer_list.sort();
689 let peer_data = peer_list.join("\n");
691
692 let span = Span::current();
695 let write_result = tokio::task::spawn_blocking(move || {
696 span.in_scope(move || atomic_write(peer_cache_file, peer_data.as_bytes()))
697 })
698 .await
699 .expect("could not write the peer cache file")?;
700
701 match write_result {
702 Ok(peer_cache_file) => {
703 info!(
704 cached_ip_count = ?peer_list.len(),
705 ?peer_cache_file,
706 "updated cached peer IP addresses"
707 );
708
709 for ip in &selected_peers {
710 metrics::counter!(
711 "zcash.net.peers.cache",
712 "cache" => peer_cache_file.display().to_string(),
713 "remote_ip" => ip.addr_label(self.expose_peer_addresses)
714 )
715 .increment(1);
716 }
717
718 Ok(())
719 }
720 Err(error) => Err(error.error),
721 }
722 }
723
724 pub fn zakura_secret_key(&self) -> Result<SecretKey, ZakuraSecretKeyError> {
745 if let Some(secret) = &self.zakura_node_secret_key {
746 return SecretKey::from_str(secret.expose_secret())
747 .map_err(|_| ZakuraSecretKeyError::InvalidConfigured);
748 }
749
750 let key_file = zakura_secret_key_file_path(&self.identity_dir, &self.network);
751
752 Ok(load_or_generate_zakura_secret_key(&key_file))
753 }
754
755 pub fn legacy_p2p(&self) -> bool {
758 self.p2p_stack.resolve(&self.network).runs_legacy()
759 }
760
761 pub fn v2_p2p(&self) -> bool {
765 self.p2p_stack.resolve(&self.network).runs_zakura()
766 }
767
768 #[cfg(any(test, feature = "proptest-impl"))]
779 pub fn for_test(p2p_stack: P2pStack) -> Config {
780 assert_ne!(
781 p2p_stack,
782 P2pStack::Default,
783 "tests must pin an explicit P2P stack",
784 );
785
786 Config {
787 p2p_stack,
788 ..Config::default()
789 }
790 }
791}
792
793fn load_or_generate_zakura_secret_key(key_file: &Path) -> SecretKey {
799 match std::fs::read_to_string(key_file) {
800 Ok(contents) => match SecretKey::from_str(contents.trim()) {
801 Ok(secret_key) => return secret_key,
802 Err(_) => warn!(
803 ?key_file,
804 "ignoring unparsable Zakura node secret key file; regenerating a new identity"
805 ),
806 },
807 Err(error) if error.kind() == ErrorKind::NotFound => {}
808 Err(error) => warn!(
809 ?error,
810 ?key_file,
811 "could not read Zakura node secret key file; regenerating a new identity"
812 ),
813 }
814
815 let secret_key = SecretKey::generate(OsRng);
816 persist_zakura_secret_key(key_file, &secret_key);
817 secret_key
818}
819
820fn persist_zakura_secret_key(key_file: &Path, secret_key: &SecretKey) {
823 let encoded = hex::encode(secret_key.to_bytes());
824
825 match atomic_write(key_file.to_path_buf(), encoded.as_bytes()) {
826 Ok(Ok(path)) => {
827 if let Err(error) = restrict_secret_key_file_permissions(&path) {
828 warn!(
829 ?error,
830 ?path,
831 "persisted Zakura node secret key but could not restrict its permissions"
832 );
833 } else {
834 info!(?path, "persisted a new Zakura node secret key");
835 }
836 }
837 Ok(Err(error)) => warn!(
838 ?error,
839 ?key_file,
840 "could not persist Zakura node secret key; using an ephemeral identity this run"
841 ),
842 Err(error) => warn!(
843 ?error,
844 ?key_file,
845 "could not persist Zakura node secret key; using an ephemeral identity this run"
846 ),
847 }
848}
849
850#[cfg(unix)]
852fn restrict_secret_key_file_permissions(path: &Path) -> io::Result<()> {
853 use std::os::unix::fs::PermissionsExt;
854
855 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
856}
857
858#[cfg(not(unix))]
860fn restrict_secret_key_file_permissions(_path: &Path) -> io::Result<()> {
861 Ok(())
862}
863
864impl Default for Config {
865 fn default() -> Config {
866 let mainnet_peers = [
867 "dnsseed.str4d.xyz:8233",
868 "dnsseed.z.cash:8233",
869 "mainnet.seeder.shieldedinfra.net:8233",
870 "mainnet.seeder.zfnd.org:8233",
871 ]
872 .iter()
873 .map(|&s| String::from(s))
874 .collect();
875
876 let testnet_peers = [
877 "dnsseed.testnet.z.cash:18233",
878 "testnet.seeder.zfnd.org:18233",
879 ]
880 .iter()
881 .map(|&s| String::from(s))
882 .collect();
883
884 Config {
885 listen_addr: "[::]:8233"
886 .parse()
887 .expect("Hardcoded address should be parseable"),
888 external_addr: None,
889 network: Network::Mainnet,
890 initial_mainnet_peers: mainnet_peers,
891 initial_testnet_peers: testnet_peers,
892 cache_dir: CacheDir::default(),
893 identity_dir: default_network_identity_dir(),
894 zakura_node_secret_key: None,
895 p2p_stack: P2pStack::Default,
896 zakura: ZakuraConfig::default(),
897 crawl_new_peer_interval: DEFAULT_CRAWL_NEW_PEER_INTERVAL,
898
899 peerset_initial_target_size: DEFAULT_PEERSET_INITIAL_TARGET_SIZE,
907 max_connections_per_ip: DEFAULT_MAX_CONNS_PER_IP,
908 expose_peer_addresses: false,
909 }
910 }
911}
912
913fn p2p_stack_from_config<'de, D>(
919 p2p_stack: Option<P2pStack>,
920 legacy_p2p: Option<bool>,
921 v2_p2p: Option<bool>,
922) -> Result<P2pStack, D::Error>
923where
924 D: Deserializer<'de>,
925{
926 match (p2p_stack, legacy_p2p, v2_p2p) {
927 (Some(_), Some(_), _) | (Some(_), _, Some(_)) => Err(de::Error::custom(
928 "network.p2p_stack can't be combined with the deprecated network.legacy_p2p or \
929 network.v2_p2p settings; use network.p2p_stack on its own",
930 )),
931
932 (Some(p2p_stack), None, None) => Ok(p2p_stack),
933
934 (None, None, None) => Ok(P2pStack::Default),
935
936 (None, legacy_p2p, v2_p2p) => match (legacy_p2p.unwrap_or(true), v2_p2p.unwrap_or(true)) {
937 (true, true) => Ok(P2pStack::Dual),
938 (true, false) => Ok(P2pStack::Legacy),
939 (false, true) => Ok(P2pStack::Zakura),
940 (false, false) => Err(de::Error::custom(
941 "network.legacy_p2p and network.v2_p2p are both false, which disables all \
942 peer-to-peer networking; set network.p2p_stack instead",
943 )),
944 },
945 }
946}
947
948#[derive(Serialize, Deserialize)]
949#[serde(deny_unknown_fields)]
950struct DTestnetParameters {
951 network_name: Option<String>,
952 network_magic: Option<[u8; 4]>,
953 slow_start_interval: Option<u32>,
954 target_difficulty_limit: Option<String>,
955 disable_pow: Option<bool>,
956 max_block_time_start_height: Option<u32>,
958 genesis_hash: Option<String>,
959 activation_heights: Option<ConfiguredActivationHeights>,
960 pre_nu6_funding_streams: Option<ConfiguredFundingStreams>,
961 post_nu6_funding_streams: Option<ConfiguredFundingStreams>,
962 funding_streams: Option<Vec<ConfiguredFundingStreams>>,
963 pre_blossom_halving_interval: Option<u32>,
964 lockbox_disbursements: Option<Vec<ConfiguredLockboxDisbursement>>,
965 #[serde(default)]
966 checkpoints: ConfiguredCheckpoints,
967 extend_funding_stream_addresses_as_required: Option<bool>,
970 temporary_orchard_disabling_soft_fork_height: Option<u32>,
975}
976
977#[derive(Serialize, Deserialize)]
979#[serde(untagged)]
980enum DNetwork {
981 DefaultForKind(NetworkKind),
982 ConfiguredRegtest {
983 params: Box<DTestnetParameters>,
984
985 #[serde(default, skip_serializing)]
986 regtest: Option<bool>,
987 },
988 ConfiguredTestnet(Box<DTestnetParameters>),
989}
990
991impl Default for DNetwork {
992 fn default() -> Self {
993 DNetwork::DefaultForKind(NetworkKind::Mainnet)
994 }
995}
996
997#[derive(Serialize, Deserialize)]
998#[serde(deny_unknown_fields, default)]
999struct DConfig {
1000 listen_addr: String,
1001 external_addr: Option<String>,
1002 network: DNetwork,
1003
1004 #[serde(default, skip_serializing_if = "Option::is_none")]
1006 testnet_parameters: Option<DTestnetParameters>,
1007
1008 initial_mainnet_peers: IndexSet<String>,
1009 initial_testnet_peers: IndexSet<String>,
1010 cache_dir: CacheDir,
1011 identity_dir: PathBuf,
1012 #[serde(default, skip_serializing)]
1013 zakura_node_secret_key: Option<ZakuraNodeSecretKey>,
1014 #[serde(default, skip_serializing_if = "Option::is_none")]
1015 p2p_stack: Option<P2pStack>,
1016 #[serde(default, skip_serializing)]
1019 legacy_p2p: Option<bool>,
1020 #[serde(default, alias = "enable_p2p_v2", skip_serializing)]
1022 v2_p2p: Option<bool>,
1023 zakura: ZakuraConfig,
1024 peerset_initial_target_size: usize,
1025 #[serde(alias = "new_peer_interval", with = "humantime_serde")]
1026 crawl_new_peer_interval: Duration,
1027 max_connections_per_ip: Option<usize>,
1028 expose_peer_addresses: bool,
1029}
1030
1031impl Default for DConfig {
1032 fn default() -> Self {
1033 let config = Config::default();
1034 Self {
1035 listen_addr: "[::]".to_string(),
1036 external_addr: None,
1037 network: Default::default(),
1038 testnet_parameters: None,
1039 initial_mainnet_peers: config.initial_mainnet_peers,
1040 initial_testnet_peers: config.initial_testnet_peers,
1041 cache_dir: config.cache_dir,
1042 identity_dir: config.identity_dir,
1043 zakura_node_secret_key: config.zakura_node_secret_key,
1044 p2p_stack: Some(config.p2p_stack),
1045 legacy_p2p: None,
1046 v2_p2p: None,
1047 zakura: config.zakura,
1048 peerset_initial_target_size: config.peerset_initial_target_size,
1049 crawl_new_peer_interval: config.crawl_new_peer_interval,
1050 max_connections_per_ip: Some(config.max_connections_per_ip),
1051 expose_peer_addresses: config.expose_peer_addresses,
1052 }
1053 }
1054}
1055
1056impl From<Arc<testnet::Parameters>> for DTestnetParameters {
1057 fn from(params: Arc<testnet::Parameters>) -> Self {
1058 Self {
1059 network_name: Some(params.network_name().to_string()),
1060 network_magic: Some(params.network_magic().0),
1061 slow_start_interval: Some(params.slow_start_interval().0),
1062 target_difficulty_limit: Some(params.target_difficulty_limit().to_string()),
1063 disable_pow: Some(params.disable_pow()),
1064 max_block_time_start_height: Some(params.max_block_time_start_height().0),
1065 genesis_hash: Some(params.genesis_hash().to_string()),
1066 activation_heights: Some(params.activation_heights().into()),
1067 pre_nu6_funding_streams: None,
1068 post_nu6_funding_streams: None,
1069 funding_streams: Some(params.funding_streams().iter().map(Into::into).collect()),
1070 pre_blossom_halving_interval: Some(
1071 params
1072 .pre_blossom_halving_interval()
1073 .try_into()
1074 .expect("should convert"),
1075 ),
1076 lockbox_disbursements: Some(
1077 params
1078 .lockbox_disbursements()
1079 .into_iter()
1080 .map(Into::into)
1081 .collect(),
1082 ),
1083 checkpoints: if params.checkpoints() == testnet::Parameters::default().checkpoints() {
1084 ConfiguredCheckpoints::Default(true)
1085 } else {
1086 params.checkpoints().into()
1087 },
1088 extend_funding_stream_addresses_as_required: None,
1089 temporary_orchard_disabling_soft_fork_height: params
1090 .temporary_orchard_disabling_soft_fork_height()
1091 .map(|height| height.0),
1092 }
1093 }
1094}
1095
1096impl From<Config> for DConfig {
1097 fn from(
1098 Config {
1099 listen_addr,
1100 external_addr,
1101 network,
1102 initial_mainnet_peers,
1103 initial_testnet_peers,
1104 cache_dir,
1105 identity_dir,
1106 zakura_node_secret_key,
1107 p2p_stack,
1108 zakura,
1109 peerset_initial_target_size,
1110 crawl_new_peer_interval,
1111 max_connections_per_ip,
1112 expose_peer_addresses,
1113 }: Config,
1114 ) -> Self {
1115 let dnetwork = match network.kind() {
1116 NetworkKind::Testnet => match network
1117 .parameters()
1118 .filter(|params| !params.is_default_testnet())
1119 .map(Into::into)
1120 {
1121 Some(params) => DNetwork::ConfiguredTestnet(Box::new(params)),
1122 None => DNetwork::DefaultForKind(NetworkKind::Testnet),
1123 },
1124
1125 NetworkKind::Regtest => match network.parameters().map(Into::into) {
1126 Some(params) => DNetwork::ConfiguredRegtest {
1127 params: Box::new(params),
1128 regtest: Some(true),
1129 },
1130 None => DNetwork::DefaultForKind(NetworkKind::Regtest),
1131 },
1132
1133 other_kind => DNetwork::DefaultForKind(other_kind),
1134 };
1135
1136 DConfig {
1137 listen_addr: listen_addr.to_string(),
1138 external_addr: external_addr.map(|addr| addr.to_string()),
1139 network: dnetwork,
1140 testnet_parameters: None,
1141 initial_mainnet_peers,
1142 initial_testnet_peers,
1143 cache_dir,
1144 identity_dir,
1145 zakura_node_secret_key,
1146 p2p_stack: Some(p2p_stack),
1147 legacy_p2p: None,
1148 v2_p2p: None,
1149 zakura,
1150 peerset_initial_target_size,
1151 crawl_new_peer_interval,
1152 max_connections_per_ip: Some(max_connections_per_ip),
1153 expose_peer_addresses,
1154 }
1155 }
1156}
1157
1158impl<'de> Deserialize<'de> for Config {
1159 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1160 where
1161 D: Deserializer<'de>,
1162 {
1163 let DConfig {
1164 listen_addr,
1165 external_addr,
1166 network: dnetwork,
1167 testnet_parameters,
1168 initial_mainnet_peers,
1169 initial_testnet_peers,
1170 cache_dir,
1171 identity_dir,
1172 zakura_node_secret_key,
1173 p2p_stack,
1174 legacy_p2p,
1175 v2_p2p,
1176 zakura,
1177 peerset_initial_target_size,
1178 crawl_new_peer_interval,
1179 max_connections_per_ip,
1180 expose_peer_addresses,
1181 } = DConfig::deserialize(deserializer)?;
1182
1183 let p2p_stack = p2p_stack_from_config::<D>(p2p_stack, legacy_p2p, v2_p2p)?;
1184
1185 let network = match (dnetwork, testnet_parameters) {
1186 (DNetwork::ConfiguredTestnet(params), _) => {
1187 build_configured_testnet::<D>(*params, &initial_testnet_peers)?
1188 }
1189 (DNetwork::ConfiguredRegtest { params, .. }, _) => {
1190 Network::new_regtest(build_regtest_params(*params))
1191 }
1192 (DNetwork::DefaultForKind(NetworkKind::Mainnet), _) => Network::Mainnet,
1193 (DNetwork::DefaultForKind(NetworkKind::Testnet), Some(params)) => {
1194 build_configured_testnet::<D>(params, &initial_testnet_peers)?
1195 }
1196 (DNetwork::DefaultForKind(NetworkKind::Testnet), None) => {
1197 Network::new_default_testnet()
1198 }
1199 (DNetwork::DefaultForKind(NetworkKind::Regtest), Some(params)) => {
1200 Network::new_regtest(build_regtest_params(params))
1201 }
1202 (DNetwork::DefaultForKind(NetworkKind::Regtest), None) => {
1203 Network::new_regtest(Default::default())
1204 }
1205 };
1206
1207 let listen_addr = match listen_addr.parse::<SocketAddr>().or_else(|_| format!("{listen_addr}:{}", network.default_port()).parse()) {
1208 Ok(socket) => Ok(socket),
1209 Err(_) => match listen_addr.parse::<IpAddr>() {
1210 Ok(ip) => Ok(SocketAddr::new(ip, network.default_port())),
1211 Err(err) => Err(de::Error::custom(format!(
1212 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
1213 ))),
1214 },
1215 }?;
1216
1217 let external_socket_addr = if let Some(address) = &external_addr {
1218 match address.parse::<SocketAddr>().or_else(|_| format!("{address}:{}", network.default_port()).parse()) {
1219 Ok(socket) => Ok(Some(socket)),
1220 Err(_) => match address.parse::<IpAddr>() {
1221 Ok(ip) => Ok(Some(SocketAddr::new(ip, network.default_port()))),
1222 Err(err) => Err(de::Error::custom(format!(
1223 "{err}; Hint: addresses can be a IPv4, IPv6 (with brackets), or a DNS name, the port is optional"
1224 ))),
1225 },
1226 }?
1227 } else {
1228 None
1229 };
1230
1231 let [max_connections_per_ip, peerset_initial_target_size] = [
1232 ("max_connections_per_ip", max_connections_per_ip, DEFAULT_MAX_CONNS_PER_IP),
1233 ("peerset_initial_target_size", Some(peerset_initial_target_size), DEFAULT_PEERSET_INITIAL_TARGET_SIZE)
1236 ].map(|(field_name, non_zero_config_field, default_config_value)| {
1237 if non_zero_config_field == Some(0) {
1238 warn!(
1239 ?field_name,
1240 ?non_zero_config_field,
1241 "{field_name} should be greater than 0, using default value of {default_config_value} instead"
1242 );
1243 }
1244
1245 non_zero_config_field.filter(|config_value| config_value > &0).unwrap_or(default_config_value)
1246 });
1247
1248 let mut zakura = zakura;
1250 zakura.apply_network_defaults(&network);
1251 let default_zakura_bootstrap_peers =
1252 ZakuraConfig::default_bootstrap_peers_for_network(&network);
1253 if zakura.bootstrap_peers.is_empty() {
1254 warn!(
1255 ?network,
1256 "no Zakura bootstrap peers configured; configure zakura.bootstrap_peers or make sure this node receives inbound Zakura connections"
1257 );
1258 } else if network.kind() != NetworkKind::Regtest
1259 && zakura.bootstrap_peers != default_zakura_bootstrap_peers
1260 {
1261 warn!(
1262 ?network,
1263 configured_zakura_bootstrap_peers = ?zakura.bootstrap_peers,
1264 ?default_zakura_bootstrap_peers,
1265 "configured Zakura bootstrap peers differ from the default peers for this network"
1266 );
1267 }
1268 if zakura_listens_on_loopback_with_non_loopback_bootstrap_peers(&zakura) {
1269 warn!(
1270 ?network,
1271 listen_addr = ?zakura.listen_addr,
1272 bootstrap_peers = ?zakura.bootstrap_peers,
1273 "configured Zakura listen_addr is loopback-only, but bootstrap peers use \
1274 non-loopback addresses; native Zakura dials may fail with \
1275 `Can't assign requested address`. Use 0.0.0.0:<port> or another routable \
1276 interface address for public Zakura peers"
1277 );
1278 }
1279 zakura
1280 .block_sync
1281 .clamp_inflight_block_bytes_to_request_floor();
1282 zakura.block_sync.clamp_reorder_lookahead_to_floor();
1283 zakura.block_sync.validate().map_err(|error| {
1284 de::Error::custom(format!("invalid zakura.block_sync config: {error}"))
1285 })?;
1286
1287 Ok(Config {
1288 listen_addr: canonical_socket_addr(listen_addr),
1289 external_addr: external_socket_addr,
1290 network,
1291 initial_mainnet_peers,
1292 initial_testnet_peers,
1293 cache_dir,
1294 identity_dir,
1295 zakura_node_secret_key,
1296 p2p_stack,
1297 zakura,
1298 peerset_initial_target_size,
1299 crawl_new_peer_interval,
1300 max_connections_per_ip,
1301 expose_peer_addresses,
1302 })
1303 }
1304}
1305
1306fn zakura_listens_on_loopback_with_non_loopback_bootstrap_peers(zakura: &ZakuraConfig) -> bool {
1307 let Some(listen_addr) = zakura.listen_addr else {
1308 return false;
1309 };
1310
1311 listen_addr.ip().is_loopback()
1312 && zakura
1313 .bootstrap_peers
1314 .iter()
1315 .filter_map(|peer| peer.rsplit_once('@'))
1316 .filter_map(|(_node_id, addr)| addr.parse::<SocketAddr>().ok())
1317 .any(|addr| !addr.ip().is_loopback())
1318}
1319
1320fn contains_default_initial_peers(initial_peers: &IndexSet<String>) -> bool {
1324 let Config {
1325 initial_mainnet_peers: mut default_initial_peers,
1326 initial_testnet_peers: default_initial_testnet_peers,
1327 ..
1328 } = Config::default();
1329 default_initial_peers.extend(default_initial_testnet_peers);
1330
1331 initial_peers
1332 .intersection(&default_initial_peers)
1333 .next()
1334 .is_some()
1335}
1336
1337fn build_configured_testnet<'de, D>(
1338 params: DTestnetParameters,
1339 initial_testnet_peers: &IndexSet<String>,
1340) -> Result<Network, D::Error>
1341where
1342 D: Deserializer<'de>,
1343{
1344 let DTestnetParameters {
1345 network_name,
1346 network_magic,
1347 slow_start_interval,
1348 target_difficulty_limit,
1349 disable_pow,
1350 max_block_time_start_height,
1351 genesis_hash,
1352 activation_heights,
1353 pre_nu6_funding_streams,
1354 post_nu6_funding_streams,
1355 funding_streams,
1356 pre_blossom_halving_interval,
1357 lockbox_disbursements,
1358 checkpoints,
1359 extend_funding_stream_addresses_as_required,
1360 temporary_orchard_disabling_soft_fork_height,
1361 } = params;
1362
1363 let mut params_builder = testnet::Parameters::build();
1364
1365 if let Some(network_name) = network_name.clone() {
1366 params_builder = params_builder
1367 .with_network_name(network_name)
1368 .map_err(de::Error::custom)?
1369 }
1370
1371 if let Some(network_magic) = network_magic {
1372 params_builder = params_builder
1373 .with_network_magic(Magic(network_magic))
1374 .map_err(de::Error::custom)?;
1375 }
1376
1377 if let Some(genesis_hash) = genesis_hash {
1378 params_builder = params_builder
1379 .with_genesis_hash(genesis_hash)
1380 .map_err(de::Error::custom)?;
1381 }
1382
1383 if let Some(slow_start_interval) = slow_start_interval {
1384 params_builder = params_builder
1385 .with_slow_start_interval(slow_start_interval.try_into().map_err(de::Error::custom)?);
1386 }
1387
1388 if let Some(target_difficulty_limit) = target_difficulty_limit.clone() {
1389 params_builder = params_builder
1390 .with_target_difficulty_limit(
1391 target_difficulty_limit
1392 .parse::<U256>()
1393 .map_err(de::Error::custom)?,
1394 )
1395 .map_err(de::Error::custom)?;
1396 }
1397
1398 if let Some(disable_pow) = disable_pow {
1399 params_builder = params_builder.with_disable_pow(disable_pow);
1400 }
1401
1402 if let Some(height) = max_block_time_start_height {
1403 params_builder = params_builder
1404 .with_max_block_time_start_height(height.try_into().map_err(de::Error::custom)?);
1405 }
1406
1407 if let Some(activation_heights) = activation_heights {
1409 params_builder = params_builder
1410 .with_activation_heights(activation_heights)
1411 .map_err(de::Error::custom)?
1412 }
1413
1414 if let Some(halving_interval) = pre_blossom_halving_interval {
1415 params_builder = params_builder
1416 .with_halving_interval(halving_interval.into())
1417 .map_err(de::Error::custom)?
1418 }
1419
1420 let mut funding_streams_vec = funding_streams.unwrap_or_default();
1422
1423 if let Some(funding_streams) = post_nu6_funding_streams {
1424 funding_streams_vec.insert(0, funding_streams);
1425 }
1426
1427 if let Some(funding_streams) = pre_nu6_funding_streams {
1428 funding_streams_vec.insert(0, funding_streams);
1429 }
1430
1431 if !funding_streams_vec.is_empty() {
1432 params_builder = params_builder.with_funding_streams(funding_streams_vec);
1433 }
1434
1435 if let Some(lockbox_disbursements) = lockbox_disbursements {
1436 params_builder = params_builder.with_lockbox_disbursements(lockbox_disbursements);
1437 }
1438
1439 params_builder = params_builder
1440 .with_checkpoints(checkpoints)
1441 .map_err(de::Error::custom)?;
1442
1443 if let Some(true) = extend_funding_stream_addresses_as_required {
1444 params_builder = params_builder.extend_funding_streams();
1445 }
1446
1447 if let Some(height) = temporary_orchard_disabling_soft_fork_height {
1449 params_builder = params_builder.with_temporary_orchard_disabling_soft_fork_height(
1450 height.try_into().map_err(de::Error::custom)?,
1451 );
1452 }
1453
1454 if !params_builder.is_compatible_with_default_parameters()
1457 && contains_default_initial_peers(initial_testnet_peers)
1458 {
1459 return Err(de::Error::custom(
1460 "cannot use default initials peers with incompatible testnet",
1461 ));
1462 };
1463
1464 if network_name.is_none() && params_builder == testnet::Parameters::build() {
1466 Ok(Network::new_default_testnet())
1467 } else {
1468 Ok(params_builder.to_network().map_err(de::Error::custom)?)
1469 }
1470}
1471
1472fn build_regtest_params(params: DTestnetParameters) -> RegtestParameters {
1473 let DTestnetParameters {
1474 activation_heights,
1475 pre_nu6_funding_streams,
1476 post_nu6_funding_streams,
1477 funding_streams,
1478 lockbox_disbursements,
1479 checkpoints,
1480 extend_funding_stream_addresses_as_required,
1481 max_block_time_start_height,
1482 ..
1483 } = params;
1484
1485 let mut funding_streams_vec = funding_streams.unwrap_or_default();
1486
1487 if let Some(funding_streams) = post_nu6_funding_streams {
1488 funding_streams_vec.insert(0, funding_streams);
1489 }
1490
1491 if let Some(funding_streams) = pre_nu6_funding_streams {
1492 funding_streams_vec.insert(0, funding_streams);
1493 }
1494
1495 RegtestParameters {
1496 activation_heights: activation_heights.unwrap_or_default(),
1497 funding_streams: Some(funding_streams_vec),
1498 lockbox_disbursements,
1499 checkpoints: Some(checkpoints),
1500 max_block_time_start_height: max_block_time_start_height.map(zakura_chain::block::Height),
1501 extend_funding_stream_addresses_as_required,
1502 }
1503}