fedimint_core/module/
mod.rs

1//! Core module system traits and types.
2//!
3//! Fedimint supports modules to allow extending its functionality.
4//! Some of the standard functionality is implemented in form of modules as
5//! well. This rust module houses the core trait
6//! [`fedimint_core::module::ModuleCommon`] used by both the server and client
7//! side module traits. Specific server and client traits exist in their
8//! respective crates.
9//!
10//! The top level server-side types are:
11//!
12//! * `fedimint_server::core::ServerModuleInit`
13//! * `fedimint_server::core::ServerModule`
14//!
15//! Top level client-side types are:
16//!
17//! * `ClientModuleInit` (in `fedimint_client`)
18//! * `ClientModule` (in `fedimint_client`)
19pub mod audit;
20pub mod registry;
21
22use std::collections::{BTreeMap, BTreeSet};
23use std::error::Error;
24use std::fmt::{self, Debug, Formatter};
25use std::marker::PhantomData;
26use std::ops;
27use std::pin::Pin;
28use std::sync::Arc;
29use std::sync::atomic::{AtomicU64, Ordering};
30
31use fedimint_logging::LOG_NET_API;
32use fedimint_util_error::FmtCompactAnyhow as _;
33use futures::Future;
34use jsonrpsee_core::JsonValue;
35use registry::ModuleRegistry;
36use serde::{Deserialize, Serialize};
37use tracing::Instrument;
38
39// TODO: Make this module public and remove theDkgPeerMessage`pub use` below
40mod version;
41pub use self::version::*;
42use crate::core::{
43    ClientConfig, Decoder, DecoderBuilder, Input, InputError, ModuleConsensusItem,
44    ModuleInstanceId, ModuleKind, Output, OutputError, OutputOutcome,
45};
46use crate::db::{
47    Committable, Database, DatabaseKey, DatabaseKeyWithNotify, DatabaseRecord, DatabaseTransaction,
48    NonCommittable,
49};
50use crate::encoding::{Decodable, DecodeError, Encodable};
51use crate::fmt_utils::AbbreviateHexBytes;
52use crate::task::MaybeSend;
53use crate::util::FmtCompact;
54use crate::{Amount, apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
55
56#[derive(Debug, PartialEq, Eq)]
57pub struct InputMeta {
58    pub amount: TransactionItemAmounts,
59    pub pub_key: secp256k1::PublicKey,
60}
61
62/// Unit of account for a given amount.
63#[derive(
64    Debug,
65    Clone,
66    Copy,
67    Eq,
68    PartialEq,
69    Hash,
70    PartialOrd,
71    Ord,
72    Deserialize,
73    Serialize,
74    Encodable,
75    Decodable,
76    Default,
77)]
78pub struct AmountUnit(u64);
79
80impl AmountUnit {
81    /// [`AmountUnit`] with id `0` is reserved for the native Bitcoin currency.
82    /// So e.g. for a mainnet Federation it's a real Bitcoin (msats), for a
83    /// signet one it's a Signet msats, etc.
84    pub const BITCOIN: Self = Self(0);
85
86    pub fn is_bitcoin(self) -> bool {
87        self == Self::BITCOIN
88    }
89
90    pub fn new_custom(unit: u64) -> Self {
91        Self(unit)
92    }
93
94    pub const fn bitcoin() -> Self {
95        Self::BITCOIN
96    }
97}
98
99#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
100pub struct AmountWithUnit {
101    amounts: Amount,
102    unit: AmountUnit,
103}
104
105/// Multi-unit amount
106///
107/// Basically (potentially) multiple amounts, each of different unit.
108///
109/// Note: implementation must be careful not to add zero-amount
110/// entries, as these could mess up equality comparisons, etc.
111#[derive(Debug, Clone, Eq, PartialEq, Hash)]
112pub struct Amounts(BTreeMap<AmountUnit, Amount>);
113
114// Note: no `impl ops::DerefMut` as it could easily accidentally break the
115// invariant
116impl ops::Deref for Amounts {
117    type Target = BTreeMap<AmountUnit, Amount>;
118
119    fn deref(&self) -> &Self::Target {
120        &self.0
121    }
122}
123
124impl Amounts {
125    pub const ZERO: Self = Self(BTreeMap::new());
126
127    pub fn new_bitcoin(amount: Amount) -> Self {
128        if amount == Amount::ZERO {
129            Self(BTreeMap::from([]))
130        } else {
131            Self(BTreeMap::from([(AmountUnit::BITCOIN, amount)]))
132        }
133    }
134
135    pub fn new_bitcoin_msats(msats: u64) -> Self {
136        Self::new_bitcoin(Amount::from_msats(msats))
137    }
138
139    pub fn new_custom(unit: AmountUnit, amount: Amount) -> Self {
140        if amount == Amount::ZERO {
141            Self(BTreeMap::from([]))
142        } else {
143            Self(BTreeMap::from([(unit, amount)]))
144        }
145    }
146
147    pub fn checked_add(mut self, rhs: &Self) -> Option<Self> {
148        self.checked_add_mut(rhs);
149
150        Some(self)
151    }
152
153    pub fn checked_add_mut(&mut self, rhs: &Self) -> Option<&mut Self> {
154        for (unit, amount) in &rhs.0 {
155            debug_assert!(
156                *amount != Amount::ZERO,
157                "`Amounts` must not add (/remove) zero-amount entries"
158            );
159            let prev = self.0.entry(*unit).or_default();
160
161            *prev = prev.checked_add(*amount)?;
162        }
163
164        Some(self)
165    }
166
167    pub fn checked_add_bitcoin(self, amount: Amount) -> Option<Self> {
168        self.checked_add_unit(amount, AmountUnit::BITCOIN)
169    }
170
171    pub fn checked_add_unit(mut self, amount: Amount, unit: AmountUnit) -> Option<Self> {
172        if amount == Amount::ZERO {
173            return Some(self);
174        }
175
176        let prev = self.0.entry(unit).or_default();
177
178        *prev = prev.checked_add(amount)?;
179
180        Some(self)
181    }
182
183    pub fn remove(&mut self, unit: &AmountUnit) -> Option<Amount> {
184        self.0.remove(unit)
185    }
186
187    pub fn get_bitcoin(&self) -> Amount {
188        self.get(&AmountUnit::BITCOIN).copied().unwrap_or_default()
189    }
190
191    pub fn expect_only_bitcoin(&self) -> Amount {
192        #[allow(clippy::option_if_let_else)] // I like it explicitly split into two cases --dpc
193        match self.get(&AmountUnit::BITCOIN) {
194            Some(amount) => {
195                assert!(
196                    self.len() == 1,
197                    "Amounts expected to contain only bitcoin and no other currencies"
198                );
199                *amount
200            }
201            None => Amount::ZERO,
202        }
203    }
204
205    pub fn iter_units(&self) -> impl Iterator<Item = AmountUnit> {
206        self.0.keys().copied()
207    }
208
209    pub fn units(&self) -> BTreeSet<AmountUnit> {
210        self.0.keys().copied().collect()
211    }
212}
213
214impl IntoIterator for Amounts {
215    type Item = (AmountUnit, Amount);
216
217    type IntoIter = <BTreeMap<AmountUnit, Amount> as IntoIterator>::IntoIter;
218
219    fn into_iter(self) -> Self::IntoIter {
220        self.0.into_iter()
221    }
222}
223
224/// Information about the amount represented by an input or output.
225///
226/// * For **inputs** the amount is funding the transaction while the fee is
227///   consuming funding
228/// * For **outputs** the amount and the fee consume funding
229#[derive(Debug, Clone, Eq, PartialEq, Hash)]
230pub struct TransactionItemAmounts {
231    pub amounts: Amounts,
232    pub fees: Amounts,
233}
234
235impl TransactionItemAmounts {
236    pub fn checked_add(self, rhs: &Self) -> Option<Self> {
237        Some(Self {
238            amounts: self.amounts.checked_add(&rhs.amounts)?,
239            fees: self.fees.checked_add(&rhs.fees)?,
240        })
241    }
242}
243
244impl TransactionItemAmounts {
245    pub const ZERO: Self = Self {
246        amounts: Amounts::ZERO,
247        fees: Amounts::ZERO,
248    };
249}
250
251/// All requests from client to server contain these fields
252#[derive(Debug, Serialize, Deserialize, Clone)]
253pub struct ApiRequest<T> {
254    /// Hashed user password if the API requires authentication
255    pub auth: Option<ApiAuth>,
256    /// Parameters required by the API
257    pub params: T,
258}
259
260pub type ApiRequestErased = ApiRequest<JsonValue>;
261
262impl Default for ApiRequestErased {
263    fn default() -> Self {
264        Self {
265            auth: None,
266            params: JsonValue::Null,
267        }
268    }
269}
270
271impl ApiRequestErased {
272    pub fn new<T: Serialize>(params: T) -> Self {
273        Self {
274            auth: None,
275            params: serde_json::to_value(params)
276                .expect("parameter serialization error - this should not happen"),
277        }
278    }
279
280    pub fn to_json(&self) -> JsonValue {
281        serde_json::to_value(self).expect("parameter serialization error - this should not happen")
282    }
283
284    pub fn with_auth(self, auth: ApiAuth) -> Self {
285        Self {
286            auth: Some(auth),
287            params: self.params,
288        }
289    }
290
291    pub fn to_typed<T: serde::de::DeserializeOwned>(
292        self,
293    ) -> Result<ApiRequest<T>, serde_json::Error> {
294        Ok(ApiRequest {
295            auth: self.auth,
296            params: serde_json::from_value::<T>(self.params)?,
297        })
298    }
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
302pub enum ApiMethod {
303    Core(String),
304    Module(ModuleInstanceId, String),
305}
306
307impl fmt::Display for ApiMethod {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match self {
310            Self::Core(s) => f.write_str(s),
311            Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
312        }
313    }
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
317pub struct IrohApiRequest {
318    pub method: ApiMethod,
319    pub request: ApiRequestErased,
320}
321
322pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
323
324/// Authentication uses the hashed user password in PHC format
325#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
326pub struct ApiAuth(pub String);
327
328impl Debug for ApiAuth {
329    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
330        write!(f, "ApiAuth(****)")
331    }
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct ApiError {
336    pub code: i32,
337    pub message: String,
338}
339
340impl Error for ApiError {}
341
342impl fmt::Display for ApiError {
343    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
344        f.write_fmt(format_args!("{} {}", self.code, self.message))
345    }
346}
347
348pub type ApiResult<T> = Result<T, ApiError>;
349
350impl ApiError {
351    pub fn new(code: i32, message: String) -> Self {
352        Self { code, message }
353    }
354
355    pub fn not_found(message: String) -> Self {
356        Self::new(404, message)
357    }
358
359    pub fn bad_request(message: String) -> Self {
360        Self::new(400, message)
361    }
362
363    pub fn unauthorized() -> Self {
364        Self::new(401, "Invalid authorization".to_string())
365    }
366
367    pub fn server_error(message: String) -> Self {
368        Self::new(500, message)
369    }
370}
371
372/// State made available to all API endpoints for handling a request
373pub struct ApiEndpointContext<'dbtx> {
374    db: Database,
375    dbtx: DatabaseTransaction<'dbtx, Committable>,
376    has_auth: bool,
377    request_auth: Option<ApiAuth>,
378}
379
380impl<'a> ApiEndpointContext<'a> {
381    /// `db` and `dbtx` should be isolated.
382    pub fn new(
383        db: Database,
384        dbtx: DatabaseTransaction<'a, Committable>,
385        has_auth: bool,
386        request_auth: Option<ApiAuth>,
387    ) -> Self {
388        Self {
389            db,
390            dbtx,
391            has_auth,
392            request_auth,
393        }
394    }
395
396    /// Database tx handle, will be committed
397    pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
398    where
399        'a: 'mtx,
400        's: 'mtx,
401    {
402        // dbtx is already isolated.
403        self.dbtx.to_ref_nc()
404    }
405
406    /// Returns the auth set on the request (regardless of whether it was
407    /// correct)
408    pub fn request_auth(&self) -> Option<ApiAuth> {
409        self.request_auth.clone()
410    }
411
412    /// Whether the request was authenticated as the guardian who controls this
413    /// fedimint server
414    pub fn has_auth(&self) -> bool {
415        self.has_auth
416    }
417
418    pub fn db(&self) -> Database {
419        self.db.clone()
420    }
421
422    /// Waits for key to be present in database.
423    pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
424    where
425        K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
426    {
427        let db = self.db.clone();
428        // self contains dbtx which is !Send
429        // try removing this and see the error.
430        async move { db.wait_key_exists(&key).await }
431    }
432
433    /// Waits for key to have a value that matches.
434    pub fn wait_value_matches<K>(
435        &self,
436        key: K,
437        matcher: impl Fn(&K::Value) -> bool + Copy,
438    ) -> impl Future<Output = K::Value>
439    where
440        K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
441    {
442        let db = self.db.clone();
443        async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
444    }
445
446    /// Attempts to commit the dbtx or returns an `ApiError`
447    pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
448        self.dbtx.commit_tx_result().await.map_err(|err| {
449            tracing::warn!(
450                target: fedimint_logging::LOG_NET_API,
451                err = %err.fmt_compact_anyhow(),
452                %path,
453                "API server error when writing to database",
454            );
455            ApiError {
456                code: 500,
457                message: "API server error when writing to database".to_string(),
458            }
459        })
460    }
461}
462
463#[apply(async_trait_maybe_send!)]
464pub trait TypedApiEndpoint {
465    type State: Sync;
466
467    /// example: /transaction
468    const PATH: &'static str;
469
470    type Param: serde::de::DeserializeOwned + Send;
471    type Response: serde::Serialize;
472
473    async fn handle<'state, 'context, 'dbtx>(
474        state: &'state Self::State,
475        context: &'context mut ApiEndpointContext<'dbtx>,
476        request: Self::Param,
477    ) -> Result<Self::Response, ApiError>
478    where
479        'dbtx: 'context;
480}
481
482pub use serde_json;
483
484/// # Example
485///
486/// ```rust
487/// # use fedimint_core::module::ApiVersion;
488/// # use fedimint_core::module::{api_endpoint, ApiEndpoint, registry::ModuleInstanceId};
489/// struct State;
490///
491/// let _: ApiEndpoint<State> = api_endpoint! {
492///     "/foobar",
493///     ApiVersion::new(0, 3),
494///     async |state: &State, _dbtx, params: ()| -> i32 {
495///         Ok(0)
496///     }
497/// };
498/// ```
499#[macro_export]
500macro_rules! __api_endpoint {
501    (
502        $path:expr_2021,
503        // Api Version this endpoint was introduced in, at the current consensus level
504        // Currently for documentation purposes only.
505        $version_introduced:expr_2021,
506        async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
507    ) => {{
508        struct Endpoint;
509
510        #[$crate::apply($crate::async_trait_maybe_send!)]
511        impl $crate::module::TypedApiEndpoint for Endpoint {
512            #[allow(deprecated)]
513            const PATH: &'static str = $path;
514            type State = $state_ty;
515            type Param = $param_ty;
516            type Response = $resp_ty;
517
518            async fn handle<'state, 'context, 'dbtx>(
519                $state: &'state Self::State,
520                $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
521                $param: Self::Param,
522            ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
523                {
524                    // just to enforce the correct type
525                    const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
526                }
527                $body
528            }
529        }
530
531        $crate::module::ApiEndpoint::from_typed::<Endpoint>()
532    }};
533}
534
535pub use __api_endpoint as api_endpoint;
536
537use self::registry::ModuleDecoderRegistry;
538
539type HandlerFnReturn<'a> =
540    Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
541type HandlerFn<M> = Box<
542    maybe_add_send_sync!(
543        dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
544    ),
545>;
546
547/// Definition of an API endpoint defined by a module `M`.
548pub struct ApiEndpoint<M> {
549    /// Path under which the API endpoint can be reached. It should start with a
550    /// `/` e.g. `/transaction`. E.g. this API endpoint would be reachable
551    /// under `module_module_instance_id_transaction` depending on the
552    /// module name returned by `[FedertionModule::api_base_name]`.
553    pub path: &'static str,
554    /// Handler for the API call that takes the following arguments:
555    ///   * Reference to the module which defined it
556    ///   * Request parameters parsed into JSON `[Value](serde_json::Value)`
557    pub handler: HandlerFn<M>,
558}
559
560/// Global request ID used for logging
561static REQ_ID: AtomicU64 = AtomicU64::new(0);
562
563// <()> is used to avoid specify state.
564impl ApiEndpoint<()> {
565    pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
566    where
567        <E as TypedApiEndpoint>::Response: MaybeSend,
568        E::Param: Debug,
569        E::Response: Debug,
570    {
571        async fn handle_request<'state, 'context, 'dbtx, E>(
572            state: &'state E::State,
573            context: &'context mut ApiEndpointContext<'dbtx>,
574            request: ApiRequest<E::Param>,
575        ) -> Result<E::Response, ApiError>
576        where
577            'dbtx: 'context,
578            E: TypedApiEndpoint,
579            E::Param: Debug,
580            E::Response: Debug,
581        {
582            tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
583            let result = E::handle(state, context, request.params).await;
584            match &result {
585                Err(err) => {
586                    tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
587                }
588                _ => {
589                    tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
590                }
591            }
592            result
593        }
594
595        ApiEndpoint {
596            path: E::PATH,
597            handler: Box::new(|m, mut context, request| {
598                Box::pin(async {
599                    let request = request
600                        .to_typed()
601                        .map_err(|e| ApiError::bad_request(e.to_string()))?;
602
603                    let span = tracing::info_span!(
604                        target: LOG_NET_API,
605                        "api_req",
606                        id = REQ_ID.fetch_add(1, Ordering::SeqCst),
607                        method = E::PATH,
608                    );
609                    let ret = handle_request::<E>(m, &mut context, request)
610                        .instrument(span)
611                        .await?;
612
613                    context.commit_tx_result(E::PATH).await?;
614
615                    Ok(serde_json::to_value(ret).expect("encoding error"))
616                })
617            }),
618        }
619    }
620}
621
622/// Operations common to Server and Client side module gen dyn newtypes
623///
624/// Due to conflict of `impl Trait for T` for both `ServerModuleInit` and
625/// `ClientModuleInit`, we can't really have a `ICommonModuleInit`, so to unify
626/// them in `ModuleInitRegistry` we move the common functionality to be an
627/// interface over their dyn newtype wrappers. A bit weird, but works.
628#[apply(async_trait_maybe_send!)]
629pub trait IDynCommonModuleInit: Debug {
630    fn decoder(&self) -> Decoder;
631
632    fn module_kind(&self) -> ModuleKind;
633
634    fn to_dyn_common(&self) -> DynCommonModuleInit;
635
636    async fn dump_database(
637        &self,
638        dbtx: &mut DatabaseTransaction<'_>,
639        prefix_names: Vec<String>,
640    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
641}
642
643/// Trait implemented by every `*ModuleInit` (server or client side)
644pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
645    type Common: CommonModuleInit;
646
647    fn dump_database(
648        &self,
649        dbtx: &mut DatabaseTransaction<'_>,
650        prefix_names: Vec<String>,
651    ) -> maybe_add_send!(
652        impl Future<
653            Output = Box<
654                dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
655            >,
656        >
657    );
658}
659
660#[apply(async_trait_maybe_send!)]
661impl<T> IDynCommonModuleInit for T
662where
663    T: ModuleInit,
664{
665    fn decoder(&self) -> Decoder {
666        T::Common::decoder()
667    }
668
669    fn module_kind(&self) -> ModuleKind {
670        T::Common::KIND
671    }
672
673    fn to_dyn_common(&self) -> DynCommonModuleInit {
674        DynCommonModuleInit::from_inner(Arc::new(self.clone()))
675    }
676
677    async fn dump_database(
678        &self,
679        dbtx: &mut DatabaseTransaction<'_>,
680        prefix_names: Vec<String>,
681    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
682        <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
683    }
684}
685
686dyn_newtype_define!(
687    #[derive(Clone)]
688    pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
689);
690
691impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
692    fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
693        self.inner.as_ref()
694    }
695}
696
697impl DynCommonModuleInit {
698    pub fn from_inner(
699        inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
700    ) -> Self {
701        Self { inner }
702    }
703}
704
705/// Logic and constant common between server side and client side modules
706#[apply(async_trait_maybe_send!)]
707pub trait CommonModuleInit: Debug + Sized {
708    const CONSENSUS_VERSION: ModuleConsensusVersion;
709    const KIND: ModuleKind;
710
711    type ClientConfig: ClientConfig;
712
713    fn decoder() -> Decoder;
714}
715
716/// Module associated types required by both client and server
717pub trait ModuleCommon {
718    type ClientConfig: ClientConfig;
719    type Input: Input;
720    type Output: Output;
721    type OutputOutcome: OutputOutcome;
722    type ConsensusItem: ModuleConsensusItem;
723    type InputError: InputError;
724    type OutputError: OutputError;
725
726    fn decoder_builder() -> DecoderBuilder {
727        let mut decoder_builder = Decoder::builder();
728        decoder_builder.with_decodable_type::<Self::ClientConfig>();
729        decoder_builder.with_decodable_type::<Self::Input>();
730        decoder_builder.with_decodable_type::<Self::Output>();
731        decoder_builder.with_decodable_type::<Self::OutputOutcome>();
732        decoder_builder.with_decodable_type::<Self::ConsensusItem>();
733        decoder_builder.with_decodable_type::<Self::InputError>();
734        decoder_builder.with_decodable_type::<Self::OutputError>();
735
736        decoder_builder
737    }
738
739    fn decoder() -> Decoder {
740        Self::decoder_builder().build()
741    }
742}
743
744/// Creates a struct that can be used to make our module-decodable structs
745/// interact with `serde`-based APIs (AlephBFT, jsonrpsee). It creates a wrapper
746/// that holds the data as serialized
747// bytes internally.
748#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
749pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
750    #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
751    #[serde(skip)] PhantomData<T>,
752);
753
754/// Same as [`SerdeModuleEncoding`] but uses base64 instead of hex encoding.
755#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
756pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
757    #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
758    #[serde(skip)] PhantomData<T>,
759);
760
761impl<T> fmt::Debug for SerdeModuleEncoding<T>
762where
763    T: Encodable + Decodable,
764{
765    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
766        f.write_str("SerdeModuleEncoding(")?;
767        fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
768        f.write_str(")")?;
769        Ok(())
770    }
771}
772
773impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
774    fn from(value: &T) -> Self {
775        let mut bytes = vec![];
776        fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
777            .expect("Writing to buffer can never fail");
778        Self(bytes, PhantomData)
779    }
780}
781
782impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
783    pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
784        Decodable::consensus_decode_whole(&self.0, modules)
785    }
786
787    /// In cases where we know exactly which module kind we expect but don't
788    /// have access to all decoders this function can be used instead.
789    ///
790    /// Note that it just assumes the decoded module instance id to be valid
791    /// since it cannot validate against the decoder registry. The lack of
792    /// access to a decoder registry also makes decoding structs impossible that
793    /// themselves contain module dyn-types (e.g. a module output containing a
794    /// fedimint transaction).
795    pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
796        let mut reader = std::io::Cursor::new(&self.0);
797        let module_instance = ModuleInstanceId::consensus_decode_partial(
798            &mut reader,
799            &ModuleDecoderRegistry::default(),
800        )?;
801
802        let total_len =
803            u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
804
805        // No recursive module decoding is supported since we give an empty decoder
806        // registry to the decode function
807        decoder.decode_complete(
808            &mut reader,
809            total_len,
810            module_instance,
811            &ModuleRegistry::default(),
812        )
813    }
814}
815
816impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
817where
818    T: Encodable + Decodable,
819{
820    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
821        f.write_str("SerdeModuleEncoding2(")?;
822        fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
823        f.write_str(")")?;
824        Ok(())
825    }
826}
827
828impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
829    fn from(value: &T) -> Self {
830        let mut bytes = vec![];
831        fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
832            .expect("Writing to buffer can never fail");
833        Self(bytes, PhantomData)
834    }
835}
836
837impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
838    pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
839        Decodable::consensus_decode_whole(&self.0, modules)
840    }
841
842    /// In cases where we know exactly which module kind we expect but don't
843    /// have access to all decoders this function can be used instead.
844    ///
845    /// Note that it just assumes the decoded module instance id to be valid
846    /// since it cannot validate against the decoder registry. The lack of
847    /// access to a decoder registry also makes decoding structs impossible that
848    /// themselves contain module dyn-types (e.g. a module output containing a
849    /// fedimint transaction).
850    pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
851        let mut reader = std::io::Cursor::new(&self.0);
852        let module_instance = ModuleInstanceId::consensus_decode_partial(
853            &mut reader,
854            &ModuleDecoderRegistry::default(),
855        )?;
856
857        let total_len =
858            u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
859
860        // No recursive module decoding is supported since we give an empty decoder
861        // registry to the decode function
862        decoder.decode_complete(
863            &mut reader,
864            total_len,
865            module_instance,
866            &ModuleRegistry::default(),
867        )
868    }
869}