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