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