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 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: 5 }])
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 return Ok(server[¶ms.identity].clone());
547 }
548
549 info!(
550 target: LOG_NET_PEER_DKG,
551 "Waiting for all p2p connections to open..."
552 );
553
554 while p2p_status_receivers.values().any(|r| r.borrow().is_none()) {
555 let peers = p2p_status_receivers
556 .iter()
557 .filter_map(|entry| entry.1.borrow().map(|_| *entry.0))
558 .collect::<Vec<PeerId>>();
559
560 info!(
561 target: LOG_NET_PEER_DKG,
562 "Connected to peers: {peers:?}..."
563 );
564
565 sleep(Duration::from_secs(1)).await;
566 }
567
568 let checksum = params.peers.consensus_hash_sha256();
569
570 info!(
571 target: LOG_NET_PEER_DKG,
572 "Comparing connection codes checksum {checksum}..."
573 );
574
575 connections
576 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
577 .await;
578
579 for peer in params
580 .peer_ids()
581 .into_iter()
582 .filter(|p| *p != params.identity)
583 {
584 ensure!(
585 connections
586 .receive_from_peer(peer)
587 .await
588 .context("Unexpected shutdown of p2p connections")?
589 == P2PMessage::Checksum(checksum),
590 "Peer {peer} has not send the correct checksum message"
591 );
592 }
593
594 info!(
595 target: LOG_NET_PEER_DKG,
596 "Running config generation..."
597 );
598
599 let handle = PeerHandle::new(
600 params.peer_ids().to_num_peers(),
601 params.identity,
602 &connections,
603 );
604
605 let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
606
607 let broadcast_public_keys = handle.exchange_encodable(broadcast_pk).await?;
608
609 let mut module_cfgs = BTreeMap::new();
610
611 for (module_id, kind, module_params) in modules.iter_modules() {
612 info!(
613 target: LOG_NET_PEER_DKG,
614 "Running config generation for module of kind {kind}..."
615 );
616
617 let cfg = registry
618 .get(kind)
619 .with_context(|| format!("Module of kind {kind} not found"))?
620 .distributed_gen(&handle, module_params, params.disable_base_fees)
621 .await?;
622
623 module_cfgs.insert(module_id, cfg);
624 }
625
626 let cfg = ServerConfig::from(
627 params.clone(),
628 params.identity,
629 broadcast_public_keys,
630 broadcast_sk,
631 module_cfgs,
632 code_version_str,
633 );
634
635 let checksum = cfg.consensus.consensus_hash_sha256();
636
637 info!(
638 target: LOG_NET_PEER_DKG,
639 "Comparing consensus config checksum {checksum}..."
640 );
641
642 connections
643 .send(Recipient::Everyone, P2PMessage::Checksum(checksum))
644 .await;
645
646 for peer in params
647 .peer_ids()
648 .into_iter()
649 .filter(|p| *p != params.identity)
650 {
651 let peer_message = connections
652 .receive_from_peer(peer)
653 .await
654 .context("Unexpected shutdown of p2p connections")?;
655
656 if peer_message != P2PMessage::Checksum(checksum) {
657 warn!(
658 target: LOG_NET_PEER_DKG,
659 expected = ?P2PMessage::Checksum(checksum),
660 received = ?peer_message,
661 config = ?cfg.consensus,
662 "Peer {peer} has not sent the correct checksum message"
663 );
664 bail!("Peer {peer} has not send the correct checksum message");
665 }
666 }
667
668 info!(
669 target: LOG_NET_PEER_DKG,
670 "Config generation has completed successfully!"
671 );
672
673 Ok(cfg)
674 }
675}
676
677impl ServerConfig {
678 pub fn tls_config(&self) -> TlsConfig {
679 TlsConfig {
680 private_key: Arc::new(
681 rustls::pki_types::PrivateKeyDer::try_from(
682 Vec::from_hex(self.private.tls_key.clone().unwrap()).unwrap(),
683 )
684 .expect("Failed to parse private key"),
685 ),
686 certificates: self
687 .consensus
688 .tls_certs
689 .iter()
690 .map(|(peer, cert)| {
691 (
692 *peer,
693 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
694 )
695 })
696 .collect(),
697 peer_names: self
698 .local
699 .p2p_endpoints
700 .iter()
701 .map(|(id, endpoint)| (*id, endpoint.name.to_string()))
702 .collect(),
703 }
704 }
705}
706
707impl ConfigGenParams {
708 pub fn peer_ids(&self) -> Vec<PeerId> {
709 self.peers.keys().copied().collect()
710 }
711
712 pub fn tls_config(&self) -> TlsConfig {
713 TlsConfig {
714 private_key: self.tls_key.clone().unwrap(),
715 certificates: self
716 .tls_certs()
717 .iter()
718 .map(|(peer, cert)| {
719 (
720 *peer,
721 rustls::pki_types::CertificateDer::from(Vec::from_hex(cert).unwrap()),
722 )
723 })
724 .collect(),
725 peer_names: self
726 .p2p_urls()
727 .into_iter()
728 .map(|(id, peer)| (id, peer.name))
729 .collect(),
730 }
731 }
732
733 pub fn tls_certs(&self) -> BTreeMap<PeerId, String> {
734 self.peers
735 .iter()
736 .filter_map(|(id, peer)| {
737 match peer.endpoints.clone() {
738 PeerEndpoints::Tcp { cert, .. } => Some(cert.encode_hex()),
739 PeerEndpoints::Iroh { .. } => None,
740 }
741 .map(|peer| (*id, peer))
742 })
743 .collect()
744 }
745
746 pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
747 self.peers
748 .iter()
749 .filter_map(|(id, peer)| {
750 match peer.endpoints.clone() {
751 PeerEndpoints::Tcp { p2p_url, .. } => Some(PeerUrl {
752 name: peer.name.clone(),
753 url: p2p_url.clone(),
754 }),
755 PeerEndpoints::Iroh { .. } => None,
756 }
757 .map(|peer| (*id, peer))
758 })
759 .collect()
760 }
761
762 pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
763 self.peers
764 .iter()
765 .filter_map(|(id, peer)| {
766 match peer.endpoints.clone() {
767 PeerEndpoints::Tcp { api_url, .. } => Some(PeerUrl {
768 name: peer.name.clone(),
769 url: api_url.clone(),
770 }),
771 PeerEndpoints::Iroh { .. } => None,
772 }
773 .map(|peer| (*id, peer))
774 })
775 .collect()
776 }
777
778 pub fn iroh_endpoints(&self) -> BTreeMap<PeerId, PeerIrohEndpoints> {
779 self.peers
780 .iter()
781 .filter_map(|(id, peer)| {
782 match peer.endpoints.clone() {
783 PeerEndpoints::Tcp { .. } => None,
784 PeerEndpoints::Iroh { api_pk, p2p_pk } => Some(PeerIrohEndpoints {
785 name: peer.name.clone(),
786 api_pk,
787 p2p_pk,
788 }),
789 }
790 .map(|peer| (*id, peer))
791 })
792 .collect()
793 }
794}