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