1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::net::SocketAddr;
3use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::{Context, bail, ensure, 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::{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}
242
243impl ServerConfigConsensus {
244 pub fn api_endpoints(&self) -> BTreeMap<PeerId, PeerUrl> {
245 if self.iroh_endpoints.is_empty() {
246 self.api_endpoints.clone()
247 } else {
248 self.iroh_endpoints
249 .iter()
250 .map(|(peer, endpoints)| {
251 let url = PeerUrl {
252 name: endpoints.name.clone(),
253 url: SafeUrl::parse(&format!("iroh://{}", endpoints.api_pk))
254 .expect("Failed to parse iroh url"),
255 };
256
257 (*peer, url)
258 })
259 .collect()
260 }
261 }
262
263 pub fn iter_module_instances(
264 &self,
265 ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
266 self.modules.iter().map(|(k, v)| (*k, &v.kind))
267 }
268
269 pub fn to_client_config(
270 &self,
271 module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
272 ) -> Result<ClientConfig, anyhow::Error> {
273 let client = ClientConfig {
274 global: GlobalClientConfig {
275 api_endpoints: self.api_endpoints(),
276 broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
277 consensus_version: self.version,
278 meta: self.meta.clone(),
279 },
280 modules: self
281 .modules
282 .iter()
283 .map(|(k, v)| {
284 let r#gen = module_config_gens
285 .get(&v.kind)
286 .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
287 Ok((*k, r#gen.get_client_config(*k, v)?))
288 })
289 .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
290 };
291 Ok(client)
292 }
293}
294
295impl ServerConfig {
296 pub fn supported_api_versions() -> SupportedCoreApiVersions {
298 SupportedCoreApiVersions {
299 core_consensus: CORE_CONSENSUS_VERSION,
300 api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 5 }])
301 .expect("not version conflicts"),
302 }
303 }
304 pub fn from(
307 params: ConfigGenParams,
308 identity: PeerId,
309 broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
310 broadcast_secret_key: SecretKey,
311 modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
312 code_version: String,
313 ) -> Self {
314 let consensus = ServerConfigConsensus {
315 code_version,
316 version: CORE_CONSENSUS_VERSION,
317 broadcast_public_keys,
318 broadcast_rounds_per_session: if is_running_in_test_env() {
319 DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
320 } else {
321 DEFAULT_BROADCAST_ROUNDS_PER_SESSION
322 },
323 api_endpoints: params.api_urls(),
324 iroh_endpoints: params.iroh_endpoints(),
325 tls_certs: params.tls_certs(),
326 modules: modules
327 .iter()
328 .map(|(peer, cfg)| (*peer, cfg.consensus.clone()))
329 .collect(),
330 meta: params.meta.clone(),
331 };
332
333 let local = ServerConfigLocal {
334 p2p_endpoints: params.p2p_urls(),
335 identity,
336 max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
337 broadcast_round_delay_ms: if is_running_in_test_env() {
338 DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
339 } else {
340 DEFAULT_BROADCAST_ROUND_DELAY_MS
341 },
342 };
343
344 let private = ServerConfigPrivate {
345 api_auth: params.api_auth.clone(),
346 tls_key: params
347 .tls_key
348 .map(|key| key.secret_der().to_vec().encode_hex()),
349 iroh_api_sk: params.iroh_api_sk,
350 iroh_p2p_sk: params.iroh_p2p_sk,
351 broadcast_secret_key,
352 modules: modules
353 .iter()
354 .map(|(peer, cfg)| (*peer, cfg.private.clone()))
355 .collect(),
356 };
357
358 Self {
359 consensus,
360 local,
361 private,
362 }
363 }
364
365 pub fn get_invite_code(&self, api_secret: Option<String>) -> InviteCode {
366 InviteCode::new(
367 self.consensus.api_endpoints()[&self.local.identity]
368 .url
369 .clone(),
370 self.local.identity,
371 self.calculate_federation_id(),
372 api_secret,
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 modules: ServerModuleConfigGenParamsRegistry,
472 params: &HashMap<PeerId, ConfigGenParams>,
473 registry: &ServerModuleInitRegistry,
474 code_version_str: &str,
475 ) -> BTreeMap<PeerId, Self> {
476 let peer0 = ¶ms[&PeerId::from(0)];
477
478 let mut broadcast_pks = BTreeMap::new();
479 let mut broadcast_sks = BTreeMap::new();
480 for peer_id in peer0.peer_ids() {
481 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
482 broadcast_pks.insert(peer_id, broadcast_pk);
483 broadcast_sks.insert(peer_id, broadcast_sk);
484 }
485
486 let module_configs: BTreeMap<_, _> = modules
487 .iter_modules()
488 .map(|(module_id, kind, module_params)| {
489 (
490 module_id,
491 registry
492 .get(kind)
493 .expect("Module not registered")
494 .trusted_dealer_gen(&peer0.peer_ids(), module_params),
495 )
496 })
497 .collect();
498
499 let server_config: BTreeMap<_, _> = peer0
500 .peer_ids()
501 .iter()
502 .map(|&id| {
503 let config = ServerConfig::from(
504 params[&id].clone(),
505 id,
506 broadcast_pks.clone(),
507 *broadcast_sks.get(&id).expect("We created this entry"),
508 module_configs
509 .iter()
510 .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
511 .collect(),
512 code_version_str.to_string(),
513 );
514 (id, config)
515 })
516 .collect();
517
518 server_config
519 }
520
521 pub async fn distributed_gen(
523 modules: ServerModuleConfigGenParamsRegistry,
524 params: &ConfigGenParams,
525 registry: ServerModuleInitRegistry,
526 code_version_str: String,
527 connections: DynP2PConnections<P2PMessage>,
528 p2p_status_receivers: P2PStatusReceivers,
529 ) -> anyhow::Result<Self> {
530 let _timing = timing::TimeReporter::new("distributed-gen").info();
531
532 if params.peer_ids().len() == 1 {
534 let server = Self::trusted_dealer_gen(
535 modules,
536 &HashMap::from([(params.identity, params.clone())]),
537 ®istry,
538 &code_version_str,
539 );
540 return Ok(server[¶ms.identity].clone());
541 }
542
543 info!(
544 target: LOG_NET_PEER_DKG,
545 "Waiting for all p2p connections to open..."
546 );
547
548 while p2p_status_receivers.values().any(|r| r.borrow().is_none()) {
549 let peers = p2p_status_receivers
550 .iter()
551 .filter_map(|entry| entry.1.borrow().map(|_| *entry.0))
552 .collect::<Vec<PeerId>>();
553
554 info!(
555 target: LOG_NET_PEER_DKG,
556 "Connected to peers: {peers:?}..."
557 );
558
559 sleep(Duration::from_secs(1)).await;
560 }
561
562 let checksum = params.peers.consensus_hash_sha256();
563
564 info!(
565 target: LOG_NET_PEER_DKG,
566 "Comparing connection codes checksum {checksum}..."
567 );
568
569 connections
570 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
571 .await;
572
573 for peer in params
574 .peer_ids()
575 .into_iter()
576 .filter(|p| *p != params.identity)
577 {
578 ensure!(
579 connections
580 .receive_from_peer(peer)
581 .await
582 .context("Unexpected shutdown of p2p connections")?
583 == P2PMessage::Checksum(checksum),
584 "Peer {peer} has not send the correct checksum message"
585 );
586 }
587
588 info!(
589 target: LOG_NET_PEER_DKG,
590 "Running config generation..."
591 );
592
593 let handle = PeerHandle::new(
594 params.peer_ids().to_num_peers(),
595 params.identity,
596 &connections,
597 );
598
599 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
600
601 let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
602
603 let mut module_cfgs = BTreeMap::new();
604
605 for (module_id, kind, module_params) in modules.iter_modules() {
606 info!(
607 target: LOG_NET_PEER_DKG,
608 "Running config generation for module of kind {kind}..."
609 );
610
611 let cfg = registry
612 .get(kind)
613 .with_context(|| format!("Module of kind {kind} not found"))?
614 .distributed_gen(&handle, module_params)
615 .await?;
616
617 module_cfgs.insert(module_id, cfg);
618 }
619
620 let cfg = ServerConfig::from(
621 params.clone(),
622 params.identity,
623 broadcast_public_keys,
624 broadcast_sk,
625 module_cfgs,
626 code_version_str,
627 );
628
629 let checksum = cfg.consensus.consensus_hash_sha256();
630
631 info!(
632 target: LOG_NET_PEER_DKG,
633 "Comparing consensus config checksum {checksum}..."
634 );
635
636 connections
637 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
638 .await;
639
640 for peer in params
641 .peer_ids()
642 .into_iter()
643 .filter(|p| *p != params.identity)
644 {
645 let peer_message = connections
646 .receive_from_peer(peer)
647 .await
648 .context("Unexpected shutdown of p2p connections")?;
649
650 if peer_message != P2PMessage::Checksum(checksum) {
651 warn!(
652 target: LOG_NET_PEER_DKG,
653 expected = ?P2PMessage::Checksum(checksum),
654 received = ?peer_message,
655 config = ?cfg.consensus,
656 "Peer {peer} has not sent the correct checksum message"
657 );
658 bail!("Peer {peer} has not send the correct checksum message");
659 }
660 }
661
662 info!(
663 target: LOG_NET_PEER_DKG,
664 "Config generation has completed successfully!"
665 );
666
667 Ok(cfg)
668 }
669}
670
671impl ServerConfig {
672 pub fn tls_config(&self) -> TlsConfig {
673 TlsConfig {
674 private_key: Arc::new(
675 rustls::pki_types::PrivateKeyDer::try_from(
676 Vec::from_hex(self.private.tls_key.clone().unwrap()).unwrap(),
677 )
678 .expect("Failed to parse private key"),
679 ),
680 certificates: self
681 .consensus
682 .tls_certs
683 .iter()
684 .map(|(peer, cert)| {
685 (
686 *peer,
687 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
688 )
689 })
690 .collect(),
691 peer_names: self
692 .local
693 .p2p_endpoints
694 .iter()
695 .map(|(id, endpoint)| (*id, endpoint.name.to_string()))
696 .collect(),
697 }
698 }
699}
700
701impl ConfigGenParams {
702 pub fn peer_ids(&self) -> Vec<PeerId> {
703 self.peers.keys().copied().collect()
704 }
705
706 pub fn tls_config(&self) -> TlsConfig {
707 TlsConfig {
708 private_key: self.tls_key.clone().unwrap(),
709 certificates: self
710 .tls_certs()
711 .iter()
712 .map(|(peer, cert)| {
713 (
714 *peer,
715 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
716 )
717 })
718 .collect(),
719 peer_names: self
720 .p2p_urls()
721 .into_iter()
722 .map(|(id, peer)| (id, peer.name))
723 .collect(),
724 }
725 }
726
727 pub fn tls_certs(&self) -> BTreeMap<PeerId, String> {
728 self.peers
729 .iter()
730 .filter_map(|(id, peer)| {
731 match peer.endpoints.clone() {
732 PeerEndpoints::Tcp { cert, .. } => Some(cert.encode_hex()),
733 PeerEndpoints::Iroh { .. } => None,
734 }
735 .map(|peer| (*id, peer))
736 })
737 .collect()
738 }
739
740 pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
741 self.peers
742 .iter()
743 .filter_map(|(id, peer)| {
744 match peer.endpoints.clone() {
745 PeerEndpoints::Tcp { p2p_url, .. } => Some(PeerUrl {
746 name: peer.name.clone(),
747 url: p2p_url.clone(),
748 }),
749 PeerEndpoints::Iroh { .. } => None,
750 }
751 .map(|peer| (*id, peer))
752 })
753 .collect()
754 }
755
756 pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
757 self.peers
758 .iter()
759 .filter_map(|(id, peer)| {
760 match peer.endpoints.clone() {
761 PeerEndpoints::Tcp { api_url, .. } => Some(PeerUrl {
762 name: peer.name.clone(),
763 url: api_url.clone(),
764 }),
765 PeerEndpoints::Iroh { .. } => None,
766 }
767 .map(|peer| (*id, peer))
768 })
769 .collect()
770 }
771
772 pub fn iroh_endpoints(&self) -> BTreeMap<PeerId, PeerIrohEndpoints> {
773 self.peers
774 .iter()
775 .filter_map(|(id, peer)| {
776 match peer.endpoints.clone() {
777 PeerEndpoints::Tcp { .. } => None,
778 PeerEndpoints::Iroh { api_pk, p2p_pk } => Some(PeerIrohEndpoints {
779 name: peer.name.clone(),
780 api_pk,
781 p2p_pk,
782 }),
783 }
784 .map(|peer| (*id, peer))
785 })
786 .collect()
787 }
788}