1use std::collections::{BTreeMap, BTreeSet};
2use std::net::SocketAddr;
3use std::sync::Arc;
4use std::time::{Duration, Instant};
5
6use anyhow::{Context, bail, format_err};
7use bitcoin::hashes::sha256;
8pub use fedimint_core::config::{
9 ClientConfig, FederationId, GlobalClientConfig, JsonWithKind, ModuleInitRegistry, P2PMessage,
10 PeerUrl, ServerModuleConfig, ServerModuleConsensusConfig, TypedServerModuleConfig,
11};
12use fedimint_core::core::{ModuleInstanceId, ModuleKind};
13use fedimint_core::envs::{is_env_var_set, is_running_in_test_env};
14use fedimint_core::module::{
15 ApiVersion, CORE_CONSENSUS_VERSION, CoreConsensusVersion, MultiApiVersion,
16 SupportedApiVersionsSummary, SupportedCoreApiVersions,
17};
18use fedimint_core::net::peers::{DynP2PConnections, Recipient};
19use fedimint_core::setup_code::{PeerEndpoints, PeerSetupCode};
20use fedimint_core::task::sleep;
21use fedimint_core::util::SafeUrl;
22use fedimint_core::{NumPeersExt, PeerId, secp256k1, timing};
23use fedimint_logging::LOG_NET_PEER_DKG;
24use fedimint_server_core::config::PeerHandleOpsExt as _;
25use fedimint_server_core::{ConfigGenModuleArgs, DynServerModuleInit, ServerModuleInitRegistry};
26use futures::future::select_all;
27use hex::{FromHex, ToHex};
28use peer_handle::PeerHandle;
29use rand::rngs::OsRng;
30use secp256k1::{PublicKey, Secp256k1, SecretKey};
31use serde::{Deserialize, Serialize};
32use tokio::select;
33use tokio_rustls::rustls;
34use tracing::{error, info, warn};
35
36use crate::fedimint_core::encoding::Encodable;
37use crate::net::p2p::P2PStatusReceivers;
38use crate::net::p2p_connector::TlsConfig;
39
40pub mod dkg;
41pub mod dkg_g1;
42pub mod dkg_g2;
43pub mod io;
44pub mod peer_handle;
45pub mod setup;
46
47pub const DEFAULT_MAX_CLIENT_CONNECTIONS: u32 = 1000;
49
50const DEFAULT_BROADCAST_ROUND_DELAY_MS: u16 = 50;
52const DEFAULT_BROADCAST_ROUNDS_PER_SESSION: u16 = 3600;
53
54fn default_broadcast_rounds_per_session() -> u16 {
55 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
56}
57
58const DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS: u16 = 50;
60const DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION: u16 = 200;
61
62#[allow(clippy::unsafe_derive_deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct ServerConfig {
66 pub consensus: ServerConfigConsensus,
68 pub local: ServerConfigLocal,
70 pub private: ServerConfigPrivate,
73}
74
75impl ServerConfig {
76 pub fn iter_module_instances(
77 &self,
78 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
79 self.consensus.iter_module_instances()
80 }
81
82 pub(crate) fn supported_api_versions_summary(
83 modules: &BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
84 module_inits: &ServerModuleInitRegistry,
85 ) -> SupportedApiVersionsSummary {
86 SupportedApiVersionsSummary {
87 core: Self::supported_api_versions(),
88 modules: modules
89 .iter()
90 .map(|(&id, config)| {
91 (
92 id,
93 module_inits
94 .get(&config.kind)
95 .expect("missing module kind gen")
96 .supported_api_versions(),
97 )
98 })
99 .collect(),
100 }
101 }
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
105pub struct ServerConfigPrivate {
106 pub tls_key: Option<String>,
108 #[serde(default)]
110 pub iroh_api_sk: Option<iroh::SecretKey>,
111 #[serde(default)]
113 pub iroh_p2p_sk: Option<iroh::SecretKey>,
114 pub broadcast_secret_key: SecretKey,
116 pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
121pub struct ServerConfigConsensus {
122 pub code_version: String,
125 pub version: CoreConsensusVersion,
127 pub broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
129 #[serde(default = "default_broadcast_rounds_per_session")]
131 pub broadcast_rounds_per_session: u16,
132 pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
134 #[serde(default)]
136 pub iroh_endpoints: BTreeMap<PeerId, PeerIrohEndpoints>,
137 pub tls_certs: BTreeMap<PeerId, String>,
139 pub modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
141 pub meta: BTreeMap<String, String>,
143}
144
145#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
146pub struct PeerIrohEndpoints {
147 pub name: String,
149 pub api_pk: iroh::PublicKey,
151 pub p2p_pk: iroh::PublicKey,
153}
154
155pub fn legacy_consensus_config_hash(cfg: &ServerConfigConsensus) -> sha256::Hash {
156 #[derive(Encodable)]
157 struct LegacyServerConfigConsensusHashMap {
158 code_version: String,
159 version: CoreConsensusVersion,
160 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
161 broadcast_rounds_per_session: u16,
162 api_endpoints: BTreeMap<PeerId, PeerUrl>,
163 tls_certs: BTreeMap<PeerId, String>,
164 modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
165 meta: BTreeMap<String, String>,
166 }
167
168 LegacyServerConfigConsensusHashMap {
169 code_version: cfg.code_version.clone(),
170 version: cfg.version,
171 broadcast_public_keys: cfg.broadcast_public_keys.clone(),
172 broadcast_rounds_per_session: cfg.broadcast_rounds_per_session,
173 api_endpoints: cfg.api_endpoints.clone(),
174 tls_certs: cfg.tls_certs.clone(),
175 modules: cfg.modules.clone(),
176 meta: cfg.meta.clone(),
177 }
178 .consensus_hash_sha256()
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct ServerConfigLocal {
185 pub p2p_endpoints: BTreeMap<PeerId, PeerUrl>,
187 pub identity: PeerId,
189 pub max_connections: u32,
191 pub broadcast_round_delay_ms: u16,
196}
197
198#[derive(Debug, Clone)]
200pub struct ConfigGenSettings {
201 pub p2p_bind: SocketAddr,
203 pub api_bind: SocketAddr,
205 pub ui_bind: SocketAddr,
207 pub p2p_url: Option<SafeUrl>,
209 pub api_url: Option<SafeUrl>,
211 pub enable_iroh: bool,
213 pub iroh_dns: Option<SafeUrl>,
215 pub iroh_relays: Vec<SafeUrl>,
217 pub network: bitcoin::Network,
219 pub available_modules: BTreeSet<ModuleKind>,
221 pub default_modules: BTreeSet<ModuleKind>,
223}
224
225#[derive(Debug, Clone)]
226pub struct ConfigGenParams {
231 pub identity: PeerId,
233 pub tls_key: Option<Arc<rustls::pki_types::PrivateKeyDer<'static>>>,
235 pub iroh_api_sk: Option<iroh::SecretKey>,
237 pub iroh_p2p_sk: Option<iroh::SecretKey>,
239 pub peers: BTreeMap<PeerId, PeerSetupCode>,
241 pub meta: BTreeMap<String, String>,
243 pub disable_base_fees: bool,
245 pub enabled_modules: BTreeSet<ModuleKind>,
247 pub network: bitcoin::Network,
249}
250
251impl ServerConfigConsensus {
252 pub fn api_endpoints(&self) -> BTreeMap<PeerId, PeerUrl> {
253 if self.iroh_endpoints.is_empty() {
254 self.api_endpoints.clone()
255 } else {
256 self.iroh_endpoints
257 .iter()
258 .map(|(peer, endpoints)| {
259 let url = PeerUrl {
260 name: endpoints.name.clone(),
261 url: SafeUrl::parse(&format!("iroh://{}", endpoints.api_pk))
262 .expect("Failed to parse iroh url"),
263 };
264
265 (*peer, url)
266 })
267 .collect()
268 }
269 }
270
271 pub fn iter_module_instances(
272 &self,
273 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
274 self.modules.iter().map(|(k, v)| (*k, &v.kind))
275 }
276
277 pub fn to_client_config(
278 &self,
279 module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
280 ) -> Result<ClientConfig, anyhow::Error> {
281 let client = ClientConfig {
282 global: GlobalClientConfig {
283 api_endpoints: self.api_endpoints(),
284 broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
285 consensus_version: self.version,
286 meta: self.meta.clone(),
287 },
288 modules: self
289 .modules
290 .iter()
291 .map(|(k, v)| {
292 let r#gen = module_config_gens
293 .get(&v.kind)
294 .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
295 Ok((*k, r#gen.get_client_config(*k, v)?))
296 })
297 .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
298 };
299 Ok(client)
300 }
301}
302
303impl ServerConfig {
304 pub fn supported_api_versions() -> SupportedCoreApiVersions {
306 SupportedCoreApiVersions {
307 core_consensus: CORE_CONSENSUS_VERSION,
308 api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 9 }])
309 .expect("not version conflicts"),
310 }
311 }
312 pub fn from(
315 params: ConfigGenParams,
316 identity: PeerId,
317 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
318 broadcast_secret_key: SecretKey,
319 modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
320 code_version: String,
321 ) -> Self {
322 let consensus = ServerConfigConsensus {
323 code_version: fedimint_core::version::release_version(&code_version).to_owned(),
324 version: CORE_CONSENSUS_VERSION,
325 broadcast_public_keys,
326 broadcast_rounds_per_session: if is_running_in_test_env() {
327 DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
328 } else {
329 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
330 },
331 api_endpoints: params.api_urls(),
332 iroh_endpoints: params.iroh_endpoints(),
333 tls_certs: params.tls_certs(),
334 modules: modules
335 .iter()
336 .map(|(peer, cfg)| (*peer, cfg.consensus.clone()))
337 .collect(),
338 meta: params.meta.clone(),
339 };
340
341 let local = ServerConfigLocal {
342 p2p_endpoints: params.p2p_urls(),
343 identity,
344 max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
345 broadcast_round_delay_ms: if is_running_in_test_env() {
346 DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
347 } else {
348 DEFAULT_BROADCAST_ROUND_DELAY_MS
349 },
350 };
351
352 let private = ServerConfigPrivate {
353 tls_key: params
354 .tls_key
355 .map(|key| key.secret_der().to_vec().encode_hex()),
356 iroh_api_sk: params.iroh_api_sk,
357 iroh_p2p_sk: params.iroh_p2p_sk,
358 broadcast_secret_key,
359 modules: modules
360 .iter()
361 .map(|(peer, cfg)| (*peer, cfg.private.clone()))
362 .collect(),
363 };
364
365 Self {
366 consensus,
367 local,
368 private,
369 }
370 }
371
372 pub fn calculate_federation_id(&self) -> FederationId {
373 FederationId(self.consensus.api_endpoints().consensus_hash())
374 }
375
376 pub fn get_module_config_typed<T: TypedServerModuleConfig>(
378 &self,
379 id: ModuleInstanceId,
380 ) -> anyhow::Result<T> {
381 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
382 let consensus = self
383 .consensus
384 .modules
385 .get(&id)
386 .ok_or_else(|| format_err!("Typed module {id} not found"))?
387 .clone();
388 let module = ServerModuleConfig::from(private, consensus);
389
390 module.to_typed()
391 }
392 pub fn get_module_id_by_kind(
393 &self,
394 kind: impl Into<ModuleKind>,
395 ) -> anyhow::Result<ModuleInstanceId> {
396 let kind = kind.into();
397 Ok(*self
398 .consensus
399 .modules
400 .iter()
401 .find(|(_, v)| v.kind == kind)
402 .ok_or_else(|| format_err!("Module {kind} not found"))?
403 .0)
404 }
405
406 pub fn get_module_config(&self, id: ModuleInstanceId) -> anyhow::Result<ServerModuleConfig> {
408 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
409 let consensus = self
410 .consensus
411 .modules
412 .get(&id)
413 .ok_or_else(|| format_err!("Module config {id} not found"))?
414 .clone();
415 Ok(ServerModuleConfig::from(private, consensus))
416 }
417
418 fn get_module_cfg_by_instance_id(
419 json: &BTreeMap<ModuleInstanceId, JsonWithKind>,
420 id: ModuleInstanceId,
421 ) -> anyhow::Result<JsonWithKind> {
422 Ok(json
423 .get(&id)
424 .ok_or_else(|| format_err!("Module cfg {id} not found"))
425 .cloned()?
426 .with_fixed_empty_value())
427 }
428
429 pub fn validate_config(
430 &self,
431 identity: &PeerId,
432 module_config_gens: &ServerModuleInitRegistry,
433 ) -> anyhow::Result<()> {
434 let endpoints = self.consensus.api_endpoints().clone();
435 let consensus = self.consensus.clone();
436 let private = self.private.clone();
437
438 let my_public_key = private.broadcast_secret_key.public_key(&Secp256k1::new());
439
440 if Some(&my_public_key) != consensus.broadcast_public_keys.get(identity) {
441 bail!("Broadcast secret key doesn't match corresponding public key");
442 }
443 if endpoints.keys().max().copied().map(PeerId::to_usize) != Some(endpoints.len() - 1) {
444 bail!("Peer ids are not indexed from 0");
445 }
446 if endpoints.keys().min().copied() != Some(PeerId::from(0)) {
447 bail!("Peer ids are not indexed from 0");
448 }
449
450 for (module_id, module_kind) in &self
451 .consensus
452 .modules
453 .iter()
454 .map(|(id, config)| Ok((*id, config.kind.clone())))
455 .collect::<anyhow::Result<BTreeSet<_>>>()?
456 {
457 module_config_gens
458 .get(module_kind)
459 .ok_or_else(|| format_err!("module config gen not found {module_kind}"))?
460 .validate_config(identity, self.get_module_config(*module_id)?)?;
461 }
462
463 Ok(())
464 }
465
466 pub fn trusted_dealer_gen(
467 params: &BTreeMap<PeerId, ConfigGenParams>,
468 registry: &ServerModuleInitRegistry,
469 code_version_str: &str,
470 ) -> BTreeMap<PeerId, Self> {
471 let peer0 = ¶ms[&PeerId::from(0)];
472
473 let mut broadcast_pks = BTreeMap::new();
474 let mut broadcast_sks = BTreeMap::new();
475 for peer_id in peer0.peer_ids() {
476 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
477 broadcast_pks.insert(peer_id, broadcast_pk);
478 broadcast_sks.insert(peer_id, broadcast_sk);
479 }
480
481 let args = ConfigGenModuleArgs {
482 network: peer0.network,
483 disable_base_fees: peer0.disable_base_fees,
484 };
485
486 let use_legacy_order = is_env_var_set("FM_BACKWARDS_COMPATIBILITY_TEST");
488 let module_iter: Vec<_> = if use_legacy_order {
489 registry.iter_legacy_order()
490 } else {
491 registry.iter().collect()
492 };
493
494 let module_configs: BTreeMap<_, _> = module_iter
495 .into_iter()
496 .filter(|(kind, _)| peer0.enabled_modules.contains(kind))
497 .enumerate()
498 .map(|(module_id, (_kind, module_init))| {
499 (
500 module_id as ModuleInstanceId,
501 module_init.trusted_dealer_gen(&peer0.peer_ids(), &args),
502 )
503 })
504 .collect();
505
506 let server_config: BTreeMap<_, _> = peer0
507 .peer_ids()
508 .iter()
509 .map(|&id| {
510 let config = ServerConfig::from(
511 params[&id].clone(),
512 id,
513 broadcast_pks.clone(),
514 *broadcast_sks.get(&id).expect("We created this entry"),
515 module_configs
516 .iter()
517 .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
518 .collect(),
519 code_version_str.to_string(),
520 );
521 (id, config)
522 })
523 .collect();
524
525 server_config
526 }
527
528 pub async fn distributed_gen(
530 params: &ConfigGenParams,
531 registry: ServerModuleInitRegistry,
532 code_version_str: String,
533 connections: DynP2PConnections<P2PMessage>,
534 mut p2p_status_receivers: P2PStatusReceivers,
535 ) -> anyhow::Result<Self> {
536 let _timing = timing::TimeReporter::new("distributed-gen").info();
537
538 if params.peer_ids().len() == 1 {
540 let server = Self::trusted_dealer_gen(
541 &BTreeMap::from([(params.identity, params.clone())]),
542 ®istry,
543 &code_version_str,
544 );
545
546 return Ok(server[¶ms.identity].clone());
547 }
548
549 info!(
550 target: LOG_NET_PEER_DKG,
551 "Waiting for all p2p connections to open..."
552 );
553
554 loop {
555 let mut pending_connection_receivers: Vec<_> = p2p_status_receivers
556 .iter_mut()
557 .filter_map(|(p, r)| {
558 r.mark_unchanged();
559 r.borrow().connected.is_none().then_some((*p, r.clone()))
560 })
561 .collect();
562
563 if pending_connection_receivers.is_empty() {
564 break;
565 }
566
567 let disconnected_peers = pending_connection_receivers
568 .iter()
569 .map(|(peer, receiver)| {
570 let last_error = receiver.borrow().last_error.clone();
571
572 (*peer, last_error)
573 })
574 .collect::<Vec<_>>();
575
576 info!(
577 target: LOG_NET_PEER_DKG,
578 pending = ?disconnected_peers,
579 "Waiting for all p2p connections to open..."
580 );
581
582 select! {
583 _ = select_all(pending_connection_receivers.iter_mut().map(|r| Box::pin(r.1.changed()))) => {}
584 () = sleep(Duration::from_secs(10)) => {}
585 }
586 }
587
588 let checksum = params.peers.consensus_hash_sha256();
589
590 info!(
591 target: LOG_NET_PEER_DKG,
592 "Comparing connection codes checksum {checksum}..."
593 );
594
595 connections.send(Recipient::Everyone, P2PMessage::Checksum(checksum));
596
597 for peer in params
598 .peer_ids()
599 .into_iter()
600 .filter(|p| *p != params.identity)
601 {
602 let peer_message = receive_from_peer_with_progress(
603 &connections,
604 peer,
605 "connection code checksum message",
606 )
607 .await?;
608
609 if peer_message != P2PMessage::Checksum(checksum) {
610 error!(
611 target: LOG_NET_PEER_DKG,
612 expected = ?P2PMessage::Checksum(checksum),
613 received = ?peer_message,
614 "Peer {peer} has sent invalid connection code checksum message"
615 );
616
617 bail!("Peer {peer} has sent invalid connection code checksum message");
618 }
619
620 info!(
621 target: LOG_NET_PEER_DKG,
622 "Peer {peer} has sent valid connection code checksum message"
623 );
624 }
625
626 info!(
627 target: LOG_NET_PEER_DKG,
628 "Running config generation..."
629 );
630
631 let handle = PeerHandle::new(
632 params.peer_ids().to_num_peers(),
633 params.identity,
634 &connections,
635 );
636
637 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
638
639 let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
640
641 let args = ConfigGenModuleArgs {
642 network: params.network,
643 disable_base_fees: params.disable_base_fees,
644 };
645
646 let use_legacy_order = is_env_var_set("FM_BACKWARDS_COMPATIBILITY_TEST");
648 let module_iter: Vec<_> = if use_legacy_order {
649 registry.iter_legacy_order()
650 } else {
651 registry.iter().collect()
652 };
653
654 let mut module_cfgs = BTreeMap::new();
655
656 for (module_id, (kind, module_init)) in module_iter
657 .into_iter()
658 .filter(|(kind, _)| params.enabled_modules.contains(kind))
659 .enumerate()
660 {
661 info!(
662 target: LOG_NET_PEER_DKG,
663 "Running config generation for module of kind {kind}..."
664 );
665
666 let cfg = module_init.distributed_gen(&handle, &args).await?;
667
668 module_cfgs.insert(module_id as ModuleInstanceId, cfg);
669 }
670
671 let cfg = ServerConfig::from(
672 params.clone(),
673 params.identity,
674 broadcast_public_keys,
675 broadcast_sk,
676 module_cfgs,
677 code_version_str,
678 );
679
680 let checksum = cfg.consensus.consensus_hash_sha256();
681
682 info!(
683 target: LOG_NET_PEER_DKG,
684 "Comparing consensus config checksum {checksum}..."
685 );
686
687 connections.send(Recipient::Everyone, P2PMessage::Checksum(checksum));
688
689 for peer in params
690 .peer_ids()
691 .into_iter()
692 .filter(|p| *p != params.identity)
693 {
694 let peer_message =
695 receive_from_peer_with_progress(&connections, peer, "consensus config checksum")
696 .await?;
697
698 if peer_message != P2PMessage::Checksum(checksum) {
699 warn!(
700 target: LOG_NET_PEER_DKG,
701 expected = ?P2PMessage::Checksum(checksum),
702 received = ?peer_message,
703 config = ?cfg.consensus,
704 "Peer {peer} has sent invalid consensus config checksum message"
705 );
706
707 bail!("Peer {peer} has sent invalid consensus config checksum message");
708 }
709
710 info!(
711 target: LOG_NET_PEER_DKG,
712 "Peer {peer} has sent valid consensus config checksum message"
713 );
714 }
715
716 info!(
717 target: LOG_NET_PEER_DKG,
718 "Config generation has completed successfully!"
719 );
720
721 Ok(cfg)
722 }
723}
724
725async fn receive_from_peer_with_progress(
726 connections: &DynP2PConnections<P2PMessage>,
727 peer: PeerId,
728 message_description: &'static str,
729) -> anyhow::Result<P2PMessage> {
730 let start = Instant::now();
731
732 loop {
733 select! {
734 peer_message = connections.receive_from_peer(peer) => {
738 return peer_message.context("Unexpected shutdown of p2p connections");
739 }
740 () = sleep(Duration::from_secs(10)) => {
741 info!(
742 target: LOG_NET_PEER_DKG,
743 %peer,
744 message = message_description,
745 elapsed_secs = start.elapsed().as_secs(),
746 "Still waiting for peer message"
747 );
748 }
749 }
750 }
751}
752
753impl ServerConfig {
754 pub fn tls_config(&self) -> TlsConfig {
755 TlsConfig {
756 private_key: Arc::new(
757 rustls::pki_types::PrivateKeyDer::try_from(
758 Vec::from_hex(self.private.tls_key.clone().unwrap()).unwrap(),
759 )
760 .expect("Failed to parse private key"),
761 ),
762 certificates: self
763 .consensus
764 .tls_certs
765 .iter()
766 .map(|(peer, cert)| {
767 (
768 *peer,
769 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
770 )
771 })
772 .collect(),
773 peer_names: self
774 .local
775 .p2p_endpoints
776 .iter()
777 .map(|(id, endpoint)| (*id, endpoint.name.clone()))
778 .collect(),
779 }
780 }
781}
782
783impl ConfigGenParams {
784 pub fn peer_ids(&self) -> Vec<PeerId> {
785 self.peers.keys().copied().collect()
786 }
787
788 pub fn tls_config(&self) -> TlsConfig {
789 TlsConfig {
790 private_key: self.tls_key.clone().unwrap(),
791 certificates: self
792 .tls_certs()
793 .iter()
794 .map(|(peer, cert)| {
795 (
796 *peer,
797 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
798 )
799 })
800 .collect(),
801 peer_names: self
802 .p2p_urls()
803 .into_iter()
804 .map(|(id, peer)| (id, peer.name))
805 .collect(),
806 }
807 }
808
809 pub fn tls_certs(&self) -> BTreeMap<PeerId, String> {
810 self.peers
811 .iter()
812 .filter_map(|(id, peer)| {
813 match peer.endpoints.clone() {
814 PeerEndpoints::Tcp { cert, .. } => Some(cert.encode_hex()),
815 PeerEndpoints::Iroh { .. } => None,
816 }
817 .map(|peer| (*id, peer))
818 })
819 .collect()
820 }
821
822 pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
823 self.peers
824 .iter()
825 .filter_map(|(id, peer)| {
826 match peer.endpoints.clone() {
827 PeerEndpoints::Tcp { p2p_url, .. } => Some(PeerUrl {
828 name: peer.name.clone(),
829 url: p2p_url.clone(),
830 }),
831 PeerEndpoints::Iroh { .. } => None,
832 }
833 .map(|peer| (*id, peer))
834 })
835 .collect()
836 }
837
838 pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
839 self.peers
840 .iter()
841 .filter_map(|(id, peer)| {
842 match peer.endpoints.clone() {
843 PeerEndpoints::Tcp { api_url, .. } => Some(PeerUrl {
844 name: peer.name.clone(),
845 url: api_url.clone(),
846 }),
847 PeerEndpoints::Iroh { .. } => None,
848 }
849 .map(|peer| (*id, peer))
850 })
851 .collect()
852 }
853
854 pub fn iroh_endpoints(&self) -> BTreeMap<PeerId, PeerIrohEndpoints> {
855 self.peers
856 .iter()
857 .filter_map(|(id, peer)| {
858 match peer.endpoints.clone() {
859 PeerEndpoints::Tcp { .. } => None,
860 PeerEndpoints::Iroh { api_pk, p2p_pk } => Some(PeerIrohEndpoints {
861 name: peer.name.clone(),
862 api_pk,
863 p2p_pk,
864 }),
865 }
866 .map(|peer| (*id, peer))
867 })
868 .collect()
869 }
870}