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