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;
8use fedimint_core::config::ServerModuleConfigGenParamsRegistry;
9pub use fedimint_core::config::{
10 ClientConfig, FederationId, GlobalClientConfig, JsonWithKind, ModuleInitRegistry, P2PMessage,
11 PeerUrl, ServerModuleConfig, ServerModuleConsensusConfig, TypedServerModuleConfig,
12};
13use fedimint_core::core::{ModuleInstanceId, ModuleKind};
14use fedimint_core::envs::is_running_in_test_env;
15use fedimint_core::invite_code::InviteCode;
16use fedimint_core::module::{
17 ApiAuth, ApiVersion, CORE_CONSENSUS_VERSION, CoreConsensusVersion, MultiApiVersion,
18 SupportedApiVersionsSummary, SupportedCoreApiVersions,
19};
20use fedimint_core::net::peers::{DynP2PConnections, Recipient};
21use fedimint_core::setup_code::{PeerEndpoints, PeerSetupCode};
22use fedimint_core::task::sleep;
23use fedimint_core::util::SafeUrl;
24use fedimint_core::{NumPeersExt, PeerId, secp256k1, timing};
25use fedimint_logging::LOG_NET_PEER_DKG;
26use fedimint_server_core::config::PeerHandleOpsExt as _;
27use fedimint_server_core::{DynServerModuleInit, ServerModuleInitRegistry};
28use hex::{FromHex, ToHex};
29use peer_handle::PeerHandle;
30use rand::rngs::OsRng;
31use secp256k1::{PublicKey, Secp256k1, SecretKey};
32use serde::{Deserialize, Serialize};
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 io;
42pub mod peer_handle;
43pub mod setup;
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(Debug, Clone, Serialize, Deserialize)]
182pub struct ServerConfigLocal {
183 pub p2p_endpoints: BTreeMap<PeerId, PeerUrl>,
185 pub identity: PeerId,
187 pub max_connections: u32,
189 pub broadcast_round_delay_ms: u16,
194}
195
196#[derive(Debug, Clone)]
198pub struct ConfigGenSettings {
199 pub p2p_bind: SocketAddr,
201 pub api_bind: SocketAddr,
203 pub ui_bind: SocketAddr,
205 pub p2p_url: Option<SafeUrl>,
207 pub api_url: Option<SafeUrl>,
209 pub enable_iroh: bool,
211 pub iroh_dns: Option<SafeUrl>,
213 pub iroh_relays: Vec<SafeUrl>,
215 pub modules: ServerModuleConfigGenParamsRegistry,
217 pub registry: ServerModuleInitRegistry,
219}
220
221#[derive(Debug, Clone)]
222pub struct ConfigGenParams {
227 pub identity: PeerId,
229 pub tls_key: Option<Arc<rustls::pki_types::PrivateKeyDer<'static>>>,
231 pub iroh_api_sk: Option<iroh::SecretKey>,
233 pub iroh_p2p_sk: Option<iroh::SecretKey>,
235 pub api_auth: ApiAuth,
237 pub peers: BTreeMap<PeerId, PeerSetupCode>,
239 pub meta: BTreeMap<String, String>,
241 pub disable_base_fees: bool,
243}
244
245impl ServerConfigConsensus {
246 pub fn api_endpoints(&self) -> BTreeMap<PeerId, PeerUrl> {
247 if self.iroh_endpoints.is_empty() {
248 self.api_endpoints.clone()
249 } else {
250 self.iroh_endpoints
251 .iter()
252 .map(|(peer, endpoints)| {
253 let url = PeerUrl {
254 name: endpoints.name.clone(),
255 url: SafeUrl::parse(&format!("iroh://{}", endpoints.api_pk))
256 .expect("Failed to parse iroh url"),
257 };
258
259 (*peer, url)
260 })
261 .collect()
262 }
263 }
264
265 pub fn iter_module_instances(
266 &self,
267 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
268 self.modules.iter().map(|(k, v)| (*k, &v.kind))
269 }
270
271 pub fn to_client_config(
272 &self,
273 module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
274 ) -> Result<ClientConfig, anyhow::Error> {
275 let client = ClientConfig {
276 global: GlobalClientConfig {
277 api_endpoints: self.api_endpoints(),
278 broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
279 consensus_version: self.version,
280 meta: self.meta.clone(),
281 },
282 modules: self
283 .modules
284 .iter()
285 .map(|(k, v)| {
286 let r#gen = module_config_gens
287 .get(&v.kind)
288 .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
289 Ok((*k, r#gen.get_client_config(*k, v)?))
290 })
291 .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
292 };
293 Ok(client)
294 }
295}
296
297impl ServerConfig {
298 pub fn supported_api_versions() -> SupportedCoreApiVersions {
300 SupportedCoreApiVersions {
301 core_consensus: CORE_CONSENSUS_VERSION,
302 api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 7 }])
303 .expect("not version conflicts"),
304 }
305 }
306 pub fn from(
309 params: ConfigGenParams,
310 identity: PeerId,
311 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
312 broadcast_secret_key: SecretKey,
313 modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
314 code_version: String,
315 ) -> Self {
316 let consensus = ServerConfigConsensus {
317 code_version,
318 version: CORE_CONSENSUS_VERSION,
319 broadcast_public_keys,
320 broadcast_rounds_per_session: if is_running_in_test_env() {
321 DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
322 } else {
323 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
324 },
325 api_endpoints: params.api_urls(),
326 iroh_endpoints: params.iroh_endpoints(),
327 tls_certs: params.tls_certs(),
328 modules: modules
329 .iter()
330 .map(|(peer, cfg)| (*peer, cfg.consensus.clone()))
331 .collect(),
332 meta: params.meta.clone(),
333 };
334
335 let local = ServerConfigLocal {
336 p2p_endpoints: params.p2p_urls(),
337 identity,
338 max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
339 broadcast_round_delay_ms: if is_running_in_test_env() {
340 DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
341 } else {
342 DEFAULT_BROADCAST_ROUND_DELAY_MS
343 },
344 };
345
346 let private = ServerConfigPrivate {
347 api_auth: params.api_auth.clone(),
348 tls_key: params
349 .tls_key
350 .map(|key| key.secret_der().to_vec().encode_hex()),
351 iroh_api_sk: params.iroh_api_sk,
352 iroh_p2p_sk: params.iroh_p2p_sk,
353 broadcast_secret_key,
354 modules: modules
355 .iter()
356 .map(|(peer, cfg)| (*peer, cfg.private.clone()))
357 .collect(),
358 };
359
360 Self {
361 consensus,
362 local,
363 private,
364 }
365 }
366
367 pub fn get_invite_code(&self, api_secret: Option<String>) -> InviteCode {
368 InviteCode::new(
369 self.consensus.api_endpoints()[&self.local.identity]
370 .url
371 .clone(),
372 self.local.identity,
373 self.calculate_federation_id(),
374 api_secret,
375 )
376 }
377
378 pub fn calculate_federation_id(&self) -> FederationId {
379 FederationId(self.consensus.api_endpoints().consensus_hash())
380 }
381
382 pub fn get_module_config_typed<T: TypedServerModuleConfig>(
384 &self,
385 id: ModuleInstanceId,
386 ) -> anyhow::Result<T> {
387 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
388 let consensus = self
389 .consensus
390 .modules
391 .get(&id)
392 .ok_or_else(|| format_err!("Typed module {id} not found"))?
393 .clone();
394 let module = ServerModuleConfig::from(private, consensus);
395
396 module.to_typed()
397 }
398 pub fn get_module_id_by_kind(
399 &self,
400 kind: impl Into<ModuleKind>,
401 ) -> anyhow::Result<ModuleInstanceId> {
402 let kind = kind.into();
403 Ok(*self
404 .consensus
405 .modules
406 .iter()
407 .find(|(_, v)| v.kind == kind)
408 .ok_or_else(|| format_err!("Module {kind} not found"))?
409 .0)
410 }
411
412 pub fn get_module_config(&self, id: ModuleInstanceId) -> anyhow::Result<ServerModuleConfig> {
414 let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
415 let consensus = self
416 .consensus
417 .modules
418 .get(&id)
419 .ok_or_else(|| format_err!("Module config {id} not found"))?
420 .clone();
421 Ok(ServerModuleConfig::from(private, consensus))
422 }
423
424 fn get_module_cfg_by_instance_id(
425 json: &BTreeMap<ModuleInstanceId, JsonWithKind>,
426 id: ModuleInstanceId,
427 ) -> anyhow::Result<JsonWithKind> {
428 Ok(json
429 .get(&id)
430 .ok_or_else(|| format_err!("Module cfg {id} not found"))
431 .cloned()?
432 .with_fixed_empty_value())
433 }
434
435 pub fn validate_config(
436 &self,
437 identity: &PeerId,
438 module_config_gens: &ServerModuleInitRegistry,
439 ) -> anyhow::Result<()> {
440 let endpoints = self.consensus.api_endpoints().clone();
441 let consensus = self.consensus.clone();
442 let private = self.private.clone();
443
444 let my_public_key = private.broadcast_secret_key.public_key(&Secp256k1::new());
445
446 if Some(&my_public_key) != consensus.broadcast_public_keys.get(identity) {
447 bail!("Broadcast secret key doesn't match corresponding public key");
448 }
449 if endpoints.keys().max().copied().map(PeerId::to_usize) != Some(endpoints.len() - 1) {
450 bail!("Peer ids are not indexed from 0");
451 }
452 if endpoints.keys().min().copied() != Some(PeerId::from(0)) {
453 bail!("Peer ids are not indexed from 0");
454 }
455
456 for (module_id, module_kind) in &self
457 .consensus
458 .modules
459 .iter()
460 .map(|(id, config)| Ok((*id, config.kind.clone())))
461 .collect::<anyhow::Result<BTreeSet<_>>>()?
462 {
463 module_config_gens
464 .get(module_kind)
465 .ok_or_else(|| format_err!("module config gen not found {module_kind}"))?
466 .validate_config(identity, self.get_module_config(*module_id)?)?;
467 }
468
469 Ok(())
470 }
471
472 pub fn trusted_dealer_gen(
473 modules: ServerModuleConfigGenParamsRegistry,
474 params: &HashMap<PeerId, ConfigGenParams>,
475 registry: &ServerModuleInitRegistry,
476 code_version_str: &str,
477 ) -> BTreeMap<PeerId, Self> {
478 let peer0 = ¶ms[&PeerId::from(0)];
479
480 let mut broadcast_pks = BTreeMap::new();
481 let mut broadcast_sks = BTreeMap::new();
482 for peer_id in peer0.peer_ids() {
483 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
484 broadcast_pks.insert(peer_id, broadcast_pk);
485 broadcast_sks.insert(peer_id, broadcast_sk);
486 }
487
488 let module_configs: BTreeMap<_, _> = modules
489 .iter_modules()
490 .map(|(module_id, kind, module_params)| {
491 (
492 module_id,
493 registry
494 .get(kind)
495 .expect("Module not registered")
496 .trusted_dealer_gen(
497 &peer0.peer_ids(),
498 module_params,
499 params[&PeerId::from(0)].disable_base_fees,
500 ),
501 )
502 })
503 .collect();
504
505 let server_config: BTreeMap<_, _> = peer0
506 .peer_ids()
507 .iter()
508 .map(|&id| {
509 let config = ServerConfig::from(
510 params[&id].clone(),
511 id,
512 broadcast_pks.clone(),
513 *broadcast_sks.get(&id).expect("We created this entry"),
514 module_configs
515 .iter()
516 .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
517 .collect(),
518 code_version_str.to_string(),
519 );
520 (id, config)
521 })
522 .collect();
523
524 server_config
525 }
526
527 pub async fn distributed_gen(
529 modules: ServerModuleConfigGenParamsRegistry,
530 params: &ConfigGenParams,
531 registry: ServerModuleInitRegistry,
532 code_version_str: String,
533 connections: DynP2PConnections<P2PMessage>,
534 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 modules,
542 &HashMap::from([(params.identity, params.clone())]),
543 ®istry,
544 &code_version_str,
545 );
546
547 return Ok(server[¶ms.identity].clone());
548 }
549
550 info!(
551 target: LOG_NET_PEER_DKG,
552 "Waiting for all p2p connections to open..."
553 );
554
555 while p2p_status_receivers.values().any(|r| r.borrow().is_none()) {
556 let connected_peers = p2p_status_receivers
557 .iter()
558 .filter_map(|entry| entry.1.borrow().is_some().then_some(*entry.0))
559 .collect::<Vec<PeerId>>();
560
561 let disconnected_peers = p2p_status_receivers
562 .iter()
563 .filter_map(|entry| entry.1.borrow().is_none().then_some(*entry.0))
564 .collect::<Vec<PeerId>>();
565
566 info!(
567 target: LOG_NET_PEER_DKG,
568 connected_peers = ?connected_peers,
569 disconnected_peers = ?disconnected_peers,
570 "Waiting for all p2p connections to open..."
571 );
572
573 sleep(Duration::from_secs(1)).await;
574 }
575
576 let checksum = params.peers.consensus_hash_sha256();
577
578 info!(
579 target: LOG_NET_PEER_DKG,
580 "Comparing connection codes checksum {checksum}..."
581 );
582
583 connections
584 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
585 .await;
586
587 for peer in params
588 .peer_ids()
589 .into_iter()
590 .filter(|p| *p != params.identity)
591 {
592 let peer_message = connections
593 .receive_from_peer(peer)
594 .await
595 .context("Unexpected shutdown of p2p connections")?;
596
597 if peer_message != P2PMessage::Checksum(checksum) {
598 error!(
599 target: LOG_NET_PEER_DKG,
600 expected = ?P2PMessage::Checksum(checksum),
601 received = ?peer_message,
602 "Peer {peer} has sent invalid connection code checksum message"
603 );
604
605 bail!("Peer {peer} has sent invalid connection code checksum message");
606 }
607
608 info!(
609 target: LOG_NET_PEER_DKG,
610 "Peer {peer} has sent valid connection code checksum message"
611 );
612 }
613
614 info!(
615 target: LOG_NET_PEER_DKG,
616 "Running config generation..."
617 );
618
619 let handle = PeerHandle::new(
620 params.peer_ids().to_num_peers(),
621 params.identity,
622 &connections,
623 );
624
625 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
626
627 let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
628
629 let mut module_cfgs = BTreeMap::new();
630
631 for (module_id, kind, module_params) in modules.iter_modules() {
632 info!(
633 target: LOG_NET_PEER_DKG,
634 "Running config generation for module of kind {kind}..."
635 );
636
637 let cfg = registry
638 .get(kind)
639 .with_context(|| format!("Module of kind {kind} not found"))?
640 .distributed_gen(&handle, module_params, params.disable_base_fees)
641 .await?;
642
643 module_cfgs.insert(module_id, cfg);
644 }
645
646 let cfg = ServerConfig::from(
647 params.clone(),
648 params.identity,
649 broadcast_public_keys,
650 broadcast_sk,
651 module_cfgs,
652 code_version_str,
653 );
654
655 let checksum = cfg.consensus.consensus_hash_sha256();
656
657 info!(
658 target: LOG_NET_PEER_DKG,
659 "Comparing consensus config checksum {checksum}..."
660 );
661
662 connections
663 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
664 .await;
665
666 for peer in params
667 .peer_ids()
668 .into_iter()
669 .filter(|p| *p != params.identity)
670 {
671 let peer_message = connections
672 .receive_from_peer(peer)
673 .await
674 .context("Unexpected shutdown of p2p connections")?;
675
676 if peer_message != P2PMessage::Checksum(checksum) {
677 warn!(
678 target: LOG_NET_PEER_DKG,
679 expected = ?P2PMessage::Checksum(checksum),
680 received = ?peer_message,
681 config = ?cfg.consensus,
682 "Peer {peer} has sent invalid consensus config checksum message"
683 );
684
685 bail!("Peer {peer} has sent invalid consensus config checksum message");
686 }
687
688 info!(
689 target: LOG_NET_PEER_DKG,
690 "Peer {peer} has sent valid consensus config checksum message"
691 );
692 }
693
694 info!(
695 target: LOG_NET_PEER_DKG,
696 "Config generation has completed successfully!"
697 );
698
699 Ok(cfg)
700 }
701}
702
703impl ServerConfig {
704 pub fn tls_config(&self) -> TlsConfig {
705 TlsConfig {
706 private_key: Arc::new(
707 rustls::pki_types::PrivateKeyDer::try_from(
708 Vec::from_hex(self.private.tls_key.clone().unwrap()).unwrap(),
709 )
710 .expect("Failed to parse private key"),
711 ),
712 certificates: self
713 .consensus
714 .tls_certs
715 .iter()
716 .map(|(peer, cert)| {
717 (
718 *peer,
719 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
720 )
721 })
722 .collect(),
723 peer_names: self
724 .local
725 .p2p_endpoints
726 .iter()
727 .map(|(id, endpoint)| (*id, endpoint.name.to_string()))
728 .collect(),
729 }
730 }
731}
732
733impl ConfigGenParams {
734 pub fn peer_ids(&self) -> Vec<PeerId> {
735 self.peers.keys().copied().collect()
736 }
737
738 pub fn tls_config(&self) -> TlsConfig {
739 TlsConfig {
740 private_key: self.tls_key.clone().unwrap(),
741 certificates: self
742 .tls_certs()
743 .iter()
744 .map(|(peer, cert)| {
745 (
746 *peer,
747 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
748 )
749 })
750 .collect(),
751 peer_names: self
752 .p2p_urls()
753 .into_iter()
754 .map(|(id, peer)| (id, peer.name))
755 .collect(),
756 }
757 }
758
759 pub fn tls_certs(&self) -> BTreeMap<PeerId, String> {
760 self.peers
761 .iter()
762 .filter_map(|(id, peer)| {
763 match peer.endpoints.clone() {
764 PeerEndpoints::Tcp { cert, .. } => Some(cert.encode_hex()),
765 PeerEndpoints::Iroh { .. } => None,
766 }
767 .map(|peer| (*id, peer))
768 })
769 .collect()
770 }
771
772 pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
773 self.peers
774 .iter()
775 .filter_map(|(id, peer)| {
776 match peer.endpoints.clone() {
777 PeerEndpoints::Tcp { p2p_url, .. } => Some(PeerUrl {
778 name: peer.name.clone(),
779 url: p2p_url.clone(),
780 }),
781 PeerEndpoints::Iroh { .. } => None,
782 }
783 .map(|peer| (*id, peer))
784 })
785 .collect()
786 }
787
788 pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
789 self.peers
790 .iter()
791 .filter_map(|(id, peer)| {
792 match peer.endpoints.clone() {
793 PeerEndpoints::Tcp { api_url, .. } => Some(PeerUrl {
794 name: peer.name.clone(),
795 url: api_url.clone(),
796 }),
797 PeerEndpoints::Iroh { .. } => None,
798 }
799 .map(|peer| (*id, peer))
800 })
801 .collect()
802 }
803
804 pub fn iroh_endpoints(&self) -> BTreeMap<PeerId, PeerIrohEndpoints> {
805 self.peers
806 .iter()
807 .filter_map(|(id, peer)| {
808 match peer.endpoints.clone() {
809 PeerEndpoints::Tcp { .. } => None,
810 PeerEndpoints::Iroh { api_pk, p2p_pk } => Some(PeerIrohEndpoints {
811 name: peer.name.clone(),
812 api_pk,
813 p2p_pk,
814 }),
815 }
816 .map(|peer| (*id, peer))
817 })
818 .collect()
819 }
820}