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