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