fedimint_server/config/
mod.rs

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