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