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