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            if o.is_empty() {
77                return Self {
78                    kind: self.kind,
79                    value: serde_json::Value::Null,
80                };
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(|(k, 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                    v.redecode_raw(modules)
237                        .context(format!("redecode_raw: instance: {k}, kind: {kind}"))
238                        .map(|v| (k, v))
239                })
240                .collect::<Result<_, _>>()?,
241            ..self
242        })
243    }
244
245    pub fn calculate_federation_id(&self) -> FederationId {
246        self.global.calculate_federation_id()
247    }
248
249    /// Get the value of a given meta field
250    pub fn meta<V: serde::de::DeserializeOwned + 'static>(
251        &self,
252        key: &str,
253    ) -> Result<Option<V>, anyhow::Error> {
254        let Some(str_value) = self.global.meta.get(key) else {
255            return Ok(None);
256        };
257        let res = serde_json::from_str(str_value)
258            .map(Some)
259            .context(format!("Decoding meta field '{key}' failed"));
260
261        // In the past we encoded some string fields as "just a string" without quotes,
262        // this code ensures that old meta values still parse since config is hard to
263        // change
264        if res.is_err() && std::any::TypeId::of::<V>() == std::any::TypeId::of::<String>() {
265            let string_ret = Box::new(str_value.clone());
266            let ret = unsafe {
267                // We can transmute a String to V because we know that V==String
268                std::mem::transmute::<Box<String>, Box<V>>(string_ret)
269            };
270            Ok(Some(*ret))
271        } else {
272            res
273        }
274    }
275
276    /// Converts a consensus-encoded client config struct to a client config
277    /// struct that when encoded as JSON shows the fields of module configs
278    /// instead of a consensus-encoded hex string.
279    ///
280    /// In case of unknown module the config value is a hex string.
281    pub fn to_json(&self) -> JsonClientConfig {
282        JsonClientConfig {
283            global: self.global.clone(),
284            modules: self
285                .modules
286                .iter()
287                .map(|(&module_instance_id, module_config)| {
288                    let module_config_json = JsonWithKind {
289                        kind: module_config.kind.clone(),
290                        value: module_config.config
291                            .clone()
292                            .decoded()
293                            .and_then(|dyn_cfg| dyn_cfg.to_json())
294                            .unwrap_or_else(|| json!({
295                            "unknown_module_hex": module_config.config.consensus_encode_to_hex()
296                        })),
297                    };
298                    (module_instance_id, module_config_json)
299                })
300                .collect(),
301        }
302    }
303}
304
305/// The federation id is a copy of the authentication threshold public key of
306/// the federation
307///
308/// Stable id so long as guardians membership does not change
309/// Unique id so long as guardians do not all collude
310#[derive(
311    Debug,
312    Copy,
313    Serialize,
314    Deserialize,
315    Clone,
316    Eq,
317    Hash,
318    PartialEq,
319    Encodable,
320    Decodable,
321    Ord,
322    PartialOrd,
323)]
324pub struct FederationId(pub sha256::Hash);
325
326#[derive(
327    Debug,
328    Copy,
329    Serialize,
330    Deserialize,
331    Clone,
332    Eq,
333    Hash,
334    PartialEq,
335    Encodable,
336    Decodable,
337    Ord,
338    PartialOrd,
339)]
340/// Prefix of the [`FederationId`], useful for UX improvements
341///
342/// Intentionally compact to save on the encoding. With 4 billion
343/// combinations real-life non-malicious collisions should never
344/// happen.
345pub struct FederationIdPrefix([u8; 4]);
346
347impl Display for FederationIdPrefix {
348    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
349        format_hex(&self.0, f)
350    }
351}
352
353impl Display for FederationId {
354    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
355        format_hex(&self.0.to_byte_array(), f)
356    }
357}
358
359impl FromStr for FederationIdPrefix {
360    type Err = anyhow::Error;
361
362    fn from_str(s: &str) -> Result<Self, Self::Err> {
363        Ok(Self(<[u8; 4]>::from_hex(s)?))
364    }
365}
366
367impl FederationIdPrefix {
368    pub fn to_bytes(&self) -> Vec<u8> {
369        self.0.to_vec()
370    }
371}
372
373/// Display as a hex encoding
374impl FederationId {
375    /// Random dummy id for testing
376    pub fn dummy() -> Self {
377        Self(sha256::Hash::from_byte_array([42; 32]))
378    }
379
380    pub(crate) fn from_byte_array(bytes: [u8; 32]) -> Self {
381        Self(sha256::Hash::from_byte_array(bytes))
382    }
383
384    pub fn to_prefix(&self) -> FederationIdPrefix {
385        FederationIdPrefix(self.0[..4].try_into().expect("can't fail"))
386    }
387
388    /// Converts a federation id to a public key to which we know but discard
389    /// the private key.
390    ///
391    /// Clients MUST never use this private key for any signing operations!
392    ///
393    /// That is ok because we only use the public key for adding a route
394    /// hint to LN invoices that tells fedimint clients that the invoice can
395    /// only be paid internally. Since no LN node with that pub key can exist
396    /// other LN senders will know that they cannot pay the invoice.
397    pub fn to_fake_ln_pub_key(
398        &self,
399        secp: &bitcoin::secp256k1::Secp256k1<bitcoin::secp256k1::All>,
400    ) -> anyhow::Result<bitcoin::secp256k1::PublicKey> {
401        let sk = bitcoin::secp256k1::SecretKey::from_slice(&self.0.to_byte_array())?;
402        Ok(bitcoin::secp256k1::PublicKey::from_secret_key(secp, &sk))
403    }
404}
405
406impl FromStr for FederationId {
407    type Err = anyhow::Error;
408
409    fn from_str(s: &str) -> Result<Self, Self::Err> {
410        Ok(Self::from_byte_array(<[u8; 32]>::from_hex(s)?))
411    }
412}
413
414impl ClientConfig {
415    /// Returns the consensus hash for a given client config
416    pub fn consensus_hash(&self) -> sha256::Hash {
417        let mut engine = HashEngine::default();
418        self.consensus_encode(&mut engine)
419            .expect("Consensus hashing should never fail");
420        sha256::Hash::from_engine(engine)
421    }
422
423    pub fn get_module<T: Decodable + 'static>(&self, id: ModuleInstanceId) -> anyhow::Result<&T> {
424        self.modules.get(&id).map_or_else(
425            || Err(format_err!("Client config for module id {id} not found")),
426            |client_cfg| client_cfg.cast(),
427        )
428    }
429
430    // TODO: rename this and one above
431    pub fn get_module_cfg(&self, id: ModuleInstanceId) -> anyhow::Result<ClientModuleConfig> {
432        self.modules.get(&id).map_or_else(
433            || Err(format_err!("Client config for module id {id} not found")),
434            |client_cfg| Ok(client_cfg.clone()),
435        )
436    }
437
438    /// (soft-deprecated): Get the first instance of a module of a given kind in
439    /// defined in config
440    ///
441    /// Since module ids are numerical and for time being we only support 1:1
442    /// mint, wallet, ln module code in the client, this is useful, but
443    /// please write any new code that avoids assumptions about available
444    /// modules.
445    pub fn get_first_module_by_kind<T: Decodable + 'static>(
446        &self,
447        kind: impl Into<ModuleKind>,
448    ) -> anyhow::Result<(ModuleInstanceId, &T)> {
449        let kind: ModuleKind = kind.into();
450        let Some((id, module_cfg)) = self.modules.iter().find(|(_, v)| v.is_kind(&kind)) else {
451            anyhow::bail!("Module kind {kind} not found")
452        };
453        Ok((*id, module_cfg.cast()?))
454    }
455
456    // TODO: rename this and above
457    pub fn get_first_module_by_kind_cfg(
458        &self,
459        kind: impl Into<ModuleKind>,
460    ) -> anyhow::Result<(ModuleInstanceId, ClientModuleConfig)> {
461        let kind: ModuleKind = kind.into();
462        self.modules
463            .iter()
464            .find(|(_, v)| v.is_kind(&kind))
465            .map(|(id, v)| (*id, v.clone()))
466            .ok_or_else(|| anyhow::format_err!("Module kind {kind} not found"))
467    }
468}
469
470#[derive(Clone, Debug)]
471pub struct ModuleInitRegistry<M>(BTreeMap<ModuleKind, M>);
472
473impl<M> ModuleInitRegistry<M> {
474    pub fn iter(&self) -> impl Iterator<Item = (&ModuleKind, &M)> {
475        self.0.iter()
476    }
477}
478
479impl<M> Default for ModuleInitRegistry<M> {
480    fn default() -> Self {
481        Self(BTreeMap::new())
482    }
483}
484
485/// Type erased `ModuleInitParams` used to generate the `ServerModuleConfig`
486/// during config gen
487#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
488pub struct ConfigGenModuleParams {
489    pub local: serde_json::Value,
490    pub consensus: serde_json::Value,
491}
492
493impl ConfigGenModuleParams {
494    pub fn new(local: serde_json::Value, consensus: serde_json::Value) -> Self {
495        Self { local, consensus }
496    }
497
498    /// Converts the JSON into typed version, errors unless both `local` and
499    /// `consensus` values are defined
500    pub fn to_typed<P: ModuleInitParams>(&self) -> anyhow::Result<P> {
501        Ok(P::from_parts(
502            Self::parse("local", self.local.clone())?,
503            Self::parse("consensus", self.consensus.clone())?,
504        ))
505    }
506
507    fn parse<P: DeserializeOwned>(name: &str, json: serde_json::Value) -> anyhow::Result<P> {
508        serde_json::from_value(json).with_context(|| format!("Schema mismatch for {name} argument"))
509    }
510
511    pub fn from_typed<P: ModuleInitParams>(p: P) -> anyhow::Result<Self> {
512        let (local, consensus) = p.to_parts();
513        Ok(Self {
514            local: serde_json::to_value(local)?,
515            consensus: serde_json::to_value(consensus)?,
516        })
517    }
518}
519
520pub type CommonModuleInitRegistry = ModuleInitRegistry<DynCommonModuleInit>;
521
522/// Registry that contains the config gen params for all modules
523pub type ServerModuleConfigGenParamsRegistry = ModuleRegistry<ConfigGenModuleParams>;
524
525impl Eq for ServerModuleConfigGenParamsRegistry {}
526
527impl PartialEq for ServerModuleConfigGenParamsRegistry {
528    fn eq(&self, other: &Self) -> bool {
529        self.iter_modules().eq(other.iter_modules())
530    }
531}
532
533impl Serialize for ServerModuleConfigGenParamsRegistry {
534    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
535        let modules: Vec<_> = self.iter_modules().collect();
536        let mut serializer = serializer.serialize_map(Some(modules.len()))?;
537        for (id, kind, params) in modules {
538            serializer.serialize_key(&id)?;
539            serializer.serialize_value(&(kind.clone(), params.clone()))?;
540        }
541        serializer.end()
542    }
543}
544
545impl<'de> Deserialize<'de> for ServerModuleConfigGenParamsRegistry {
546    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
547    where
548        D: Deserializer<'de>,
549    {
550        let json: BTreeMap<ModuleInstanceId, (ModuleKind, ConfigGenModuleParams)> =
551            Deserialize::deserialize(deserializer)?;
552        let mut params = BTreeMap::new();
553
554        for (id, (kind, module)) in json {
555            params.insert(id, (kind, module));
556        }
557        Ok(Self::from(params))
558    }
559}
560
561impl<M> From<Vec<M>> for ModuleInitRegistry<M>
562where
563    M: AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static>,
564{
565    fn from(value: Vec<M>) -> Self {
566        Self(
567            value
568                .into_iter()
569                .map(|i| (i.as_ref().module_kind(), i))
570                .collect::<BTreeMap<_, _>>(),
571        )
572    }
573}
574
575impl<M> FromIterator<M> for ModuleInitRegistry<M>
576where
577    M: AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
578{
579    fn from_iter<T: IntoIterator<Item = M>>(iter: T) -> Self {
580        Self(
581            iter.into_iter()
582                .map(|i| (i.as_ref().module_kind(), i))
583                .collect::<BTreeMap<_, _>>(),
584        )
585    }
586}
587
588impl<M> ModuleInitRegistry<M> {
589    pub fn new() -> Self {
590        Self::default()
591    }
592
593    pub fn attach<T>(&mut self, r#gen: T)
594    where
595        T: Into<M> + 'static + Send + Sync,
596        M: AsRef<dyn IDynCommonModuleInit + 'static + Send + Sync>,
597    {
598        let r#gen: M = r#gen.into();
599        let kind = r#gen.as_ref().module_kind();
600        assert!(
601            self.0.insert(kind.clone(), r#gen).is_none(),
602            "Can't insert module of same kind twice: {kind}"
603        );
604    }
605
606    pub fn kinds(&self) -> BTreeSet<ModuleKind> {
607        self.0.keys().cloned().collect()
608    }
609
610    pub fn get(&self, k: &ModuleKind) -> Option<&M> {
611        self.0.get(k)
612    }
613}
614
615impl ModuleRegistry<ConfigGenModuleParams> {
616    pub fn attach_config_gen_params_by_id<T: ModuleInitParams>(
617        &mut self,
618        id: ModuleInstanceId,
619        kind: ModuleKind,
620        r#gen: T,
621    ) -> &mut Self {
622        let params = ConfigGenModuleParams::from_typed(r#gen)
623            .unwrap_or_else(|err| panic!("Invalid config gen params for {kind}: {err}"));
624        self.register_module(id, kind, params);
625        self
626    }
627
628    pub fn attach_config_gen_params<T: ModuleInitParams>(
629        &mut self,
630        kind: ModuleKind,
631        r#gen: T,
632    ) -> &mut Self {
633        let params = ConfigGenModuleParams::from_typed(r#gen)
634            .unwrap_or_else(|err| panic!("Invalid config gen params for {kind}: {err}"));
635        self.append_module(kind, params);
636        self
637    }
638}
639
640impl<M> ModuleInitRegistry<M>
641where
642    M: AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static>,
643{
644    #[deprecated(
645        note = "You probably want `available_decoders` to support missing module kinds. If you really want a strict behavior, use `decoders_strict`"
646    )]
647    pub fn decoders<'a>(
648        &self,
649        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
650    ) -> anyhow::Result<ModuleDecoderRegistry> {
651        self.decoders_strict(modules)
652    }
653
654    /// Get decoders for `modules` and fail if any is unsupported
655    pub fn decoders_strict<'a>(
656        &self,
657        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
658    ) -> anyhow::Result<ModuleDecoderRegistry> {
659        let mut decoders = BTreeMap::new();
660        for (id, kind) in modules {
661            let Some(init) = self.0.get(kind) else {
662                anyhow::bail!(
663                    "Detected configuration for unsupported module id: {id}, kind: {kind}"
664                )
665            };
666
667            decoders.insert(id, (kind.clone(), init.as_ref().decoder()));
668        }
669        Ok(ModuleDecoderRegistry::from(decoders))
670    }
671
672    /// Get decoders for `modules` and skip unsupported ones
673    pub fn available_decoders<'a>(
674        &self,
675        modules: impl Iterator<Item = (ModuleInstanceId, &'a ModuleKind)>,
676    ) -> anyhow::Result<ModuleDecoderRegistry> {
677        let mut decoders = BTreeMap::new();
678        for (id, kind) in modules {
679            let Some(init) = self.0.get(kind) else {
680                warn!(target: LOG_CORE, "Unsupported module id: {id}, kind: {kind}");
681                continue;
682            };
683
684            decoders.insert(id, (kind.clone(), init.as_ref().decoder()));
685        }
686        Ok(ModuleDecoderRegistry::from(decoders))
687    }
688}
689
690/// Empty struct for if there are no params
691#[derive(Debug, Default, Clone, Serialize, Deserialize)]
692pub struct EmptyGenParams {}
693
694pub trait ModuleInitParams: serde::Serialize + serde::de::DeserializeOwned {
695    /// Locally configurable parameters for config generation
696    type Local: DeserializeOwned + Serialize;
697    /// Consensus parameters for config generation
698    type Consensus: DeserializeOwned + Serialize;
699
700    /// Assemble from the distinct parts
701    fn from_parts(local: Self::Local, consensus: Self::Consensus) -> Self;
702
703    /// Split the config into its distinct parts
704    fn to_parts(self) -> (Self::Local, Self::Consensus);
705}
706
707#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
708pub struct ServerModuleConsensusConfig {
709    pub kind: ModuleKind,
710    pub version: ModuleConsensusVersion,
711    #[serde(with = "::hex::serde")]
712    pub config: Vec<u8>,
713}
714
715/// Config for the client-side of a particular Federation module
716///
717/// Since modules are (tbd.) pluggable into Federations,
718/// it needs to be some form of an abstract type-erased-like
719/// value.
720#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
721pub struct ClientModuleConfig {
722    pub kind: ModuleKind,
723    pub version: ModuleConsensusVersion,
724    #[serde(with = "::fedimint_core::encoding::as_hex")]
725    pub config: DynRawFallback<DynClientConfig>,
726}
727
728impl ClientModuleConfig {
729    pub fn from_typed<T: fedimint_core::core::ClientConfig>(
730        module_instance_id: ModuleInstanceId,
731        kind: ModuleKind,
732        version: ModuleConsensusVersion,
733        value: T,
734    ) -> anyhow::Result<Self> {
735        Ok(Self {
736            kind,
737            version,
738            config: fedimint_core::core::DynClientConfig::from_typed(module_instance_id, value)
739                .into(),
740        })
741    }
742
743    pub fn redecode_raw(
744        self,
745        modules: &ModuleDecoderRegistry,
746    ) -> Result<Self, crate::encoding::DecodeError> {
747        Ok(Self {
748            config: self.config.redecode_raw(modules)?,
749            ..self
750        })
751    }
752
753    pub fn is_kind(&self, kind: &ModuleKind) -> bool {
754        &self.kind == kind
755    }
756
757    pub fn kind(&self) -> &ModuleKind {
758        &self.kind
759    }
760}
761
762impl ClientModuleConfig {
763    pub fn cast<T>(&self) -> anyhow::Result<&T>
764    where
765        T: 'static,
766    {
767        self.config
768            .expect_decoded_ref()
769            .as_any()
770            .downcast_ref::<T>()
771            .context("can't convert client module config to desired type")
772    }
773}
774
775/// Config for the server-side of a particular Federation module
776///
777/// See [`ClientModuleConfig`].
778#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
779pub struct ServerModuleConfig {
780    pub local: JsonWithKind,
781    pub private: JsonWithKind,
782    pub consensus: ServerModuleConsensusConfig,
783}
784
785impl ServerModuleConfig {
786    pub fn from(
787        local: JsonWithKind,
788        private: JsonWithKind,
789        consensus: ServerModuleConsensusConfig,
790    ) -> Self {
791        Self {
792            local,
793            private,
794            consensus,
795        }
796    }
797
798    pub fn to_typed<T: TypedServerModuleConfig>(&self) -> anyhow::Result<T> {
799        let local = serde_json::from_value(self.local.value().clone())?;
800        let private = serde_json::from_value(self.private.value().clone())?;
801        let consensus = <T::Consensus>::consensus_decode_whole(
802            &self.consensus.config[..],
803            &ModuleRegistry::default(),
804        )?;
805
806        Ok(TypedServerModuleConfig::from_parts(
807            local, private, consensus,
808        ))
809    }
810}
811
812/// Consensus-critical part of a server side module config
813pub trait TypedServerModuleConsensusConfig:
814    DeserializeOwned + Serialize + Encodable + Decodable
815{
816    fn kind(&self) -> ModuleKind;
817
818    fn version(&self) -> ModuleConsensusVersion;
819
820    fn from_erased(erased: &ServerModuleConsensusConfig) -> anyhow::Result<Self> {
821        Ok(Self::consensus_decode_whole(
822            &erased.config[..],
823            &ModuleRegistry::default(),
824        )?)
825    }
826}
827
828/// Module (server side) config, typed
829pub trait TypedServerModuleConfig: DeserializeOwned + Serialize {
830    /// Local non-consensus, not security-sensitive settings
831    type Local: DeserializeOwned + Serialize;
832    /// Private for this federation member data that are security sensitive and
833    /// will be encrypted at rest
834    type Private: DeserializeOwned + Serialize;
835    /// Shared consensus-critical config
836    type Consensus: TypedServerModuleConsensusConfig;
837
838    /// Assemble from the three functionally distinct parts
839    fn from_parts(local: Self::Local, private: Self::Private, consensus: Self::Consensus) -> Self;
840
841    /// Split the config into its three functionally distinct parts
842    fn to_parts(self) -> (ModuleKind, Self::Local, Self::Private, Self::Consensus);
843
844    /// Turn the typed config into type-erased version
845    fn to_erased(self) -> ServerModuleConfig {
846        let (kind, local, private, consensus) = self.to_parts();
847
848        ServerModuleConfig {
849            local: JsonWithKind::new(
850                kind.clone(),
851                serde_json::to_value(local).expect("serialization can't fail"),
852            ),
853            private: JsonWithKind::new(
854                kind,
855                serde_json::to_value(private).expect("serialization can't fail"),
856            ),
857            consensus: ServerModuleConsensusConfig {
858                kind: consensus.kind(),
859                version: consensus.version(),
860                config: consensus.consensus_encode_to_vec(),
861            },
862        }
863    }
864}
865
866#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encodable, Decodable)]
867pub enum P2PMessage {
868    Aleph(Vec<u8>),
869    Checksum(sha256::Hash),
870    Dkg(DkgMessage),
871    Encodable(Vec<u8>),
872}
873
874#[derive(Debug, PartialEq, Eq, Clone, Encodable, Decodable)]
875pub enum DkgMessage {
876    Hash(sha256::Hash),
877    Commitment(Vec<(G1Projective, G2Projective)>),
878    Share(Scalar),
879}
880
881// TODO: Remove the Serde encoding as soon as the p2p layer drops it as
882// requirement
883impl Serialize for DkgMessage {
884    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
885    where
886        S: Serializer,
887    {
888        self.consensus_encode_to_hex().serialize(serializer)
889    }
890}
891
892impl<'de> Deserialize<'de> for DkgMessage {
893    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
894    where
895        D: Deserializer<'de>,
896    {
897        Self::consensus_decode_hex(
898            &String::deserialize(deserializer)?,
899            &ModuleDecoderRegistry::default(),
900        )
901        .map_err(serde::de::Error::custom)
902    }
903}
904
905/// Key under which the federation name can be sent to client in the `meta` part
906/// of the config
907pub const META_FEDERATION_NAME_KEY: &str = "federation_name";
908
909pub fn load_from_file<T: DeserializeOwned>(path: &Path) -> Result<T, anyhow::Error> {
910    let file = std::fs::File::open(path)?;
911    Ok(serde_json::from_reader(file)?)
912}
913
914pub mod serde_binary_human_readable {
915    use std::borrow::Cow;
916
917    use hex::{FromHex, ToHex};
918    use serde::de::DeserializeOwned;
919    use serde::{Deserialize, Deserializer, Serialize, Serializer};
920
921    pub fn serialize<T: Serialize, S: Serializer>(x: &T, s: S) -> Result<S::Ok, S::Error> {
922        if s.is_human_readable() {
923            let bytes =
924                bincode::serialize(x).map_err(|e| serde::ser::Error::custom(format!("{e:?}")))?;
925            s.serialize_str(&bytes.encode_hex::<String>())
926        } else {
927            Serialize::serialize(x, s)
928        }
929    }
930
931    pub fn deserialize<'d, T: DeserializeOwned, D: Deserializer<'d>>(d: D) -> Result<T, D::Error> {
932        if d.is_human_readable() {
933            let hex_str: Cow<str> = Deserialize::deserialize(d)?;
934            let bytes = Vec::from_hex(hex_str.as_ref()).map_err(serde::de::Error::custom)?;
935            bincode::deserialize(&bytes).map_err(|e| serde::de::Error::custom(format!("{e:?}")))
936        } else {
937            Deserialize::deserialize(d)
938        }
939    }
940}
941
942#[cfg(test)]
943mod tests;