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