1use std::collections::{BTreeMap, BTreeSet};
2use std::net::SocketAddr;
3use std::sync::Arc;
4use std::time::Duration;
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 ApiAuth, 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 api_auth: ApiAuth,
108 pub tls_key: Option<String>,
110 #[serde(default)]
112 pub iroh_api_sk: Option<iroh::SecretKey>,
113 #[serde(default)]
115 pub iroh_p2p_sk: Option<iroh::SecretKey>,
116 pub broadcast_secret_key: SecretKey,
118 pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
123pub struct ServerConfigConsensus {
124 pub code_version: String,
126 pub version: CoreConsensusVersion,
128 pub broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
130 #[serde(default = "default_broadcast_rounds_per_session")]
132 pub broadcast_rounds_per_session: u16,
133 pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
135 #[serde(default)]
137 pub iroh_endpoints: BTreeMap<PeerId, PeerIrohEndpoints>,
138 pub tls_certs: BTreeMap<PeerId, String>,
140 pub modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
142 pub meta: BTreeMap<String, String>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
147pub struct PeerIrohEndpoints {
148 pub name: String,
150 pub api_pk: iroh::PublicKey,
152 pub p2p_pk: iroh::PublicKey,
154}
155
156pub fn legacy_consensus_config_hash(cfg: &ServerConfigConsensus) -> sha256::Hash {
157 #[derive(Encodable)]
158 struct LegacyServerConfigConsensusHashMap {
159 code_version: String,
160 version: CoreConsensusVersion,
161 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
162 broadcast_rounds_per_session: u16,
163 api_endpoints: BTreeMap<PeerId, PeerUrl>,
164 tls_certs: BTreeMap<PeerId, String>,
165 modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
166 meta: BTreeMap<String, String>,
167 }
168
169 LegacyServerConfigConsensusHashMap {
170 code_version: cfg.code_version.clone(),
171 version: cfg.version,
172 broadcast_public_keys: cfg.broadcast_public_keys.clone(),
173 broadcast_rounds_per_session: cfg.broadcast_rounds_per_session,
174 api_endpoints: cfg.api_endpoints.clone(),
175 tls_certs: cfg.tls_certs.clone(),
176 modules: cfg.modules.clone(),
177 meta: cfg.meta.clone(),
178 }
179 .consensus_hash_sha256()
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
185pub struct ServerConfigLocal {
186 pub p2p_endpoints: BTreeMap<PeerId, PeerUrl>,
188 pub identity: PeerId,
190 pub max_connections: u32,
192 pub broadcast_round_delay_ms: u16,
197}
198
199#[derive(Debug, Clone)]
201pub struct ConfigGenSettings {
202 pub p2p_bind: SocketAddr,
204 pub api_bind: SocketAddr,
206 pub ui_bind: SocketAddr,
208 pub p2p_url: Option<SafeUrl>,
210 pub api_url: Option<SafeUrl>,
212 pub enable_iroh: bool,
214 pub iroh_dns: Option<SafeUrl>,
216 pub iroh_relays: Vec<SafeUrl>,
218 pub network: bitcoin::Network,
220 pub available_modules: BTreeSet<ModuleKind>,
222 pub default_modules: BTreeSet<ModuleKind>,
224}
225
226#[derive(Debug, Clone)]
227pub struct ConfigGenParams {
232 pub identity: PeerId,
234 pub tls_key: Option<Arc<rustls::pki_types::PrivateKeyDer<'static>>>,
236 pub iroh_api_sk: Option<iroh::SecretKey>,
238 pub iroh_p2p_sk: Option<iroh::SecretKey>,
240 pub api_auth: ApiAuth,
242 pub peers: BTreeMap<PeerId, PeerSetupCode>,
244 pub meta: BTreeMap<String, String>,
246 pub disable_base_fees: bool,
248 pub enabled_modules: BTreeSet<ModuleKind>,
250 pub network: bitcoin::Network,
252}
253
254impl ServerConfigConsensus {
255 pub fn api_endpoints(&self) -> BTreeMap<PeerId, PeerUrl> {
256 if self.iroh_endpoints.is_empty() {
257 self.api_endpoints.clone()
258 } else {
259 self.iroh_endpoints
260 .iter()
261 .map(|(peer, endpoints)| {
262 let url = PeerUrl {
263 name: endpoints.name.clone(),
264 url: SafeUrl::parse(&format!("iroh://{}", endpoints.api_pk))
265 .expect("Failed to parse iroh url"),
266 };
267
268 (*peer, url)
269 })
270 .collect()
271 }
272 }
273
274 pub fn iter_module_instances(
275 &self,
276 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
277 self.modules.iter().map(|(k, v)| (*k, &v.kind))
278 }
279
280 pub fn to_client_config(
281 &self,
282 module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
283 ) -> Result<ClientConfig, anyhow::Error> {
284 let client = ClientConfig {
285 global: GlobalClientConfig {
286 api_endpoints: self.api_endpoints(),
287 broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
288 consensus_version: self.version,
289 meta: self.meta.clone(),
290 },
291 modules: self
292 .modules
293 .iter()
294 .map(|(k, v)| {
295 let r#gen = module_config_gens
296 .get(&v.kind)
297 .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
298 Ok((*k, r#gen.get_client_config(*k, v)?))
299 })
300 .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
301 };
302 Ok(client)
303 }
304}
305
306impl ServerConfig {
307 pub fn supported_api_versions() -> SupportedCoreApiVersions {
309 SupportedCoreApiVersions {
310 core_consensus: CORE_CONSENSUS_VERSION,
311 api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 9 }])
312 .expect("not version conflicts"),
313 }
314 }
315 pub fn from(
318 params: ConfigGenParams,
319 identity: PeerId,
320 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
321 broadcast_secret_key: SecretKey,
322 modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
323 code_version: String,
324 ) -> Self {
325 let consensus = ServerConfigConsensus {
326 code_version,
327 version: CORE_CONSENSUS_VERSION,
328 broadcast_public_keys,
329 broadcast_rounds_per_session: if is_running_in_test_env() {
330 DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
331 } else {
332 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
333 },
334 api_endpoints: params.api_urls(),
335 iroh_endpoints: params.iroh_endpoints(),
336 tls_certs: params.tls_certs(),
337 modules: modules
338 .iter()
339 .map(|(peer, cfg)| (*peer, cfg.consensus.clone()))
340 .collect(),
341 meta: params.meta.clone(),
342 };
343
344 let local = ServerConfigLocal {
345 p2p_endpoints: params.p2p_urls(),
346 identity,
347 max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
348 broadcast_round_delay_ms: if is_running_in_test_env() {
349 DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
350 } else {
351 DEFAULT_BROADCAST_ROUND_DELAY_MS
352 },
353 };
354
355 let private = ServerConfigPrivate {
356 api_auth: params.api_auth.clone(),
357 tls_key: params
358 .tls_key
359 .map(|key| key.secret_der().to_vec().encode_hex()),
360 iroh_api_sk: params.iroh_api_sk,
361 iroh_p2p_sk: params.iroh_p2p_sk,
362 broadcast_secret_key,
363 modules: modules
364 .iter()
365 .map(|(peer, cfg)| (*peer, cfg.private.clone()))
366 .collect(),
367 };
368
369 Self {
370 consensus,
371 local,
372 private,
373 }
374 }
375
376 pub fn calculate_federation_id(&self) -> FederationId {
377 FederationId(self.consensus.api_endpoints().consensus_hash())
378 }
379
380 pub fn get_module_config_typed<T: TypedServerModuleConfig>(
382 &self,
383 id: ModuleInstanceId,
384 ) -> anyhow::Result<T> {
385 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
386 let consensus = self
387 .consensus
388 .modules
389 .get(&id)
390 .ok_or_else(|| format_err!("Typed module {id} not found"))?
391 .clone();
392 let module = ServerModuleConfig::from(private, consensus);
393
394 module.to_typed()
395 }
396 pub fn get_module_id_by_kind(
397 &self,
398 kind: impl Into<ModuleKind>,
399 ) -> anyhow::Result<ModuleInstanceId> {
400 let kind = kind.into();
401 Ok(*self
402 .consensus
403 .modules
404 .iter()
405 .find(|(_, v)| v.kind == kind)
406 .ok_or_else(|| format_err!("Module {kind} not found"))?
407 .0)
408 }
409
410 pub fn get_module_config(&self, id: ModuleInstanceId) -> anyhow::Result<ServerModuleConfig> {
412 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
413 let consensus = self
414 .consensus
415 .modules
416 .get(&id)
417 .ok_or_else(|| format_err!("Module config {id} not found"))?
418 .clone();
419 Ok(ServerModuleConfig::from(private, consensus))
420 }
421
422 fn get_module_cfg_by_instance_id(
423 json: &BTreeMap<ModuleInstanceId, JsonWithKind>,
424 id: ModuleInstanceId,
425 ) -> anyhow::Result<JsonWithKind> {
426 Ok(json
427 .get(&id)
428 .ok_or_else(|| format_err!("Module cfg {id} not found"))
429 .cloned()?
430 .with_fixed_empty_value())
431 }
432
433 pub fn validate_config(
434 &self,
435 identity: &PeerId,
436 module_config_gens: &ServerModuleInitRegistry,
437 ) -> anyhow::Result<()> {
438 let endpoints = self.consensus.api_endpoints().clone();
439 let consensus = self.consensus.clone();
440 let private = self.private.clone();
441
442 let my_public_key = private.broadcast_secret_key.public_key(&Secp256k1::new());
443
444 if Some(&my_public_key) != consensus.broadcast_public_keys.get(identity) {
445 bail!("Broadcast secret key doesn't match corresponding public key");
446 }
447 if endpoints.keys().max().copied().map(PeerId::to_usize) != Some(endpoints.len() - 1) {
448 bail!("Peer ids are not indexed from 0");
449 }
450 if endpoints.keys().min().copied() != Some(PeerId::from(0)) {
451 bail!("Peer ids are not indexed from 0");
452 }
453
454 for (module_id, module_kind) in &self
455 .consensus
456 .modules
457 .iter()
458 .map(|(id, config)| Ok((*id, config.kind.clone())))
459 .collect::<anyhow::Result<BTreeSet<_>>>()?
460 {
461 module_config_gens
462 .get(module_kind)
463 .ok_or_else(|| format_err!("module config gen not found {module_kind}"))?
464 .validate_config(identity, self.get_module_config(*module_id)?)?;
465 }
466
467 Ok(())
468 }
469
470 pub fn trusted_dealer_gen(
471 params: &BTreeMap<PeerId, ConfigGenParams>,
472 registry: &ServerModuleInitRegistry,
473 code_version_str: &str,
474 ) -> BTreeMap<PeerId, Self> {
475 let peer0 = ¶ms[&PeerId::from(0)];
476
477 let mut broadcast_pks = BTreeMap::new();
478 let mut broadcast_sks = BTreeMap::new();
479 for peer_id in peer0.peer_ids() {
480 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
481 broadcast_pks.insert(peer_id, broadcast_pk);
482 broadcast_sks.insert(peer_id, broadcast_sk);
483 }
484
485 let args = ConfigGenModuleArgs {
486 network: peer0.network,
487 disable_base_fees: peer0.disable_base_fees,
488 };
489
490 let use_legacy_order = is_env_var_set("FM_BACKWARDS_COMPATIBILITY_TEST");
492 let module_iter: Vec<_> = if use_legacy_order {
493 registry.iter_legacy_order()
494 } else {
495 registry.iter().collect()
496 };
497
498 let module_configs: BTreeMap<_, _> = module_iter
499 .into_iter()
500 .filter(|(kind, _)| peer0.enabled_modules.contains(kind))
501 .enumerate()
502 .map(|(module_id, (_kind, module_init))| {
503 (
504 module_id as ModuleInstanceId,
505 module_init.trusted_dealer_gen(&peer0.peer_ids(), &args),
506 )
507 })
508 .collect();
509
510 let server_config: BTreeMap<_, _> = peer0
511 .peer_ids()
512 .iter()
513 .map(|&id| {
514 let config = ServerConfig::from(
515 params[&id].clone(),
516 id,
517 broadcast_pks.clone(),
518 *broadcast_sks.get(&id).expect("We created this entry"),
519 module_configs
520 .iter()
521 .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
522 .collect(),
523 code_version_str.to_string(),
524 );
525 (id, config)
526 })
527 .collect();
528
529 server_config
530 }
531
532 pub async fn distributed_gen(
534 params: &ConfigGenParams,
535 registry: ServerModuleInitRegistry,
536 code_version_str: String,
537 connections: DynP2PConnections<P2PMessage>,
538 mut p2p_status_receivers: P2PStatusReceivers,
539 ) -> anyhow::Result<Self> {
540 let _timing = timing::TimeReporter::new("distributed-gen").info();
541
542 if params.peer_ids().len() == 1 {
544 let server = Self::trusted_dealer_gen(
545 &BTreeMap::from([(params.identity, params.clone())]),
546 ®istry,
547 &code_version_str,
548 );
549
550 return Ok(server[¶ms.identity].clone());
551 }
552
553 info!(
554 target: LOG_NET_PEER_DKG,
555 "Waiting for all p2p connections to open..."
556 );
557
558 loop {
559 let mut pending_connection_receivers: Vec<_> = p2p_status_receivers
560 .iter_mut()
561 .filter_map(|(p, r)| {
562 r.mark_unchanged();
563 r.borrow().is_none().then_some((*p, r.clone()))
564 })
565 .collect();
566
567 if pending_connection_receivers.is_empty() {
568 break;
569 }
570
571 let disconnected_peers = pending_connection_receivers
572 .iter()
573 .map(|entry| entry.0)
574 .collect::<Vec<PeerId>>();
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 = connections
603 .receive_from_peer(peer)
604 .await
605 .context("Unexpected shutdown of p2p connections")?;
606
607 if peer_message != P2PMessage::Checksum(checksum) {
608 error!(
609 target: LOG_NET_PEER_DKG,
610 expected = ?P2PMessage::Checksum(checksum),
611 received = ?peer_message,
612 "Peer {peer} has sent invalid connection code checksum message"
613 );
614
615 bail!("Peer {peer} has sent invalid connection code checksum message");
616 }
617
618 info!(
619 target: LOG_NET_PEER_DKG,
620 "Peer {peer} has sent valid connection code checksum message"
621 );
622 }
623
624 info!(
625 target: LOG_NET_PEER_DKG,
626 "Running config generation..."
627 );
628
629 let handle = PeerHandle::new(
630 params.peer_ids().to_num_peers(),
631 params.identity,
632 &connections,
633 );
634
635 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
636
637 let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
638
639 let args = ConfigGenModuleArgs {
640 network: params.network,
641 disable_base_fees: params.disable_base_fees,
642 };
643
644 let use_legacy_order = is_env_var_set("FM_BACKWARDS_COMPATIBILITY_TEST");
646 let module_iter: Vec<_> = if use_legacy_order {
647 registry.iter_legacy_order()
648 } else {
649 registry.iter().collect()
650 };
651
652 let mut module_cfgs = BTreeMap::new();
653
654 for (module_id, (kind, module_init)) in module_iter
655 .into_iter()
656 .filter(|(kind, _)| params.enabled_modules.contains(kind))
657 .enumerate()
658 {
659 info!(
660 target: LOG_NET_PEER_DKG,
661 "Running config generation for module of kind {kind}..."
662 );
663
664 let cfg = module_init.distributed_gen(&handle, &args).await?;
665
666 module_cfgs.insert(module_id as ModuleInstanceId, cfg);
667 }
668
669 let cfg = ServerConfig::from(
670 params.clone(),
671 params.identity,
672 broadcast_public_keys,
673 broadcast_sk,
674 module_cfgs,
675 code_version_str,
676 );
677
678 let checksum = cfg.consensus.consensus_hash_sha256();
679
680 info!(
681 target: LOG_NET_PEER_DKG,
682 "Comparing consensus config checksum {checksum}..."
683 );
684
685 connections.send(Recipient::Everyone, P2PMessage::Checksum(checksum));
686
687 for peer in params
688 .peer_ids()
689 .into_iter()
690 .filter(|p| *p != params.identity)
691 {
692 let peer_message = connections
693 .receive_from_peer(peer)
694 .await
695 .context("Unexpected shutdown of p2p connections")?;
696
697 if peer_message != P2PMessage::Checksum(checksum) {
698 warn!(
699 target: LOG_NET_PEER_DKG,
700 expected = ?P2PMessage::Checksum(checksum),
701 received = ?peer_message,
702 config = ?cfg.consensus,
703 "Peer {peer} has sent invalid consensus config checksum message"
704 );
705
706 bail!("Peer {peer} has sent invalid consensus config checksum message");
707 }
708
709 info!(
710 target: LOG_NET_PEER_DKG,
711 "Peer {peer} has sent valid consensus config checksum message"
712 );
713 }
714
715 info!(
716 target: LOG_NET_PEER_DKG,
717 "Config generation has completed successfully!"
718 );
719
720 Ok(cfg)
721 }
722}
723
724impl ServerConfig {
725 pub fn tls_config(&self) -> TlsConfig {
726 TlsConfig {
727 private_key: Arc::new(
728 rustls::pki_types::PrivateKeyDer::try_from(
729 Vec::from_hex(self.private.tls_key.clone().unwrap()).unwrap(),
730 )
731 .expect("Failed to parse private key"),
732 ),
733 certificates: self
734 .consensus
735 .tls_certs
736 .iter()
737 .map(|(peer, cert)| {
738 (
739 *peer,
740 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
741 )
742 })
743 .collect(),
744 peer_names: self
745 .local
746 .p2p_endpoints
747 .iter()
748 .map(|(id, endpoint)| (*id, endpoint.name.clone()))
749 .collect(),
750 }
751 }
752}
753
754impl ConfigGenParams {
755 pub fn peer_ids(&self) -> Vec<PeerId> {
756 self.peers.keys().copied().collect()
757 }
758
759 pub fn tls_config(&self) -> TlsConfig {
760 TlsConfig {
761 private_key: self.tls_key.clone().unwrap(),
762 certificates: self
763 .tls_certs()
764 .iter()
765 .map(|(peer, cert)| {
766 (
767 *peer,
768 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
769 )
770 })
771 .collect(),
772 peer_names: self
773 .p2p_urls()
774 .into_iter()
775 .map(|(id, peer)| (id, peer.name))
776 .collect(),
777 }
778 }
779
780 pub fn tls_certs(&self) -> BTreeMap<PeerId, String> {
781 self.peers
782 .iter()
783 .filter_map(|(id, peer)| {
784 match peer.endpoints.clone() {
785 PeerEndpoints::Tcp { cert, .. } => Some(cert.encode_hex()),
786 PeerEndpoints::Iroh { .. } => None,
787 }
788 .map(|peer| (*id, peer))
789 })
790 .collect()
791 }
792
793 pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
794 self.peers
795 .iter()
796 .filter_map(|(id, peer)| {
797 match peer.endpoints.clone() {
798 PeerEndpoints::Tcp { p2p_url, .. } => Some(PeerUrl {
799 name: peer.name.clone(),
800 url: p2p_url.clone(),
801 }),
802 PeerEndpoints::Iroh { .. } => None,
803 }
804 .map(|peer| (*id, peer))
805 })
806 .collect()
807 }
808
809 pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
810 self.peers
811 .iter()
812 .filter_map(|(id, peer)| {
813 match peer.endpoints.clone() {
814 PeerEndpoints::Tcp { api_url, .. } => Some(PeerUrl {
815 name: peer.name.clone(),
816 url: api_url.clone(),
817 }),
818 PeerEndpoints::Iroh { .. } => None,
819 }
820 .map(|peer| (*id, peer))
821 })
822 .collect()
823 }
824
825 pub fn iroh_endpoints(&self) -> BTreeMap<PeerId, PeerIrohEndpoints> {
826 self.peers
827 .iter()
828 .filter_map(|(id, peer)| {
829 match peer.endpoints.clone() {
830 PeerEndpoints::Tcp { .. } => None,
831 PeerEndpoints::Iroh { api_pk, p2p_pk } => Some(PeerIrohEndpoints {
832 name: peer.name.clone(),
833 api_pk,
834 p2p_pk,
835 }),
836 }
837 .map(|peer| (*id, peer))
838 })
839 .collect()
840 }
841}