Skip to main content

fedimint_server/config/
mod.rs

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