fedimint_server/config/
mod.rs

1use std::collections::{BTreeMap, BTreeSet, HashMap};
2use std::env;
3use std::net::SocketAddr;
4use std::time::Duration;
5
6use anyhow::{bail, format_err};
7use fedimint_core::admin_client::ConfigGenParamsConsensus;
8pub use fedimint_core::config::{
9    serde_binary_human_readable, ClientConfig, DkgError, DkgPeerMsg, DkgResult, FederationId,
10    GlobalClientConfig, JsonWithKind, ModuleInitRegistry, PeerUrl, ServerModuleConfig,
11    ServerModuleConsensusConfig, ServerModuleInitRegistry, TypedServerModuleConfig,
12};
13use fedimint_core::core::{ModuleInstanceId, ModuleKind, MODULE_INSTANCE_ID_GLOBAL};
14use fedimint_core::envs::is_running_in_test_env;
15use fedimint_core::invite_code::InviteCode;
16use fedimint_core::module::{
17    ApiAuth, ApiVersion, CoreConsensusVersion, DynServerModuleInit, MultiApiVersion, PeerHandle,
18    SupportedApiVersionsSummary, SupportedCoreApiVersions, CORE_CONSENSUS_VERSION,
19};
20use fedimint_core::net::peers::{IMuxPeerConnections, IPeerConnections, PeerConnections};
21use fedimint_core::task::{timeout, Cancelled, Elapsed, TaskGroup};
22use fedimint_core::{secp256k1, timing, PeerId};
23use fedimint_logging::{LOG_NET_PEER, LOG_NET_PEER_DKG};
24use futures::future::join_all;
25use rand::rngs::OsRng;
26use secp256k1::{PublicKey, Secp256k1, SecretKey};
27use serde::de::DeserializeOwned;
28use serde::{Deserialize, Serialize};
29use tokio_rustls::rustls;
30use tracing::{error, info};
31
32use crate::config::api::ConfigGenParamsLocal;
33use crate::config::distributedgen::{DkgRunner, PeerHandleOps};
34use crate::envs::FM_MAX_CLIENT_CONNECTIONS_ENV;
35use crate::fedimint_core::encoding::Encodable;
36use crate::fedimint_core::NumPeersExt;
37use crate::multiplexed::PeerConnectionMultiplexer;
38use crate::net::connect::{dns_sanitize, Connector, TlsConfig};
39use crate::net::peers::{DelayCalculator, NetworkConfig};
40use crate::net::peers_reliable::ReconnectPeerConnectionsReliable;
41use crate::TlsTcpConnector;
42
43pub mod api;
44pub mod distributedgen;
45pub mod io;
46
47/// The default maximum open connections the API can handle
48const 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#[derive(Debug, Clone, Serialize, Deserialize)]
63/// All the serializable configuration for the fedimint server
64pub struct ServerConfig {
65    /// Contains all configuration that needs to be the same for every server
66    pub consensus: ServerConfigConsensus,
67    /// Contains all configuration that is locally configurable and not secret
68    pub local: ServerConfigLocal,
69    /// Contains all configuration that will be encrypted such as private key
70    /// material
71    pub private: ServerConfigPrivate,
72}
73
74impl ServerConfig {
75    pub fn iter_module_instances(
76        &self,
77    ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
78        self.consensus.iter_module_instances()
79    }
80
81    pub(crate) fn supported_api_versions_summary(
82        modules: &BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
83        module_inits: &ServerModuleInitRegistry,
84    ) -> SupportedApiVersionsSummary {
85        SupportedApiVersionsSummary {
86            core: Self::supported_api_versions(),
87            modules: modules
88                .iter()
89                .map(|(&id, config)| {
90                    (
91                        id,
92                        module_inits
93                            .get(&config.kind)
94                            .expect("missing module kind gen")
95                            .supported_api_versions(),
96                    )
97                })
98                .collect(),
99        }
100    }
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct ServerConfigPrivate {
105    /// Secret API auth string
106    pub api_auth: ApiAuth,
107    /// Secret key for TLS communication, required for peer authentication
108    #[serde(with = "serde_tls_key")]
109    pub tls_key: rustls::PrivateKey,
110    /// Secret key for the atomic broadcast to sign messages
111    pub broadcast_secret_key: SecretKey,
112    /// Secret material from modules
113    pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize, Encodable)]
117pub struct ServerConfigConsensus {
118    /// The version of the binary code running
119    pub code_version: String,
120    /// Agreed on core consensus version
121    pub version: CoreConsensusVersion,
122    /// Public keys for the atomic broadcast to authenticate messages
123    pub broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
124    /// Number of rounds per session.
125    #[serde(default = "default_broadcast_rounds_per_session")]
126    pub broadcast_rounds_per_session: u16,
127    /// Network addresses and names for all peer APIs
128    pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
129    /// Certs for TLS communication, required for peer authentication
130    #[serde(with = "serde_tls_cert_map")]
131    pub tls_certs: BTreeMap<PeerId, rustls::Certificate>,
132    /// All configuration that needs to be the same for modules
133    pub modules: BTreeMap<ModuleInstanceId, ServerModuleConsensusConfig>,
134    /// Additional config the federation wants to transmit to the clients
135    pub meta: BTreeMap<String, String>,
136}
137
138// FIXME: (@leonardo) Should this have another field for the expected transport
139// ? (e.g. clearnet/tor/...)
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ServerConfigLocal {
142    /// Network addresses and names for all p2p connections
143    pub p2p_endpoints: BTreeMap<PeerId, PeerUrl>,
144    /// Our peer id (generally should not change)
145    pub identity: PeerId,
146    /// How many API connections we will accept
147    pub max_connections: u32,
148    /// Influences the atomic broadcast ordering latency, should be higher than
149    /// the expected latency between peers so everyone can get proposed
150    /// consensus items confirmed. This is only relevant for byzantine
151    /// faults.
152    pub broadcast_round_delay_ms: u16,
153    /// Non-consensus, non-private configuration from modules
154    pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
155}
156
157#[derive(Debug, Clone)]
158/// All the parameters necessary for generating the `ServerConfig` during setup
159///
160/// * Guardians can create the parameters using a setup UI or CLI tool
161/// * Used for distributed or trusted config generation
162pub struct ConfigGenParams {
163    pub local: ConfigGenParamsLocal,
164    pub consensus: ConfigGenParamsConsensus,
165}
166
167impl ServerConfigConsensus {
168    pub fn iter_module_instances(
169        &self,
170    ) -> impl Iterator<Item = (ModuleInstanceId, &ModuleKind)> + '_ {
171        self.modules.iter().map(|(k, v)| (*k, &v.kind))
172    }
173
174    pub fn to_client_config(
175        &self,
176        module_config_gens: &ModuleInitRegistry<DynServerModuleInit>,
177    ) -> Result<ClientConfig, anyhow::Error> {
178        let client = ClientConfig {
179            global: GlobalClientConfig {
180                api_endpoints: self.api_endpoints.clone(),
181                broadcast_public_keys: Some(self.broadcast_public_keys.clone()),
182                consensus_version: self.version,
183                meta: self.meta.clone(),
184            },
185            modules: self
186                .modules
187                .iter()
188                .map(|(k, v)| {
189                    let gen = module_config_gens
190                        .get(&v.kind)
191                        .ok_or_else(|| format_err!("Module gen kind={} not found", v.kind))?;
192                    Ok((*k, gen.get_client_config(*k, v)?))
193                })
194                .collect::<anyhow::Result<BTreeMap<_, _>>>()?,
195        };
196        Ok(client)
197    }
198}
199
200impl ServerConfig {
201    /// Api versions supported by this server
202    pub fn supported_api_versions() -> SupportedCoreApiVersions {
203        SupportedCoreApiVersions {
204            core_consensus: CORE_CONSENSUS_VERSION,
205            api: MultiApiVersion::try_from_iter([ApiVersion { major: 0, minor: 4 }])
206                .expect("not version conflicts"),
207        }
208    }
209    /// Creates a new config from the results of a trusted or distributed key
210    /// setup
211    pub fn from(
212        params: ConfigGenParams,
213        identity: PeerId,
214        broadcast_public_keys: BTreeMap<PeerId, PublicKey>,
215        broadcast_secret_key: SecretKey,
216        modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>,
217        code_version_str: String,
218    ) -> Self {
219        let private = ServerConfigPrivate {
220            api_auth: params.local.api_auth.clone(),
221            tls_key: params.local.our_private_key.clone(),
222            broadcast_secret_key,
223            modules: BTreeMap::new(),
224        };
225        let local = ServerConfigLocal {
226            p2p_endpoints: params.p2p_urls(),
227            identity,
228            max_connections: DEFAULT_MAX_CLIENT_CONNECTIONS,
229            broadcast_round_delay_ms: if is_running_in_test_env() {
230                DEFAULT_TEST_BROADCAST_ROUND_DELAY_MS
231            } else {
232                DEFAULT_BROADCAST_ROUND_DELAY_MS
233            },
234            modules: BTreeMap::new(),
235        };
236        let consensus = ServerConfigConsensus {
237            code_version: code_version_str,
238            version: CORE_CONSENSUS_VERSION,
239            broadcast_public_keys,
240            broadcast_rounds_per_session: if is_running_in_test_env() {
241                DEFAULT_TEST_BROADCAST_ROUNDS_PER_SESSION
242            } else {
243                DEFAULT_BROADCAST_ROUNDS_PER_SESSION
244            },
245            api_endpoints: params.api_urls(),
246            tls_certs: params.tls_certs(),
247            modules: BTreeMap::new(),
248            meta: params.consensus.meta,
249        };
250        let mut cfg = Self {
251            consensus,
252            local,
253            private,
254        };
255        cfg.add_modules(modules);
256        cfg
257    }
258
259    pub fn get_invite_code(&self, api_secret: Option<String>) -> InviteCode {
260        InviteCode::new(
261            self.consensus.api_endpoints[&self.local.identity]
262                .url
263                .clone(),
264            self.local.identity,
265            self.calculate_federation_id(),
266            api_secret,
267        )
268    }
269
270    pub fn calculate_federation_id(&self) -> FederationId {
271        FederationId(self.consensus.api_endpoints.consensus_hash())
272    }
273
274    pub fn add_modules(&mut self, modules: BTreeMap<ModuleInstanceId, ServerModuleConfig>) {
275        for (name, config) in modules {
276            let ServerModuleConfig {
277                local,
278                private,
279                consensus,
280            } = config;
281
282            self.local.modules.insert(name, local);
283            self.private.modules.insert(name, private);
284            self.consensus.modules.insert(name, consensus);
285        }
286    }
287
288    /// Constructs a module config by name
289    pub fn get_module_config_typed<T: TypedServerModuleConfig>(
290        &self,
291        id: ModuleInstanceId,
292    ) -> anyhow::Result<T> {
293        let local = Self::get_module_cfg_by_instance_id(&self.local.modules, id)?;
294        let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
295        let consensus = self
296            .consensus
297            .modules
298            .get(&id)
299            .ok_or_else(|| format_err!("Module {id} not found"))?
300            .clone();
301        let module = ServerModuleConfig::from(local, private, consensus);
302
303        module.to_typed()
304    }
305    pub fn get_module_id_by_kind(
306        &self,
307        kind: impl Into<ModuleKind>,
308    ) -> anyhow::Result<ModuleInstanceId> {
309        let kind = kind.into();
310        Ok(*self
311            .consensus
312            .modules
313            .iter()
314            .find(|(_, v)| v.kind == kind)
315            .ok_or_else(|| format_err!("Module {kind} not found"))?
316            .0)
317    }
318
319    /// Constructs a module config by id
320    pub fn get_module_config(&self, id: ModuleInstanceId) -> anyhow::Result<ServerModuleConfig> {
321        let local = Self::get_module_cfg_by_instance_id(&self.local.modules, id)?;
322        let private = Self::get_module_cfg_by_instance_id(&self.private.modules, id)?;
323        let consensus = self
324            .consensus
325            .modules
326            .get(&id)
327            .ok_or_else(|| format_err!("Module {id} not found"))?
328            .clone();
329        Ok(ServerModuleConfig::from(local, private, consensus))
330    }
331
332    fn get_module_cfg_by_instance_id(
333        json: &BTreeMap<ModuleInstanceId, JsonWithKind>,
334        id: ModuleInstanceId,
335    ) -> anyhow::Result<JsonWithKind> {
336        Ok(json
337            .get(&id)
338            .ok_or_else(|| format_err!("Module {id} not found"))
339            .cloned()?
340            .with_fixed_empty_value())
341    }
342
343    pub fn validate_config(
344        &self,
345        identity: &PeerId,
346        module_config_gens: &ServerModuleInitRegistry,
347    ) -> anyhow::Result<()> {
348        let peers = self.local.p2p_endpoints.clone();
349        let consensus = self.consensus.clone();
350        let private = self.private.clone();
351
352        let my_public_key = private.broadcast_secret_key.public_key(&Secp256k1::new());
353
354        if Some(&my_public_key) != consensus.broadcast_public_keys.get(identity) {
355            bail!("Broadcast secret key doesn't match corresponding public key");
356        }
357        if peers.keys().max().copied().map(PeerId::to_usize) != Some(peers.len() - 1) {
358            bail!("Peer ids are not indexed from 0");
359        }
360        if peers.keys().min().copied() != Some(PeerId::from(0)) {
361            bail!("Peer ids are not indexed from 0");
362        }
363
364        for (module_id, module_kind) in &self
365            .consensus
366            .modules
367            .iter()
368            .map(|(id, config)| Ok((*id, config.kind.clone())))
369            .collect::<anyhow::Result<BTreeSet<_>>>()?
370        {
371            module_config_gens
372                .get(module_kind)
373                .ok_or_else(|| format_err!("module config gen not found {module_kind}"))?
374                .validate_config(identity, self.get_module_config(*module_id)?)?;
375        }
376
377        Ok(())
378    }
379
380    pub fn trusted_dealer_gen(
381        params: &HashMap<PeerId, ConfigGenParams>,
382        registry: &ServerModuleInitRegistry,
383        code_version_str: &str,
384    ) -> BTreeMap<PeerId, Self> {
385        let peer0 = &params[&PeerId::from(0)];
386
387        let mut broadcast_pks = BTreeMap::new();
388        let mut broadcast_sks = BTreeMap::new();
389        for peer_id in peer0.peer_ids() {
390            let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
391            broadcast_pks.insert(peer_id, broadcast_pk);
392            broadcast_sks.insert(peer_id, broadcast_sk);
393        }
394
395        let modules = peer0.consensus.modules.iter_modules();
396        let module_configs: BTreeMap<_, _> = modules
397            .map(|(module_id, kind, module_params)| {
398                (
399                    module_id,
400                    registry
401                        .get(kind)
402                        .expect("Module not registered")
403                        .trusted_dealer_gen(&peer0.peer_ids(), module_params),
404                )
405            })
406            .collect();
407
408        let server_config: BTreeMap<_, _> = peer0
409            .peer_ids()
410            .iter()
411            .map(|&id| {
412                let config = ServerConfig::from(
413                    params[&id].clone(),
414                    id,
415                    broadcast_pks.clone(),
416                    *broadcast_sks.get(&id).expect("We created this entry"),
417                    module_configs
418                        .iter()
419                        .map(|(module_id, cfgs)| (*module_id, cfgs[&id].clone()))
420                        .collect(),
421                    code_version_str.to_string(),
422                );
423                (id, config)
424            })
425            .collect();
426
427        server_config
428    }
429
430    /// Runs the distributed key gen algorithm
431    pub async fn distributed_gen(
432        p2p_bind_addr: SocketAddr,
433        params: &ConfigGenParams,
434        registry: ServerModuleInitRegistry,
435        delay_calculator: DelayCalculator,
436        task_group: &TaskGroup,
437        code_version_str: String,
438    ) -> DkgResult<Self> {
439        let _timing /* logs on drop */ = timing::TimeReporter::new("distributed-gen").info();
440        let server_conn = connect(
441            params.p2p_network(p2p_bind_addr),
442            params.tls_config(),
443            delay_calculator,
444            task_group,
445        )
446        .await;
447        let connections = PeerConnectionMultiplexer::new(server_conn).into_dyn();
448
449        let peers = &params.peer_ids();
450        let our_id = &params.local.our_id;
451
452        let broadcast_keys_exchange = PeerHandle::new(
453            &connections,
454            MODULE_INSTANCE_ID_GLOBAL,
455            *our_id,
456            peers.clone(),
457        );
458
459        let (broadcast_sk, broadcast_pk) = secp256k1::generate_keypair(&mut OsRng);
460
461        let broadcast_public_keys = broadcast_keys_exchange
462            .exchange_pubkeys("broadcast".to_string(), broadcast_pk)
463            .await?;
464
465        // in case we are running by ourselves, avoid DKG
466        if peers.len() == 1 {
467            let server = Self::trusted_dealer_gen(
468                &HashMap::from([(*our_id, params.clone())]),
469                &registry,
470                &code_version_str,
471            );
472            return Ok(server[our_id].clone());
473        }
474        info!(
475            target: LOG_NET_PEER_DKG,
476            "Peer {} running distributed key generation...", our_id
477        );
478
479        // BFT uses a lower threshold of signing keys (f+1)
480        let mut dkg = DkgRunner::new(
481            KeyType::Bft,
482            peers.to_num_peers().one_honest(),
483            our_id,
484            peers,
485        );
486        dkg.add(KeyType::Auth, peers.to_num_peers().threshold());
487        dkg.add(KeyType::Epoch, peers.to_num_peers().threshold());
488
489        let mut registered_modules = registry.kinds();
490        let mut module_cfgs: BTreeMap<ModuleInstanceId, ServerModuleConfig> = BTreeMap::new();
491        let modules = params.consensus.modules.iter_modules();
492        let modules_runner = modules.map(|(module_instance_id, kind, module_params)| {
493            let dkg = PeerHandle::new(&connections, module_instance_id, *our_id, peers.clone());
494            let registry = registry.clone();
495
496            async move {
497                let result = match registry.get(kind) {
498                    None => Err(DkgError::ModuleNotFound(kind.clone())),
499                    Some(gen) => gen.distributed_gen(&dkg, module_params).await,
500                };
501                (module_instance_id, result)
502            }
503        });
504        for (module_instance_id, config) in join_all(modules_runner).await {
505            let config = config?;
506            registered_modules.remove(&config.consensus.kind);
507            module_cfgs.insert(module_instance_id, config);
508        }
509        if !registered_modules.is_empty() {
510            return Err(DkgError::ParamsNotFound(registered_modules));
511        }
512
513        info!(
514            target: LOG_NET_PEER_DKG,
515            "Sending confirmations to other peers."
516        );
517        // Note: Since our outgoing buffers are asynchronous, we don't actually know
518        // if other peers received our message, just because we received theirs.
519        // That's why we need to do a one last best effort sync.
520        let dkg_done = "DKG DONE".to_string();
521        connections
522            .send(
523                peers,
524                (MODULE_INSTANCE_ID_GLOBAL, dkg_done.clone()),
525                DkgPeerMsg::Done,
526            )
527            .await?;
528
529        info!(
530            target: LOG_NET_PEER_DKG,
531            "Waiting for confirmations from other peers."
532        );
533        if let Err(Elapsed) = timeout(Duration::from_secs(30), async {
534            let mut done_peers = BTreeSet::from([*our_id]);
535
536            while done_peers.len() < peers.len() {
537                match connections.receive((MODULE_INSTANCE_ID_GLOBAL, dkg_done.clone())).await {
538                    Ok((peer_id, DkgPeerMsg::Done)) => {
539                        info!(
540                            target: LOG_NET_PEER_DKG,
541                            peer_id = %peer_id,
542                            "Got completion confirmation"
543                        );
544                        done_peers.insert(peer_id);
545                    },
546                    Ok((peer_id, msg)) => {
547                        error!(target: LOG_NET_PEER_DKG, %peer_id, ?msg, "Received incorrect message after dkg was supposed to be finished. Probably dkg multiplexing bug.");
548                    },
549                    Err(Cancelled) => {/* ignore shutdown for time being, we'll timeout soon anyway */},
550                }
551            }
552        })
553        .await
554        {
555            error!(target: LOG_NET_PEER_DKG, "Timeout waiting for dkg completion confirmation from other peers");
556        };
557
558        let server = ServerConfig::from(
559            params.clone(),
560            *our_id,
561            broadcast_public_keys,
562            broadcast_sk,
563            module_cfgs,
564            code_version_str,
565        );
566
567        info!(
568            target: LOG_NET_PEER,
569            "Distributed key generation has completed successfully!"
570        );
571
572        Ok(server)
573    }
574}
575
576/// The types of keys to run distributed key generation for
577#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
578pub enum KeyType {
579    Bft,
580    Epoch,
581    Auth,
582}
583
584impl ServerConfig {
585    pub fn network_config(&self, p2p_bind_addr: SocketAddr) -> NetworkConfig {
586        NetworkConfig {
587            identity: self.local.identity,
588            peers: self
589                .local
590                .p2p_endpoints
591                .iter()
592                .map(|(&id, endpoint)| (id, endpoint.url.clone()))
593                .collect(),
594            p2p_bind_addr,
595        }
596    }
597
598    pub fn tls_config(&self) -> TlsConfig {
599        TlsConfig {
600            our_private_key: self.private.tls_key.clone(),
601            peer_certs: self.consensus.tls_certs.clone(),
602            peer_names: self
603                .local
604                .p2p_endpoints
605                .iter()
606                .map(|(id, endpoint)| (*id, endpoint.name.to_string()))
607                .collect(),
608        }
609    }
610
611    pub fn get_incoming_count(&self) -> u16 {
612        self.local.identity.into()
613    }
614}
615
616impl ConfigGenParams {
617    pub fn peer_ids(&self) -> Vec<PeerId> {
618        self.consensus.peers.keys().copied().collect()
619    }
620
621    pub fn p2p_network(&self, p2p_bind_addr: SocketAddr) -> NetworkConfig {
622        NetworkConfig {
623            identity: self.local.our_id,
624            p2p_bind_addr,
625            peers: self
626                .p2p_urls()
627                .into_iter()
628                .map(|(id, peer)| (id, peer.url))
629                .collect(),
630        }
631    }
632
633    pub fn tls_config(&self) -> TlsConfig {
634        TlsConfig {
635            our_private_key: self.local.our_private_key.clone(),
636            peer_certs: self.tls_certs(),
637            peer_names: self
638                .p2p_urls()
639                .into_iter()
640                .map(|(id, peer)| (id, peer.name))
641                .collect(),
642        }
643    }
644
645    pub fn tls_certs(&self) -> BTreeMap<PeerId, rustls::Certificate> {
646        self.consensus
647            .peers
648            .iter()
649            .map(|(id, peer)| (*id, peer.cert.clone()))
650            .collect::<BTreeMap<_, _>>()
651    }
652
653    pub fn p2p_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
654        self.consensus
655            .peers
656            .iter()
657            .map(|(id, peer)| {
658                (
659                    *id,
660                    PeerUrl {
661                        name: peer.name.clone(),
662                        url: peer.p2p_url.clone(),
663                    },
664                )
665            })
666            .collect::<BTreeMap<_, _>>()
667    }
668
669    pub fn api_urls(&self) -> BTreeMap<PeerId, PeerUrl> {
670        self.consensus
671            .peers
672            .iter()
673            .map(|(id, peer)| {
674                (
675                    *id,
676                    PeerUrl {
677                        name: peer.name.clone(),
678                        url: peer.api_url.clone(),
679                    },
680                )
681            })
682            .collect::<BTreeMap<_, _>>()
683    }
684}
685
686// TODO: Remove once new config gen UI is written
687pub fn max_connections() -> u32 {
688    env::var(FM_MAX_CLIENT_CONNECTIONS_ENV)
689        .ok()
690        .and_then(|s| s.parse().ok())
691        .unwrap_or(DEFAULT_MAX_CLIENT_CONNECTIONS)
692}
693
694pub async fn connect<T>(
695    network: NetworkConfig,
696    certs: TlsConfig,
697    delay_calculator: DelayCalculator,
698    task_group: &TaskGroup,
699) -> PeerConnections<T>
700where
701    T: std::fmt::Debug + Clone + Serialize + DeserializeOwned + Unpin + Send + Sync + 'static,
702{
703    let connector = TlsTcpConnector::new(certs, network.identity).into_dyn();
704    let (connections, _) =
705        ReconnectPeerConnectionsReliable::new(network, delay_calculator, connector, task_group)
706            .await;
707    connections.into_dyn()
708}
709
710pub fn gen_cert_and_key(
711    name: &str,
712) -> Result<(rustls::Certificate, rustls::PrivateKey), anyhow::Error> {
713    let keypair = rcgen::KeyPair::generate()?;
714    let keypair_ser = keypair.serialize_der();
715    let mut params = rcgen::CertificateParams::new(vec![dns_sanitize(name)])?;
716
717    params.is_ca = rcgen::IsCa::NoCa;
718    params
719        .distinguished_name
720        .push(rcgen::DnType::CommonName, dns_sanitize(name));
721
722    let cert = params.self_signed(&keypair)?;
723
724    Ok((
725        rustls::Certificate(cert.der().to_vec()),
726        rustls::PrivateKey(keypair_ser),
727    ))
728}
729
730mod serde_tls_cert_map {
731    use std::borrow::Cow;
732    use std::collections::BTreeMap;
733
734    use fedimint_core::PeerId;
735    use hex::{FromHex, ToHex};
736    use serde::de::Error;
737    use serde::ser::SerializeMap;
738    use serde::{Deserialize, Deserializer, Serializer};
739    use tokio_rustls::rustls;
740
741    pub fn serialize<S>(
742        certs: &BTreeMap<PeerId, rustls::Certificate>,
743        serializer: S,
744    ) -> Result<S::Ok, S::Error>
745    where
746        S: Serializer,
747    {
748        let mut serializer = serializer.serialize_map(Some(certs.len()))?;
749        for (key, value) in certs {
750            serializer.serialize_key(key)?;
751            let hex_str = value.0.encode_hex::<String>();
752            serializer.serialize_value(&hex_str)?;
753        }
754        serializer.end()
755    }
756
757    pub fn deserialize<'de, D>(
758        deserializer: D,
759    ) -> Result<BTreeMap<PeerId, rustls::Certificate>, D::Error>
760    where
761        D: Deserializer<'de>,
762    {
763        let map: BTreeMap<PeerId, Cow<str>> = Deserialize::deserialize(deserializer)?;
764        let mut certs = BTreeMap::new();
765
766        for (key, value) in map {
767            let cert =
768                rustls::Certificate(Vec::from_hex(value.as_ref()).map_err(D::Error::custom)?);
769            certs.insert(key, cert);
770        }
771        Ok(certs)
772    }
773}
774
775mod serde_tls_key {
776    use std::borrow::Cow;
777
778    use hex::{FromHex, ToHex};
779    use serde::{Deserialize, Deserializer, Serialize, Serializer};
780    use tokio_rustls::rustls;
781
782    pub fn serialize<S>(key: &rustls::PrivateKey, serializer: S) -> Result<S::Ok, S::Error>
783    where
784        S: Serializer,
785    {
786        let hex_str = key.0.encode_hex::<String>();
787        Serialize::serialize(&hex_str, serializer)
788    }
789
790    pub fn deserialize<'de, D>(deserializer: D) -> Result<rustls::PrivateKey, D::Error>
791    where
792        D: Deserializer<'de>,
793    {
794        let hex_str: Cow<str> = Deserialize::deserialize(deserializer)?;
795        let bytes = Vec::from_hex(hex_str.as_ref()).map_err(serde::de::Error::custom)?;
796        Ok(rustls::PrivateKey(bytes))
797    }
798}