Skip to main content

fedimint_server/config/
mod.rs

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