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