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