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