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