1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::net::SocketAddr;
3use std::time::Duration;
4
5use anyhow::{Context, bail, ensure, format_err};
6use bitcoin::hashes::sha256;
7use fedimint_core::config::ServerModuleConfigGenParamsRegistry;
8pub use fedimint_core::config::{
9 ClientConfig, FederationId, GlobalClientConfig, JsonWithKind, ModuleInitRegistry, P2PMessage,
10 PeerUrl, ServerModuleConfig, ServerModuleConsensusConfig, TypedServerModuleConfig,
11 serde_binary_human_readable,
12};
13use fedimint_core::core::{ModuleInstanceId, ModuleKind};
14use fedimint_core::encoding::Decodable;
15use fedimint_core::envs::is_running_in_test_env;
16use fedimint_core::invite_code::InviteCode;
17use fedimint_core::module::registry::ModuleDecoderRegistry;
18use fedimint_core::module::{
19 ApiAuth, ApiVersion, CORE_CONSENSUS_VERSION, CoreConsensusVersion, MultiApiVersion,
20 SupportedApiVersionsSummary, SupportedCoreApiVersions,
21};
22use fedimint_core::net::peers::{DynP2PConnections, Recipient};
23use fedimint_core::task::sleep;
24use fedimint_core::util::SafeUrl;
25use fedimint_core::{NumPeersExt, PeerId, base32, secp256k1, timing};
26use fedimint_logging::LOG_NET_PEER_DKG;
27use fedimint_server_core::config::PeerHandleOpsExt as _;
28use fedimint_server_core::{DynServerModuleInit, ServerModuleInitRegistry};
29use hex::{FromHex, ToHex};
30use peer_handle::PeerHandle;
31use rand::rngs::OsRng;
32use secp256k1::{PublicKey, Secp256k1, SecretKey};
33use serde::{Deserialize, Serialize};
34use tokio_rustls::rustls;
35use tracing::info;
36
37use crate::fedimint_core::encoding::Encodable;
38use crate::net::p2p::P2PStatusReceivers;
39use crate::net::p2p_connector::TlsConfig;
40
41pub mod dkg;
42pub mod io;
43pub mod peer_handle;
44pub mod setup;
45
46pub const DEFAULT_MAX_CLIENT_CONNECTIONS: u32 = 1000;
48
49const DEFAULT_BROADCAST_ROUND_DELAY_MS: u16 = 50;
51const DEFAULT_BROADCAST_ROUNDS_PER_SESSION: u16 = 3600;
52
53fn default_broadcast_rounds_per_session() -> u16 {
54 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
55}
56
57const DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS: u16 = 50;
59const DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION: u16 = 200;
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ServerConfig {
64 pub consensus: ServerConfigConsensus,
66 pub local: ServerConfigLocal,
68 pub private: ServerConfigPrivate,
71}
72
73impl ServerConfig {
74 pub fn iter_module_instances(
75 &self,
76 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
77 self.consensus.iter_module_instances()
78 }
79
80 pub(crate) fn supported_api_versions_summary(
81 modules: &BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
82 module_inits: &ServerModuleInitRegistry,
83 ) -> SupportedApiVersionsSummary {
84 SupportedApiVersionsSummary {
85 core: Self::supported_api_versions(),
86 modules: modules
87 .iter()
88 .map(|(&id, config)| {
89 (
90 id,
91 module_inits
92 .get(&config.kind)
93 .expect("missing module kind gen")
94 .supported_api_versions(),
95 )
96 })
97 .collect(),
98 }
99 }
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ServerConfigPrivate {
104 pub api_auth: ApiAuth,
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,
124 pub version: CoreConsensusVersion,
126 pub broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
128 #[serde(default = "default_broadcast_rounds_per_session")]
130 pub broadcast_rounds_per_session: u16,
131 pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
133 #[serde(default)]
135 pub iroh_endpoints: BTreeMap<PeerId, PeerIrohEndpoints>,
136 pub tls_certs: BTreeMap<PeerId, String>,
138 pub modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
140 pub meta: BTreeMap<String, String>,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
145pub struct PeerIrohEndpoints {
146 pub name: String,
148 pub api_pk: iroh::PublicKey,
150 pub p2p_pk: iroh::PublicKey,
152}
153
154pub fn legacy_consensus_config_hash(cfg: &ServerConfigConsensus) -> sha256::Hash {
155 #[derive(Encodable)]
156 struct LegacyServerConfigConsensusHashMap {
157 code_version: String,
158 version: CoreConsensusVersion,
159 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
160 broadcast_rounds_per_session: u16,
161 api_endpoints: BTreeMap<PeerId, PeerUrl>,
162 tls_certs: BTreeMap<PeerId, String>,
163 modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
164 meta: BTreeMap<String, String>,
165 }
166
167 LegacyServerConfigConsensusHashMap {
168 code_version: cfg.code_version.clone(),
169 version: cfg.version,
170 broadcast_public_keys: cfg.broadcast_public_keys.clone(),
171 broadcast_rounds_per_session: cfg.broadcast_rounds_per_session,
172 api_endpoints: cfg.api_endpoints.clone(),
173 tls_certs: cfg.tls_certs.clone(),
174 modules: cfg.modules.clone(),
175 meta: cfg.meta.clone(),
176 }
177 .consensus_hash_sha256()
178}
179
180#[derive(Clone, Debug, Serialize, Deserialize, Default)]
182pub enum NetworkingStack {
183 #[default]
184 Tcp,
185 Iroh,
186}
187
188#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct ServerConfigLocal {
192 pub p2p_endpoints: BTreeMap<PeerId, PeerUrl>,
194 pub identity: PeerId,
196 pub max_connections: u32,
198 pub broadcast_round_delay_ms: u16,
203 pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
205}
206
207#[derive(Debug, Clone)]
209pub struct ConfigGenSettings {
210 pub p2p_bind: SocketAddr,
212 pub bind_api_ws: SocketAddr,
214 pub bind_api_iroh: SocketAddr,
216 pub ui_bind: SocketAddr,
218 pub p2p_url: Option<SafeUrl>,
220 pub api_url: Option<SafeUrl>,
222 pub networking: NetworkingStack,
226 pub modules: ServerModuleConfigGenParamsRegistry,
228 pub registry: ServerModuleInitRegistry,
230}
231
232#[derive(Debug, Clone)]
233pub struct ConfigGenParams {
238 pub identity: PeerId,
240 pub tls_key: Option<rustls::PrivateKey>,
242 pub iroh_api_sk: Option<iroh::SecretKey>,
244 pub iroh_p2p_sk: Option<iroh::SecretKey>,
246 pub api_auth: ApiAuth,
248 pub peers: BTreeMap<PeerId, PeerSetupCode>,
250 pub meta: BTreeMap<String, String>,
252}
253
254#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Encodable, Decodable)]
255pub struct PeerSetupCode {
257 pub name: String,
259 pub endpoints: PeerEndpoints,
261 pub federation_name: Option<String>,
263}
264
265impl PeerSetupCode {
266 pub fn encode_base32(&self) -> String {
267 format!(
268 "fedimint{}",
269 base32::encode(&self.consensus_encode_to_vec())
270 )
271 }
272
273 pub fn decode_base32(s: &str) -> anyhow::Result<Self> {
274 ensure!(s.starts_with("fedimint"), "Invalid Prefix");
275
276 let params = Self::consensus_decode_whole(
277 &base32::decode(&s[8..])?,
278 &ModuleDecoderRegistry::default(),
279 )?;
280
281 Ok(params)
282 }
283}
284
285#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Encodable, Decodable)]
286pub enum PeerEndpoints {
287 Tcp {
288 api_url: SafeUrl,
290 p2p_url: SafeUrl,
292 cert: Vec<u8>,
294 },
295 Iroh {
296 api_pk: iroh::PublicKey,
298 p2p_pk: iroh::PublicKey,
300 },
301}
302
303impl ServerConfigConsensus {
304 pub fn api_endpoints(&self) -> BTreeMap<PeerId, PeerUrl> {
305 if self.iroh_endpoints.is_empty() {
306 self.api_endpoints.clone()
307 } else {
308 self.iroh_endpoints
309 .iter()
310 .map(|(peer, endpoints)| {
311 let url = PeerUrl {
312 name: endpoints.name.clone(),
313 url: SafeUrl::parse(&format!("iroh://{}", endpoints.api_pk))
314 .expect("Failed to parse iroh url"),
315 };
316
317 (*peer, url)
318 })
319 .collect()
320 }
321 }
322
323 pub fn iter_module_instances(
324 &self,
325 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
326 self.modules.iter().map(|(k, v)| (*k, &v.kind))
327 }
328
329 pub fn to_client_config(
330 &self,
331 module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
332 ) -> Result<ClientConfig, anyhow::Error> {
333 let client = ClientConfig {
334 global: GlobalClientConfig {
335 api_endpoints: self.api_endpoints(),
336 broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
337 consensus_version: self.version,
338 meta: self.meta.clone(),
339 },
340 modules: self
341 .modules
342 .iter()
343 .map(|(k, v)| {
344 let r#gen = module_config_gens
345 .get(&v.kind)
346 .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
347 Ok((*k, r#gen.get_client_config(*k, v)?))
348 })
349 .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
350 };
351 Ok(client)
352 }
353}
354
355impl ServerConfig {
356 pub fn supported_api_versions() -> SupportedCoreApiVersions {
358 SupportedCoreApiVersions {
359 core_consensus: CORE_CONSENSUS_VERSION,
360 api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 5 }])
361 .expect("not version conflicts"),
362 }
363 }
364 pub fn from(
367 params: ConfigGenParams,
368 identity: PeerId,
369 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
370 broadcast_secret_key: SecretKey,
371 modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
372 code_version: String,
373 ) -> Self {
374 let consensus = ServerConfigConsensus {
375 code_version,
376 version: CORE_CONSENSUS_VERSION,
377 broadcast_public_keys,
378 broadcast_rounds_per_session: if is_running_in_test_env() {
379 DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
380 } else {
381 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
382 },
383 api_endpoints: params.api_urls(),
384 iroh_endpoints: params.iroh_endpoints(),
385 tls_certs: params.tls_certs(),
386 modules: modules
387 .iter()
388 .map(|(peer, cfg)| (*peer, cfg.consensus.clone()))
389 .collect(),
390 meta: params.meta.clone(),
391 };
392
393 let local = ServerConfigLocal {
394 p2p_endpoints: params.p2p_urls(),
395 identity,
396 max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
397 broadcast_round_delay_ms: if is_running_in_test_env() {
398 DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
399 } else {
400 DEFAULT_BROADCAST_ROUND_DELAY_MS
401 },
402 modules: modules
403 .iter()
404 .map(|(peer, cfg)| (*peer, cfg.local.clone()))
405 .collect(),
406 };
407
408 let private = ServerConfigPrivate {
409 api_auth: params.api_auth.clone(),
410 tls_key: params.tls_key.map(|key| key.0.encode_hex()),
411 iroh_api_sk: params.iroh_api_sk,
412 iroh_p2p_sk: params.iroh_p2p_sk,
413 broadcast_secret_key,
414 modules: modules
415 .iter()
416 .map(|(peer, cfg)| (*peer, cfg.private.clone()))
417 .collect(),
418 };
419
420 Self {
421 consensus,
422 local,
423 private,
424 }
425 }
426
427 pub fn get_invite_code(&self, api_secret: Option<String>) -> InviteCode {
428 InviteCode::new(
429 self.consensus.api_endpoints()[&self.local.identity]
430 .url
431 .clone(),
432 self.local.identity,
433 self.calculate_federation_id(),
434 api_secret,
435 )
436 }
437
438 pub fn calculate_federation_id(&self) -> FederationId {
439 FederationId(self.consensus.api_endpoints().consensus_hash())
440 }
441
442 pub fn get_module_config_typed<T: TypedServerModuleConfig>(
444 &self,
445 id: ModuleInstanceId,
446 ) -> anyhow::Result<T> {
447 let local = Self::get_module_cfg_by_instance_id(&self.local.modules, id)?;
448 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
449 let consensus = self
450 .consensus
451 .modules
452 .get(&id)
453 .ok_or_else(|| format_err!("Module {id} not found"))?
454 .clone();
455 let module = ServerModuleConfig::from(local, private, consensus);
456
457 module.to_typed()
458 }
459 pub fn get_module_id_by_kind(
460 &self,
461 kind: impl Into<ModuleKind>,
462 ) -> anyhow::Result<ModuleInstanceId> {
463 let kind = kind.into();
464 Ok(*self
465 .consensus
466 .modules
467 .iter()
468 .find(|(_, v)| v.kind == kind)
469 .ok_or_else(|| format_err!("Module {kind} not found"))?
470 .0)
471 }
472
473 pub fn get_module_config(&self, id: ModuleInstanceId) -> anyhow::Result<ServerModuleConfig> {
475 let local = Self::get_module_cfg_by_instance_id(&self.local.modules, id)?;
476 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
477 let consensus = self
478 .consensus
479 .modules
480 .get(&id)
481 .ok_or_else(|| format_err!("Module {id} not found"))?
482 .clone();
483 Ok(ServerModuleConfig::from(local, private, consensus))
484 }
485
486 fn get_module_cfg_by_instance_id(
487 json: &BTreeMap<ModuleInstanceId, JsonWithKind>,
488 id: ModuleInstanceId,
489 ) -> anyhow::Result<JsonWithKind> {
490 Ok(json
491 .get(&id)
492 .ok_or_else(|| format_err!("Module {id} not found"))
493 .cloned()?
494 .with_fixed_empty_value())
495 }
496
497 pub fn validate_config(
498 &self,
499 identity: &PeerId,
500 module_config_gens: &ServerModuleInitRegistry,
501 ) -> anyhow::Result<()> {
502 let endpoints = self.consensus.api_endpoints().clone();
503 let consensus = self.consensus.clone();
504 let private = self.private.clone();
505
506 let my_public_key = private.broadcast_secret_key.public_key(&Secp256k1::new());
507
508 if Some(&my_public_key) != consensus.broadcast_public_keys.get(identity) {
509 bail!("Broadcast secret key doesn't match corresponding public key");
510 }
511 if endpoints.keys().max().copied().map(PeerId::to_usize) != Some(endpoints.len() - 1) {
512 bail!("Peer ids are not indexed from 0");
513 }
514 if endpoints.keys().min().copied() != Some(PeerId::from(0)) {
515 bail!("Peer ids are not indexed from 0");
516 }
517
518 for (module_id, module_kind) in &self
519 .consensus
520 .modules
521 .iter()
522 .map(|(id, config)| Ok((*id, config.kind.clone())))
523 .collect::<anyhow::Result<BTreeSet<_>>>()?
524 {
525 module_config_gens
526 .get(module_kind)
527 .ok_or_else(|| format_err!("module config gen not found {module_kind}"))?
528 .validate_config(identity, self.get_module_config(*module_id)?)?;
529 }
530
531 Ok(())
532 }
533
534 pub fn trusted_dealer_gen(
535 modules: ServerModuleConfigGenParamsRegistry,
536 params: &HashMap<PeerId, ConfigGenParams>,
537 registry: &ServerModuleInitRegistry,
538 code_version_str: &str,
539 ) -> BTreeMap<PeerId, Self> {
540 let peer0 = ¶ms[&PeerId::from(0)];
541
542 let mut broadcast_pks = BTreeMap::new();
543 let mut broadcast_sks = BTreeMap::new();
544 for peer_id in peer0.peer_ids() {
545 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
546 broadcast_pks.insert(peer_id, broadcast_pk);
547 broadcast_sks.insert(peer_id, broadcast_sk);
548 }
549
550 let module_configs: BTreeMap<_, _> = modules
551 .iter_modules()
552 .map(|(module_id, kind, module_params)| {
553 (
554 module_id,
555 registry
556 .get(kind)
557 .expect("Module not registered")
558 .trusted_dealer_gen(&peer0.peer_ids(), module_params),
559 )
560 })
561 .collect();
562
563 let server_config: BTreeMap<_, _> = peer0
564 .peer_ids()
565 .iter()
566 .map(|&id| {
567 let config = ServerConfig::from(
568 params[&id].clone(),
569 id,
570 broadcast_pks.clone(),
571 *broadcast_sks.get(&id).expect("We created this entry"),
572 module_configs
573 .iter()
574 .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
575 .collect(),
576 code_version_str.to_string(),
577 );
578 (id, config)
579 })
580 .collect();
581
582 server_config
583 }
584
585 pub async fn distributed_gen(
587 modules: ServerModuleConfigGenParamsRegistry,
588 params: &ConfigGenParams,
589 registry: ServerModuleInitRegistry,
590 code_version_str: String,
591 connections: DynP2PConnections<P2PMessage>,
592 p2p_status_receivers: P2PStatusReceivers,
593 ) -> anyhow::Result<Self> {
594 let _timing = timing::TimeReporter::new("distributed-gen").info();
595
596 if params.peer_ids().len() == 1 {
598 let server = Self::trusted_dealer_gen(
599 modules,
600 &HashMap::from([(params.identity, params.clone())]),
601 ®istry,
602 &code_version_str,
603 );
604 return Ok(server[¶ms.identity].clone());
605 }
606
607 info!(
608 target: LOG_NET_PEER_DKG,
609 "Waiting for all p2p connections to open..."
610 );
611
612 while p2p_status_receivers.values().any(|r| r.borrow().is_none()) {
613 let peers = p2p_status_receivers
614 .iter()
615 .filter_map(|entry| entry.1.borrow().map(|_| *entry.0))
616 .collect::<Vec<PeerId>>();
617
618 info!(
619 target: LOG_NET_PEER_DKG,
620 "Connected to peers: {peers:?}..."
621 );
622
623 sleep(Duration::from_secs(1)).await;
624 }
625
626 let checksum = params.peers.consensus_hash_sha256();
627
628 info!(
629 target: LOG_NET_PEER_DKG,
630 "Comparing connection codes checksum {checksum}..."
631 );
632
633 connections
634 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
635 .await;
636
637 for peer in params
638 .peer_ids()
639 .into_iter()
640 .filter(|p| *p != params.identity)
641 {
642 ensure!(
643 connections
644 .receive_from_peer(peer)
645 .await
646 .context("Unexpected shutdown of p2p connections")?
647 == P2PMessage::Checksum(checksum),
648 "Peer {peer} has not send the correct checksum message"
649 );
650 }
651
652 info!(
653 target: LOG_NET_PEER_DKG,
654 "Running config generation..."
655 );
656
657 let handle = PeerHandle::new(
658 params.peer_ids().to_num_peers(),
659 params.identity,
660 &connections,
661 );
662
663 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
664
665 let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
666
667 let mut module_cfgs = BTreeMap::new();
668
669 for (module_id, kind, module_params) in modules.iter_modules() {
670 info!(
671 target: LOG_NET_PEER_DKG,
672 "Running config generation for module of kind {kind}..."
673 );
674
675 let cfg = registry
676 .get(kind)
677 .context("Module of kind {kind} not found")?
678 .distributed_gen(&handle, module_params)
679 .await?;
680
681 module_cfgs.insert(module_id, cfg);
682 }
683
684 let cfg = ServerConfig::from(
685 params.clone(),
686 params.identity,
687 broadcast_public_keys,
688 broadcast_sk,
689 module_cfgs,
690 code_version_str,
691 );
692
693 let checksum = cfg.consensus.consensus_hash_sha256();
694
695 info!(
696 target: LOG_NET_PEER_DKG,
697 "Comparing consensus config checksum {checksum}..."
698 );
699
700 connections
701 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
702 .await;
703
704 for peer in params
705 .peer_ids()
706 .into_iter()
707 .filter(|p| *p != params.identity)
708 {
709 ensure!(
710 connections
711 .receive_from_peer(peer)
712 .await
713 .context("Unexpected shutdown of p2p connections")?
714 == P2PMessage::Checksum(checksum),
715 "Peer {peer} has not send the correct checksum message"
716 );
717 }
718
719 info!(
720 target: LOG_NET_PEER_DKG,
721 "Config generation has completed successfully!"
722 );
723
724 Ok(cfg)
725 }
726}
727
728impl ServerConfig {
729 pub fn tls_config(&self) -> TlsConfig {
730 TlsConfig {
731 private_key: rustls::PrivateKey(
732 Vec::from_hex(self.private.tls_key.clone().unwrap()).unwrap(),
733 ),
734 certificates: self
735 .consensus
736 .tls_certs
737 .iter()
738 .map(|(peer, cert)| (*peer, rustls::Certificate(Vec::from_hex(cert).unwrap())))
739 .collect(),
740 peer_names: self
741 .local
742 .p2p_endpoints
743 .iter()
744 .map(|(id, endpoint)| (*id, endpoint.name.to_string()))
745 .collect(),
746 }
747 }
748}
749
750impl ConfigGenParams {
751 pub fn peer_ids(&self) -> Vec<PeerId> {
752 self.peers.keys().copied().collect()
753 }
754
755 pub fn tls_config(&self) -> TlsConfig {
756 TlsConfig {
757 private_key: self.tls_key.clone().unwrap(),
758 certificates: self
759 .tls_certs()
760 .iter()
761 .map(|(peer, cert)| (*peer, rustls::Certificate(Vec::from_hex(cert).unwrap())))
762 .collect(),
763 peer_names: self
764 .p2p_urls()
765 .into_iter()
766 .map(|(id, peer)| (id, peer.name))
767 .collect(),
768 }
769 }
770
771 pub fn tls_certs(&self) -> BTreeMap<PeerId, String> {
772 self.peers
773 .iter()
774 .filter_map(|(id, peer)| {
775 match peer.endpoints.clone() {
776 PeerEndpoints::Tcp { cert, .. } => Some(cert.encode_hex()),
777 PeerEndpoints::Iroh { .. } => None,
778 }
779 .map(|peer| (*id, peer))
780 })
781 .collect()
782 }
783
784 pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
785 self.peers
786 .iter()
787 .filter_map(|(id, peer)| {
788 match peer.endpoints.clone() {
789 PeerEndpoints::Tcp { p2p_url, .. } => Some(PeerUrl {
790 name: peer.name.clone(),
791 url: p2p_url.clone(),
792 }),
793 PeerEndpoints::Iroh { .. } => None,
794 }
795 .map(|peer| (*id, peer))
796 })
797 .collect()
798 }
799
800 pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
801 self.peers
802 .iter()
803 .filter_map(|(id, peer)| {
804 match peer.endpoints.clone() {
805 PeerEndpoints::Tcp { api_url, .. } => Some(PeerUrl {
806 name: peer.name.clone(),
807 url: api_url.clone(),
808 }),
809 PeerEndpoints::Iroh { .. } => None,
810 }
811 .map(|peer| (*id, peer))
812 })
813 .collect()
814 }
815
816 pub fn iroh_endpoints(&self) -> BTreeMap<PeerId, PeerIrohEndpoints> {
817 self.peers
818 .iter()
819 .filter_map(|(id, peer)| {
820 match peer.endpoints.clone() {
821 PeerEndpoints::Tcp { .. } => None,
822 PeerEndpoints::Iroh { api_pk, p2p_pk } => Some(PeerIrohEndpoints {
823 name: peer.name.clone(),
824 api_pk,
825 p2p_pk,
826 }),
827 }
828 .map(|peer| (*id, peer))
829 })
830 .collect()
831 }
832}