fedimint_core/
config.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt::{Debug, Display};
3use std::hash::Hash;
4use std::path::Path;
5use std::str::FromStr;
6
7use anyhow::{Context, format_err};
8use bitcoin::hashes::sha256::HashEngine;
9use bitcoin::hashes::{Hash as BitcoinHash, hex, sha256};
10use bls12_381::Scalar;
11use fedimint_core::core::{ModuleInstanceId, ModuleKind};
12use fedimint_core::encoding::{DynRawFallback, Encodable};
13use fedimint_core::module::registry::ModuleRegistry;
14use fedimint_core::util::SafeUrl;
15use fedimint_core::{ModuleDecoderRegistry, format_hex};
16use fedimint_logging::LOG_CORE;
17use hex::FromHex;
18use secp256k1::PublicKey;
19use serde::de::DeserializeOwned;
20use serde::ser::SerializeMap;
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22use serde_json::json;
23use threshold_crypto::{G1Projective, G2Projective};
24use tracing::warn;
25
26use crate::core::DynClientConfig;
27use crate::encoding::Decodable;
28use crate::module::{
29    CoreConsensusVersion, DynCommonModuleInit, IDynCommonModuleInit, ModuleConsensusVersion,
30};
31use crate::{PeerId, maybe_add_send_sync};
32
33// TODO: make configurable
34/// This limits the RAM consumption of a AlephBFT Unit to roughly 50kB
35pub const ALEPH_BFT_UNIT_BYTE_LIMIT: usize = 50_000;
36
37/// [`serde_json::Value`] that must contain `kind: String` field
38///
39/// TODO: enforce at ser/deserialization
40/// TODO: make inside prive and enforce `kind` on construction, to
41/// other functions non-falliable
42#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
43pub struct JsonWithKind {
44    kind: ModuleKind,
45    #[serde(flatten)]
46    value: serde_json::Value,
47}
48
49impl JsonWithKind {
50    pub fn new(kind: ModuleKind, value: serde_json::Value) -> Self {
51        Self { kind, value }
52    }
53
54    /// Workaround for a serde `flatten` quirk
55    ///
56    /// We serialize config with no fields as: eg. `{ kind: "ln" }`.
57    ///
58    /// When `kind` gets removed and `value` is parsed, it will
59    /// parse as `Value::Object` that is empty.
60    ///
61    /// However empty module structs, like `struct FooConfigLocal;` (unit
62    /// struct), will fail to deserialize with this value, as they expect
63    /// `Value::Null`.
64    ///
65    /// We can turn manually empty object into null, and that's what
66    /// we do in this function. This fixes the deserialization into
67    /// unit type, but in turn breaks deserialization into `struct Foo{}`,
68    /// which is arguably much less common, but valid.
69    ///
70    /// TODO: In the future, we should have a typed and erased versions of
71    /// module construction traits, and then we can try with and
72    /// without the workaround to have both cases working.
73    /// See <https://github.com/fedimint/fedimint/issues/1303>
74    pub fn with_fixed_empty_value(self) -> Self {
75        if let serde_json::Value::Object(ref o) = self.value
76            && o.is_empty()
77        {
78            return Self {
79                kind: self.kind,
80                value: serde_json::Value::Null,
81            };
82        }
83
84        self
85    }
86
87    pub fn value(&self) -> &serde_json::Value {
88        &self.value
89    }
90
91    pub fn kind(&self) -> &ModuleKind {
92        &self.kind
93    }
94
95    pub fn is_kind(&self, kind: &ModuleKind) -> bool {
96        &self.kind == kind
97    }
98}
99
100#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize, Encodable, Decodable)]
101pub struct PeerUrl {
102    /// The peer's public URL (e.g. `wss://fedimint-server-1:5000`)
103    pub url: SafeUrl,
104    /// The peer's name
105    pub name: String,
106}
107
108/// Total client config v0 (<0.4.0). Does not contain broadcast public keys.
109///
110/// This includes global settings and client-side module configs.
111#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
112pub struct ClientConfigV0 {
113    #[serde(flatten)]
114    pub global: GlobalClientConfigV0,
115    #[serde(deserialize_with = "de_int_key")]
116    pub modules: BTreeMap<ModuleInstanceId, ClientModuleConfig>,
117}
118
119/// Total client config
120///
121/// This includes global settings and client-side module configs.
122#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
123pub struct ClientConfig {
124    #[serde(flatten)]
125    pub global: GlobalClientConfig,
126    #[serde(deserialize_with = "de_int_key")]
127    pub modules: BTreeMap<ModuleInstanceId, ClientModuleConfig>,
128}
129
130// FIXME: workaround for https://github.com/serde-rs/json/issues/989
131fn de_int_key<'de, D, K, V>(deserializer: D) -> Result<BTreeMap<K, V>, D::Error>
132where
133    D: Deserializer<'de>,
134    K: Eq + Ord + FromStr,
135    K::Err: Display,
136    V: Deserialize<'de>,
137{
138    let string_map = <BTreeMap<String, V>>::deserialize(deserializer)?;
139    let map = string_map
140        .into_iter()
141        .map(|(key_str, value)| {
142            let key = K::from_str(&key_str).map_err(serde::de::Error::custom)?;
143            Ok((key, value))
144        })
145        .collect::<Result<BTreeMap<_, _>, _>>()?;
146    Ok(map)
147}
148
149fn optional_de_int_key<'de, D, K, V>(deserializer: D) -> Result<Option<BTreeMap<K, V>>, D::Error>
150where
151    D: Deserializer<'de>,
152    K: Eq + Ord + FromStr,
153    K::Err: Display,
154    V: Deserialize<'de>,
155{
156    let Some(string_map) = <Option<BTreeMap<String, V>>>::deserialize(deserializer)? else {
157        return Ok(None);
158    };
159
160    let map = string_map
161        .into_iter()
162        .map(|(key_str, value)| {
163            let key = K::from_str(&key_str).map_err(serde::de::Error::custom)?;
164            Ok((key, value))
165        })
166        .collect::<Result<BTreeMap<_, _>, _>>()?;
167
168    Ok(Some(map))
169}
170
171/// Client config that cannot be cryptographically verified but is easier to
172/// parse by external tools
173#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
174pub struct JsonClientConfig {
175    pub global: GlobalClientConfig,
176    pub modules: BTreeMap<ModuleInstanceId, JsonWithKind>,
177}
178
179/// Federation-wide client config
180#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
181pub struct GlobalClientConfigV0 {
182    /// API endpoints for each federation member
183    #[serde(deserialize_with = "de_int_key")]
184    pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
185    /// Core consensus version
186    pub consensus_version: CoreConsensusVersion,
187    // TODO: make it a String -> serde_json::Value map?
188    /// Additional config the federation wants to transmit to the clients
189    pub meta: BTreeMap<String, String>,
190}
191
192/// Federation-wide client config
193#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
194pub struct GlobalClientConfig {
195    /// API endpoints for each federation member
196    #[serde(deserialize_with = "de_int_key")]
197    pub api_endpoints: BTreeMap<PeerId, PeerUrl>,
198    /// Signing session keys for each federation member
199    /// Optional for 0.3.x backwards compatibility
200    #[serde(default, deserialize_with = "optional_de_int_key")]
201    pub broadcast_public_keys: Option<BTreeMap<PeerId, PublicKey>>,
202    /// Core consensus version
203    pub consensus_version: CoreConsensusVersion,
204    // TODO: make it a String -> serde_json::Value map?
205    /// Additional config the federation wants to transmit to the clients
206    pub meta: BTreeMap<String, String>,
207}
208
209impl GlobalClientConfig {
210    /// 0.4.0 and later uses a hash of broadcast public keys to calculate the
211    /// federation id. 0.3.x and earlier use a hash of api endpoints
212    pub fn calculate_federation_id(&self) -> FederationId {
213        FederationId(self.api_endpoints.consensus_hash())
214    }
215
216    /// Federation name from config metadata (if set)
217    pub fn federation_name(&self) -> Option<&str> {
218        self.meta.get(META_FEDERATION_NAME_KEY).map(|x| &**x)
219    }
220}
221
222impl ClientConfig {
223    /// See [`DynRawFallback::redecode_raw`].
224    pub fn redecode_raw(
225        self,
226        modules: &ModuleDecoderRegistry,
227    ) -> Result<Self, crate::encoding::DecodeError> {
228        Ok(Self {
229            modules: self
230                .modules
231                .into_iter()
232                .map(|(module_id, v)| {
233                    // Assuming this isn't running in any hot path it's better to have the debug
234                    // info than saving one allocation
235                    let kind = v.kind.clone();
236
237                    v.redecode_raw(modules)
238                        .context(format!("redecode_raw: instance: {module_id}, kind: {kind}"))
239                        .map(|v| (module_id, v))
240                })
241                .collect::<Result<_, _>>()?,
242            ..self
243        })
244    }
245
246    pub fn calculate_federation_id(&self) -> FederationId {
247        self.global.calculate_federation_id()
248    }
249
250    /// Get the value of a given meta field
251    pub fn meta<V: serde::de::DeserializeOwned + 'static>(
252        &self,
253        key: &str,
254    ) -> Result<Option<V>, anyhow::Error> {
255        let Some(str_value) = self.global.meta.get(key) else {
256            return Ok(None);
257        };
258        let res = serde_json::from_str(str_value)
259            .map(Some)
260            .context(format!("Decoding meta field '{key}' failed"));
261
262        // In the past we encoded some string fields as "just a string" without quotes,
263        // this code ensures that old meta values still parse since config is hard to
264        // change
265        if res.is_err() && std::any::TypeId::of::<V>() == std::any::TypeId::of::<String>() {
266            let string_ret = Box::new(str_value.clone());
267            let ret = unsafe {
268                // We can transmute a String to V because we know that V==String
269                std::mem::transmute::<Box<String>, Box<V>>(string_ret)
270            };
271            Ok(Some(*ret))
272        } else {
273            res
274        }
275    }
276
277    /// Converts a consensus-encoded client config struct to a client config
278    /// struct that when encoded as JSON shows the fields of module configs
279    /// instead of a consensus-encoded hex string.
280    ///
281    /// In case of unknown module the config value is a hex string.
282    pub fn to_json(&self) -> JsonClientConfig {
283        JsonClientConfig {
284            global: self.global.clone(),
285            modules: self
286                .modules
287                .iter()
288                .map(|(&module_instance_id, module_config)| {
289                    let module_config_json = JsonWithKind {
290                        kind: module_config.kind.clone(),
291                        value: module_config.config
292                            .clone()
293                            .decoded()
294                            .and_then(|dyn_cfg| dyn_cfg.to_json())
295                            .unwrap_or_else(|| json!({
296                            "unknown_module_hex": module_config.config.consensus_encode_to_hex()
297                        })),
298                    };
299                    (module_instance_id, module_config_json)
300                })
301                .collect(),
302        }
303    }
304}
305
306/// The federation id is a copy of the authentication threshold public key of
307/// the federation
308///
309/// Stable id so long as guardians membership does not change
310/// Unique id so long as guardians do not all collude
311#[derive(
312    Debug,
313    Copy,
314    Serialize,
315    Deserialize,
316    Clone,
317    Eq,
318    Hash,
319    PartialEq,
320    Encodable,
321    Decodable,
322    Ord,
323    PartialOrd,
324)]
325pub struct FederationId(pub sha256::Hash);
326
327#[derive(
328    Debug,
329    Copy,
330    Serialize,
331    Deserialize,
332    Clone,
333    Eq,
334    Hash,
335    PartialEq,
336    Encodable,
337    Decodable,
338    Ord,
339    PartialOrd,
340)]
341/// Prefix of the [`FederationId`], useful for UX improvements
342///
343/// Intentionally compact to save on the encoding. With 4 billion
344/// combinations real-life non-malicious collisions should never
345/// happen.
346pub struct FederationIdPrefix([u8; 4]);
347
348impl Display for FederationIdPrefix {
349    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
350        format_hex(&self.0, f)
351    }
352}
353
354impl Display for FederationId {
355    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
356        format_hex(&self.0.to_byte_array(), f)
357    }
358}
359
360impl FromStr for FederationIdPrefix {
361    type Err = anyhow::Error;
362
363    fn from_str(s: &str) -> Result<Self, Self::Err> {
364        Ok(Self(<[u8; 4]>::from_hex(s)?))
365    }
366}
367
368impl FederationIdPrefix {
369    pub fn to_bytes(&self) -> Vec<u8> {
370        self.0.to_vec()
371    }
372}
373
374/// Display as a hex encoding
375impl FederationId {
376    /// Random dummy id for testing
377    pub fn dummy() -> Self {
378        Self(sha256::Hash::from_byte_array([42; 32]))
379    }
380
381    pub(crate) fn from_byte_array(bytes: [u8; 32]) -> Self {
382        Self(sha256::Hash::from_byte_array(bytes))
383    }
384
385    pub fn to_prefix(&self) -> FederationIdPrefix {
386        FederationIdPrefix(self.0[..4].try_into().expect("can't fail"))
387    }
388
389    /// Converts a federation id to a public key to which we know but discard
390    /// the private key.
391    ///
392    /// Clients MUST never use this private key for any signing operations!
393    ///
394    /// That is ok because we only use the public key for adding a route
395    /// hint to LN invoices that tells fedimint clients that the invoice can
396    /// only be paid internally. Since no LN node with that pub key can exist
397    /// other LN senders will know that they cannot pay the invoice.
398    pub fn to_fake_ln_pub_key(
399        &self,
400        secp: &bitcoin::secp256k1::Secp256k1<bitcoin::secp256k1::All>,
401    ) -> anyhow::Result<bitcoin::secp256k1::PublicKey> {
402        let sk = bitcoin::secp256k1::SecretKey::from_slice(&self.0.to_byte_array())?;
403        Ok(bitcoin::secp256k1::PublicKey::from_secret_key(secp, &sk))
404    }
405}
406
407impl FromStr for FederationId {
408    type Err = anyhow::Error;
409
410    fn from_str(s: &str) -> Result<Self, Self::Err> {
411        Ok(Self::from_byte_array(<[u8; 32]>::from_hex(s)?))
412    }
413}
414
415impl ClientConfig {
416    /// Returns the consensus hash for a given client config
417    pub fn consensus_hash(&self) -> sha256::Hash {
418        let mut engine = HashEngine::default();
419        self.consensus_encode(&mut engine)
420            .expect("Consensus hashing should never fail");
421        sha256::Hash::from_engine(engine)
422    }
423
424    pub fn get_module<T: Decodable + 'static>(&self, id: ModuleInstanceId) -> anyhow::Result<&T> {
425        self.modules.get(&id).map_or_else(
426            || Err(format_err!("Client config for module id {id} not found")),
427            |client_cfg| client_cfg.cast(),
428        )
429    }
430
431    // TODO: rename this and one above
432    pub fn get_module_cfg(&self, id: ModuleInstanceId) -> anyhow::Result<ClientModuleConfig> {
433        self.modules.get(&id).map_or_else(
434            || Err(format_err!("Client config for module id {id} not found")),
435            |client_cfg| Ok(client_cfg.clone()),
436        )
437    }
438
439    /// (soft-deprecated): Get the first instance of a module of a given kind in
440    /// defined in config
441    ///
442    /// Since module ids are numerical and for time being we only support 1:1
443    /// mint, wallet, ln module code in the client, this is useful, but
444    /// please write any new code that avoids assumptions about available
445    /// modules.
446    pub fn get_first_module_by_kind<T: Decodable + 'static>(
447        &self,
448        kind: impl Into<ModuleKind>,
449    ) -> anyhow::Result<(ModuleInstanceId, &T)> {
450        let kind: ModuleKind = kind.into();
451        let Some((id, module_cfg)) = self.modules.iter().find(|(_, v)| v.is_kind(&kind)) else {
452            anyhow::bail!("Module kind {kind} not found")
453        };
454        Ok((*id, module_cfg.cast()?))
455    }
456
457    // TODO: rename this and above
458    pub fn get_first_module_by_kind_cfg(
459        &self,
460        kind: impl Into<ModuleKind>,
461    ) -> anyhow::Result<(ModuleInstanceId, ClientModuleConfig)> {
462        let kind: ModuleKind = kind.into();
463        self.modules
464            .iter()
465            .find(|(_, v)| v.is_kind(&kind))
466            .map(|(id, v)| (*id, v.clone()))
467            .ok_or_else(|| anyhow::format_err!("Module kind {kind} not found"))
468    }
469}
470
471#[derive(Clone, Debug)]
472pub struct ModuleInitRegistry<M>(BTreeMap<ModuleKind, M>);
473
474impl<M> ModuleInitRegistry<M> {
475    pub fn iter(&self) -> impl Iterator<Item = (&ModuleKind, &M)> {
476        self.0.iter()
477    }
478}
479
480impl<M> Default for ModuleInitRegistry<M> {
481    fn default() -> Self {
482        Self(BTreeMap::new())
483    }
484}
485
486/// Type erased `ModuleInitParams` used to generate the `ServerModuleConfig`
487/// during config gen
488#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
489pub struct ConfigGenModuleParams {
490    pub local: serde_json::Value,
491    pub consensus: serde_json::Value,
492}
493
494impl ConfigGenModuleParams {
495    pub fn new(local: serde_json::Value, consensus: serde_json::Value) -> Self {
496        Self { local, consensus }
497    }
498
499    /// Converts the JSON into typed version, errors unless both `local` and
500    /// `consensus` values are defined
501    pub fn to_typed<P: ModuleInitParams>(&self) -> anyhow::Result<P> {
502        Ok(P::from_parts(
503            Self::parse("local", self.local.clone())?,
504            Self::parse("consensus", self.consensus.clone())?,
505        ))
506    }
507
508    fn parse<P: DeserializeOwned>(name: &str, json: serde_json::Value) -> anyhow::Result<P> {
509        serde_json::from_value(json).with_context(|| format!("Schema mismatch for {name} argument"))
510    }
511
512    pub fn from_typed<P: ModuleInitParams>(p: P) -> anyhow::Result<Self> {
513        let (local, consensus) = p.to_parts();
514        Ok(Self {
515            local: serde_json::to_value(local)?,
516            consensus: serde_json::to_value(consensus)?,
517        })
518    }
519}
520
521pub type CommonModuleInitRegistry = ModuleInitRegistry<DynCommonModuleInit>;
522
523/// Registry that contains the config gen params for all modules
524pub type ServerModuleConfigGenParamsRegistry = ModuleRegistry<ConfigGenModuleParams>;
525
526impl Eq for ServerModuleConfigGenParamsRegistry {}
527
528impl PartialEq for ServerModuleConfigGenParamsRegistry {
529    fn eq(&self, other: &Self) -> bool {
530        self.iter_modules().eq(other.iter_modules())
531    }
532}
533
534impl Serialize for ServerModuleConfigGenParamsRegistry {
535    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
536        let modules: Vec<_> = self.iter_modules().collect();
537        let mut serializer = serializer.serialize_map(Some(modules.len()))?;
538        for (id, kind, params) in modules {
539            serializer.serialize_key(&id)?;
540            serializer.serialize_value(&(kind.clone(), params.clone()))?;
541        }
542        serializer.end()
543    }
544}
545
546impl<'de> Deserialize<'de> for ServerModuleConfigGenParamsRegistry {
547    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
548    where
549        D: Deserializer<'de>,
550    {
551        let json: BTreeMap<ModuleInstanceId, (ModuleKind, ConfigGenModuleParams)> =
552            Deserialize::deserialize(deserializer)?;
553        let mut params = BTreeMap::new();
554
555        for (id, (kind, module)) in json {
556            params.insert(id, (kind, module));
557        }
558        Ok(Self::from(params))
559    }
560}
561
562impl<M> From<Vec<M>> for ModuleInitRegistry<M>
563where
564    M: AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static>,
565{
566    fn from(value: Vec<M>) -> Self {
567        Self(
568            value
569                .into_iter()
570                .map(|i| (i.as_ref().module_kind(), i))
571                .collect::<BTreeMap<_, _>>(),
572        )
573    }
574}
575
576impl<M> FromIterator<M> for ModuleInitRegistry<M>
577where
578    M: AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
579{
580    fn from_iter<T: IntoIterator<Item = M>>(iter: T) -> Self {
581        Self(
582            iter.into_iter()
583                .map(|i| (i.as_ref().module_kind(), i))
584                .collect::<BTreeMap<_, _>>(),
585        )
586    }
587}
588
589impl<M> ModuleInitRegistry<M> {
590    pub fn new() -> Self {
591        Self::default()
592    }
593
594    pub fn attach<T>(&mut self, r#gen: T)
595    where
596        T: Into<M> + 'static + Send + Sync,
597        M: AsRef<dyn IDynCommonModuleInit + 'static + Send + Sync>,
598    {
599        let r#gen: M = r#gen.into();
600        let kind = r#gen.as_ref().module_kind();
601        assert!(
602            self.0.insert(kind.clone(), r#gen).is_none(),
603            "Can't insert module of same kind twice: {kind}"
604        );
605    }
606
607    pub fn kinds(&self) -> BTreeSet<ModuleKind> {
608        self.0.keys().cloned().collect()
609    }
610
611    pub fn get(&self, k: &ModuleKind) -> Option<&M> {
612        self.0.get(k)
613    }
614}
615
616impl ModuleRegistry<ConfigGenModuleParams> {
617    pub fn attach_config_gen_params_by_id<T: ModuleInitParams>(
618        &mut self,
619        id: ModuleInstanceId,
620        kind: ModuleKind,
621        r#gen: T,
622    ) -> &mut Self {
623        let params = ConfigGenModuleParams::from_typed(r#gen)
624            .unwrap_or_else(|err| panic!("Invalid config gen params for {kind}: {err}"));
625        self.register_module(id, kind, params);
626        self
627    }
628
629    pub fn attach_config_gen_params<T: ModuleInitParams>(
630        &mut self,
631        kind: ModuleKind,
632        r#gen: T,
633    ) -> &mut Self {
634        let params = ConfigGenModuleParams::from_typed(r#gen)
635            .unwrap_or_else(|err| panic!("Invalid config gen params for {kind}: {err}"));
636        self.append_module(kind, params);
637        self
638    }
639}
640
641impl<M> ModuleInitRegistry<M>
642where
643    M: AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static>,
644{
645    #[deprecated(
646        note = "You probably want `available_decoders` to support missing module kinds. If you really want a strict behavior, use `decoders_strict`"
647    )]
648    pub fn decoders<'a>(
649        &self,
650        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
651    ) -> anyhow::Result<ModuleDecoderRegistry> {
652        self.decoders_strict(modules)
653    }
654
655    /// Get decoders for `modules` and fail if any is unsupported
656    pub fn decoders_strict<'a>(
657        &self,
658        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
659    ) -> anyhow::Result<ModuleDecoderRegistry> {
660        let mut decoders = BTreeMap::new();
661        for (id, kind) in modules {
662            let Some(init) = self.0.get(kind) else {
663                anyhow::bail!(
664                    "Detected configuration for unsupported module id: {id}, kind: {kind}"
665                )
666            };
667
668            decoders.insert(id, (kind.clone(), init.as_ref().decoder()));
669        }
670        Ok(ModuleDecoderRegistry::from(decoders))
671    }
672
673    /// Get decoders for `modules` and skip unsupported ones
674    pub fn available_decoders<'a>(
675        &self,
676        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
677    ) -> anyhow::Result<ModuleDecoderRegistry> {
678        let mut decoders = BTreeMap::new();
679        for (id, kind) in modules {
680            let Some(init) = self.0.get(kind) else {
681                warn!(target: LOG_CORE, "Unsupported module id: {id}, kind: {kind}");
682                continue;
683            };
684
685            decoders.insert(id, (kind.clone(), init.as_ref().decoder()));
686        }
687        Ok(ModuleDecoderRegistry::from(decoders))
688    }
689}
690
691/// Empty struct for if there are no params
692#[derive(Debug, Default, Clone, Serialize, Deserialize)]
693pub struct EmptyGenParams {}
694
695pub trait ModuleInitParams: serde::Serialize + serde::de::DeserializeOwned {
696    /// Locally configurable parameters for config generation
697    type Local: DeserializeOwned + Serialize;
698    /// Consensus parameters for config generation
699    type Consensus: DeserializeOwned + Serialize;
700
701    /// Assemble from the distinct parts
702    fn from_parts(local: Self::Local, consensus: Self::Consensus) -> Self;
703
704    /// Split the config into its distinct parts
705    fn to_parts(self) -> (Self::Local, Self::Consensus);
706}
707
708#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
709pub struct ServerModuleConsensusConfig {
710    pub kind: ModuleKind,
711    pub version: ModuleConsensusVersion,
712    #[serde(with = "::hex::serde")]
713    pub config: Vec<u8>,
714}
715
716/// Config for the client-side of a particular Federation module
717///
718/// Since modules are (tbd.) pluggable into Federations,
719/// it needs to be some form of an abstract type-erased-like
720/// value.
721#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
722pub struct ClientModuleConfig {
723    pub kind: ModuleKind,
724    pub version: ModuleConsensusVersion,
725    #[serde(with = "::fedimint_core::encoding::as_hex")]
726    pub config: DynRawFallback<DynClientConfig>,
727}
728
729impl ClientModuleConfig {
730    pub fn from_typed<T: fedimint_core::core::ClientConfig>(
731        module_instance_id: ModuleInstanceId,
732        kind: ModuleKind,
733        version: ModuleConsensusVersion,
734        value: T,
735    ) -> anyhow::Result<Self> {
736        Ok(Self {
737            kind,
738            version,
739            config: fedimint_core::core::DynClientConfig::from_typed(module_instance_id, value)
740                .into(),
741        })
742    }
743
744    pub fn redecode_raw(
745        self,
746        modules: &ModuleDecoderRegistry,
747    ) -> Result<Self, crate::encoding::DecodeError> {
748        Ok(Self {
749            config: self.config.redecode_raw(modules)?,
750            ..self
751        })
752    }
753
754    pub fn is_kind(&self, kind: &ModuleKind) -> bool {
755        &self.kind == kind
756    }
757
758    pub fn kind(&self) -> &ModuleKind {
759        &self.kind
760    }
761}
762
763impl ClientModuleConfig {
764    pub fn cast<T>(&self) -> anyhow::Result<&T>
765    where
766        T: 'static,
767    {
768        self.config
769            .expect_decoded_ref()
770            .as_any()
771            .downcast_ref::<T>()
772            .context("can't convert client module config to desired type")
773    }
774}
775
776/// Config for the server-side of a particular Federation module
777///
778/// See [`ClientModuleConfig`].
779#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
780pub struct ServerModuleConfig {
781    pub private: JsonWithKind,
782    pub consensus: ServerModuleConsensusConfig,
783}
784
785impl ServerModuleConfig {
786    pub fn from(private: JsonWithKind, consensus: ServerModuleConsensusConfig) -> Self {
787        Self { private, consensus }
788    }
789
790    pub fn to_typed<T: TypedServerModuleConfig>(&self) -> anyhow::Result<T> {
791        let private = serde_json::from_value(self.private.value().clone())?;
792        let consensus = <T::Consensus>::consensus_decode_whole(
793            &self.consensus.config[..],
794            &ModuleRegistry::default(),
795        )?;
796
797        Ok(TypedServerModuleConfig::from_parts(private, consensus))
798    }
799}
800
801/// Consensus-critical part of a server side module config
802pub trait TypedServerModuleConsensusConfig:
803    DeserializeOwned + Serialize + Encodable + Decodable
804{
805    fn kind(&self) -> ModuleKind;
806
807    fn version(&self) -> ModuleConsensusVersion;
808
809    fn from_erased(erased: &ServerModuleConsensusConfig) -> anyhow::Result<Self> {
810        Ok(Self::consensus_decode_whole(
811            &erased.config[..],
812            &ModuleRegistry::default(),
813        )?)
814    }
815}
816
817/// Module (server side) config, typed
818pub trait TypedServerModuleConfig: DeserializeOwned + Serialize {
819    /// Private for this federation member data that are security sensitive and
820    /// will be encrypted at rest
821    type Private: DeserializeOwned + Serialize;
822    /// Shared consensus-critical config
823    type Consensus: TypedServerModuleConsensusConfig;
824
825    /// Assemble from the three functionally distinct parts
826    fn from_parts(private: Self::Private, consensus: Self::Consensus) -> Self;
827
828    /// Split the config into its two functionally distinct parts
829    fn to_parts(self) -> (ModuleKind, Self::Private, Self::Consensus);
830
831    /// Turn the typed config into type-erased version
832    fn to_erased(self) -> ServerModuleConfig {
833        let (kind, private, consensus) = self.to_parts();
834
835        ServerModuleConfig {
836            private: JsonWithKind::new(
837                kind,
838                serde_json::to_value(private).expect("serialization can't fail"),
839            ),
840            consensus: ServerModuleConsensusConfig {
841                kind: consensus.kind(),
842                version: consensus.version(),
843                config: consensus.consensus_encode_to_vec(),
844            },
845        }
846    }
847}
848
849#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encodable, Decodable)]
850pub enum P2PMessage {
851    Aleph(Vec<u8>),
852    Checksum(sha256::Hash),
853    Dkg(DkgMessage),
854    Encodable(Vec<u8>),
855}
856
857#[derive(Debug, PartialEq, Eq, Clone, Encodable, Decodable)]
858pub enum DkgMessage {
859    Hash(sha256::Hash),
860    Commitment(Vec<(G1Projective, G2Projective)>),
861    Share(Scalar),
862}
863
864// TODO: Remove the Serde encoding as soon as the p2p layer drops it as
865// requirement
866impl Serialize for DkgMessage {
867    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
868    where
869        S: Serializer,
870    {
871        self.consensus_encode_to_hex().serialize(serializer)
872    }
873}
874
875impl<'de> Deserialize<'de> for DkgMessage {
876    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
877    where
878        D: Deserializer<'de>,
879    {
880        Self::consensus_decode_hex(
881            &String::deserialize(deserializer)?,
882            &ModuleDecoderRegistry::default(),
883        )
884        .map_err(serde::de::Error::custom)
885    }
886}
887
888/// Key under which the federation name can be sent to client in the `meta` part
889/// of the config
890pub const META_FEDERATION_NAME_KEY: &str = "federation_name";
891
892pub fn load_from_file<T: DeserializeOwned>(path: &Path) -> Result<T, anyhow::Error> {
893    let file = std::fs::File::open(path)?;
894    Ok(serde_json::from_reader(file)?)
895}
896
897#[cfg(test)]
898mod tests;