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// TODO: either nuke or turn all `api_secret: Option<String>` into `api_secret:
325// Option<ApiAuth>`
326/// Authentication uses the hashed user password in PHC format
327#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
328pub struct ApiAuth(pub String);
329
330impl Debug for ApiAuth {
331    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
332        write!(f, "ApiAuth(****)")
333    }
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct ApiError {
338    pub code: i32,
339    pub message: String,
340}
341
342impl Error for ApiError {}
343
344impl fmt::Display for ApiError {
345    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
346        f.write_fmt(format_args!("{} {}", self.code, self.message))
347    }
348}
349
350pub type ApiResult<T> = Result<T, ApiError>;
351
352impl ApiError {
353    pub fn new(code: i32, message: String) -> Self {
354        Self { code, message }
355    }
356
357    pub fn not_found(message: String) -> Self {
358        Self::new(404, message)
359    }
360
361    pub fn bad_request(message: String) -> Self {
362        Self::new(400, message)
363    }
364
365    pub fn unauthorized() -> Self {
366        Self::new(401, "Invalid authorization".to_string())
367    }
368
369    pub fn server_error(message: String) -> Self {
370        Self::new(500, message)
371    }
372}
373
374/// State made available to all API endpoints for handling a request
375pub struct ApiEndpointContext<'dbtx> {
376    db: Database,
377    dbtx: DatabaseTransaction<'dbtx, Committable>,
378    has_auth: bool,
379    request_auth: Option<ApiAuth>,
380}
381
382impl<'a> ApiEndpointContext<'a> {
383    /// `db` and `dbtx` should be isolated.
384    pub fn new(
385        db: Database,
386        dbtx: DatabaseTransaction<'a, Committable>,
387        has_auth: bool,
388        request_auth: Option<ApiAuth>,
389    ) -> Self {
390        Self {
391            db,
392            dbtx,
393            has_auth,
394            request_auth,
395        }
396    }
397
398    /// Database tx handle, will be committed
399    pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
400    where
401        'a: 'mtx,
402        's: 'mtx,
403    {
404        // dbtx is already isolated.
405        self.dbtx.to_ref_nc()
406    }
407
408    /// Returns the auth set on the request (regardless of whether it was
409    /// correct)
410    pub fn request_auth(&self) -> Option<ApiAuth> {
411        self.request_auth.clone()
412    }
413
414    /// Whether the request was authenticated as the guardian who controls this
415    /// fedimint server
416    pub fn has_auth(&self) -> bool {
417        self.has_auth
418    }
419
420    pub fn db(&self) -> Database {
421        self.db.clone()
422    }
423
424    /// Waits for key to be present in database.
425    pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
426    where
427        K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
428    {
429        let db = self.db.clone();
430        // self contains dbtx which is !Send
431        // try removing this and see the error.
432        async move { db.wait_key_exists(&key).await }
433    }
434
435    /// Waits for key to have a value that matches.
436    pub fn wait_value_matches<K>(
437        &self,
438        key: K,
439        matcher: impl Fn(&K::Value) -> bool + Copy,
440    ) -> impl Future<Output = K::Value>
441    where
442        K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
443    {
444        let db = self.db.clone();
445        async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
446    }
447
448    /// Attempts to commit the dbtx or returns an `ApiError`
449    pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
450        self.dbtx.commit_tx_result().await.map_err(|err| {
451            tracing::warn!(
452                target: fedimint_logging::LOG_NET_API,
453                err = %err.fmt_compact_anyhow(),
454                %path,
455                "API server error when writing to database",
456            );
457            ApiError {
458                code: 500,
459                message: "API server error when writing to database".to_string(),
460            }
461        })
462    }
463}
464
465#[apply(async_trait_maybe_send!)]
466pub trait TypedApiEndpoint {
467    type State: Sync;
468
469    /// example: /transaction
470    const PATH: &'static str;
471
472    type Param: serde::de::DeserializeOwned + Send;
473    type Response: serde::Serialize;
474
475    async fn handle<'state, 'context, 'dbtx>(
476        state: &'state Self::State,
477        context: &'context mut ApiEndpointContext<'dbtx>,
478        request: Self::Param,
479    ) -> Result<Self::Response, ApiError>
480    where
481        'dbtx: 'context;
482}
483
484pub use serde_json;
485
486/// # Example
487///
488/// ```rust
489/// # use fedimint_core::module::ApiVersion;
490/// # use fedimint_core::module::{api_endpoint, ApiEndpoint, registry::ModuleInstanceId};
491/// struct State;
492///
493/// let _: ApiEndpoint<State> = api_endpoint! {
494///     "/foobar",
495///     ApiVersion::new(0, 3),
496///     async |state: &State, _dbtx, params: ()| -> i32 {
497///         Ok(0)
498///     }
499/// };
500/// ```
501#[macro_export]
502macro_rules! __api_endpoint {
503    (
504        $path:expr_2021,
505        // Api Version this endpoint was introduced in, at the current consensus level
506        // Currently for documentation purposes only.
507        $version_introduced:expr_2021,
508        async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
509    ) => {{
510        struct Endpoint;
511
512        #[$crate::apply($crate::async_trait_maybe_send!)]
513        impl $crate::module::TypedApiEndpoint for Endpoint {
514            #[allow(deprecated)]
515            const PATH: &'static str = $path;
516            type State = $state_ty;
517            type Param = $param_ty;
518            type Response = $resp_ty;
519
520            async fn handle<'state, 'context, 'dbtx>(
521                $state: &'state Self::State,
522                $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
523                $param: Self::Param,
524            ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
525                {
526                    // just to enforce the correct type
527                    const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
528                }
529                $body
530            }
531        }
532
533        $crate::module::ApiEndpoint::from_typed::<Endpoint>()
534    }};
535}
536
537pub use __api_endpoint as api_endpoint;
538
539use self::registry::ModuleDecoderRegistry;
540
541type HandlerFnReturn<'a> =
542    Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
543type HandlerFn<M> = Box<
544    maybe_add_send_sync!(
545        dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
546    ),
547>;
548
549/// Definition of an API endpoint defined by a module `M`.
550pub struct ApiEndpoint<M> {
551    /// Path under which the API endpoint can be reached. It should start with a
552    /// `/` e.g. `/transaction`. E.g. this API endpoint would be reachable
553    /// under `module_module_instance_id_transaction` depending on the
554    /// module name returned by `[FedertionModule::api_base_name]`.
555    pub path: &'static str,
556    /// Handler for the API call that takes the following arguments:
557    ///   * Reference to the module which defined it
558    ///   * Request parameters parsed into JSON `[Value](serde_json::Value)`
559    pub handler: HandlerFn<M>,
560}
561
562/// Global request ID used for logging
563static REQ_ID: AtomicU64 = AtomicU64::new(0);
564
565// <()> is used to avoid specify state.
566impl ApiEndpoint<()> {
567    pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
568    where
569        <E as TypedApiEndpoint>::Response: MaybeSend,
570        E::Param: Debug,
571        E::Response: Debug,
572    {
573        async fn handle_request<'state, 'context, 'dbtx, E>(
574            state: &'state E::State,
575            context: &'context mut ApiEndpointContext<'dbtx>,
576            request: ApiRequest<E::Param>,
577        ) -> Result<E::Response, ApiError>
578        where
579            'dbtx: 'context,
580            E: TypedApiEndpoint,
581            E::Param: Debug,
582            E::Response: Debug,
583        {
584            tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
585            let result = E::handle(state, context, request.params).await;
586            match &result {
587                Err(err) => {
588                    tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
589                }
590                _ => {
591                    tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
592                }
593            }
594            result
595        }
596
597        ApiEndpoint {
598            path: E::PATH,
599            handler: Box::new(|m, mut context, request| {
600                Box::pin(async {
601                    let request = request
602                        .to_typed()
603                        .map_err(|e| ApiError::bad_request(e.to_string()))?;
604
605                    let span = tracing::info_span!(
606                        target: LOG_NET_API,
607                        "api_req",
608                        id = REQ_ID.fetch_add(1, Ordering::SeqCst),
609                        method = E::PATH,
610                    );
611                    let ret = handle_request::<E>(m, &mut context, request)
612                        .instrument(span)
613                        .await?;
614
615                    context.commit_tx_result(E::PATH).await?;
616
617                    Ok(serde_json::to_value(ret).expect("encoding error"))
618                })
619            }),
620        }
621    }
622}
623
624/// Operations common to Server and Client side module gen dyn newtypes
625///
626/// Due to conflict of `impl Trait for T` for both `ServerModuleInit` and
627/// `ClientModuleInit`, we can't really have a `ICommonModuleInit`, so to unify
628/// them in `ModuleInitRegistry` we move the common functionality to be an
629/// interface over their dyn newtype wrappers. A bit weird, but works.
630#[apply(async_trait_maybe_send!)]
631pub trait IDynCommonModuleInit: Debug {
632    fn decoder(&self) -> Decoder;
633
634    fn module_kind(&self) -> ModuleKind;
635
636    fn to_dyn_common(&self) -> DynCommonModuleInit;
637
638    async fn dump_database(
639        &self,
640        dbtx: &mut DatabaseTransaction<'_>,
641        prefix_names: Vec<String>,
642    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
643}
644
645/// Trait implemented by every `*ModuleInit` (server or client side)
646pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
647    type Common: CommonModuleInit;
648
649    fn dump_database(
650        &self,
651        dbtx: &mut DatabaseTransaction<'_>,
652        prefix_names: Vec<String>,
653    ) -> maybe_add_send!(
654        impl Future<
655            Output = Box<
656                dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
657            >,
658        >
659    );
660}
661
662#[apply(async_trait_maybe_send!)]
663impl<T> IDynCommonModuleInit for T
664where
665    T: ModuleInit,
666{
667    fn decoder(&self) -> Decoder {
668        T::Common::decoder()
669    }
670
671    fn module_kind(&self) -> ModuleKind {
672        T::Common::KIND
673    }
674
675    fn to_dyn_common(&self) -> DynCommonModuleInit {
676        DynCommonModuleInit::from_inner(Arc::new(self.clone()))
677    }
678
679    async fn dump_database(
680        &self,
681        dbtx: &mut DatabaseTransaction<'_>,
682        prefix_names: Vec<String>,
683    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
684        <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
685    }
686}
687
688dyn_newtype_define!(
689    #[derive(Clone)]
690    pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
691);
692
693impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
694    fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
695        self.inner.as_ref()
696    }
697}
698
699impl DynCommonModuleInit {
700    pub fn from_inner(
701        inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
702    ) -> Self {
703        Self { inner }
704    }
705}
706
707/// Logic and constant common between server side and client side modules
708#[apply(async_trait_maybe_send!)]
709pub trait CommonModuleInit: Debug + Sized {
710    const CONSENSUS_VERSION: ModuleConsensusVersion;
711    const KIND: ModuleKind;
712
713    type ClientConfig: ClientConfig;
714
715    fn decoder() -> Decoder;
716}
717
718/// Module associated types required by both client and server
719pub trait ModuleCommon {
720    type ClientConfig: ClientConfig;
721    type Input: Input;
722    type Output: Output;
723    type OutputOutcome: OutputOutcome;
724    type ConsensusItem: ModuleConsensusItem;
725    type InputError: InputError;
726    type OutputError: OutputError;
727
728    fn decoder_builder() -> DecoderBuilder {
729        let mut decoder_builder = Decoder::builder();
730        decoder_builder.with_decodable_type::<Self::ClientConfig>();
731        decoder_builder.with_decodable_type::<Self::Input>();
732        decoder_builder.with_decodable_type::<Self::Output>();
733        decoder_builder.with_decodable_type::<Self::OutputOutcome>();
734        decoder_builder.with_decodable_type::<Self::ConsensusItem>();
735        decoder_builder.with_decodable_type::<Self::InputError>();
736        decoder_builder.with_decodable_type::<Self::OutputError>();
737
738        decoder_builder
739    }
740
741    fn decoder() -> Decoder {
742        Self::decoder_builder().build()
743    }
744}
745
746/// Creates a struct that can be used to make our module-decodable structs
747/// interact with `serde`-based APIs (AlephBFT, jsonrpsee). It creates a wrapper
748/// that holds the data as serialized
749// bytes internally.
750#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
751pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
752    #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
753    #[serde(skip)] PhantomData<T>,
754);
755
756/// Same as [`SerdeModuleEncoding`] but uses base64 instead of hex encoding.
757#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
758pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
759    #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
760    #[serde(skip)] PhantomData<T>,
761);
762
763impl<T> fmt::Debug for SerdeModuleEncoding<T>
764where
765    T: Encodable + Decodable,
766{
767    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
768        f.write_str("SerdeModuleEncoding(")?;
769        fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
770        f.write_str(")")?;
771        Ok(())
772    }
773}
774
775impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
776    fn from(value: &T) -> Self {
777        let mut bytes = vec![];
778        fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
779            .expect("Writing to buffer can never fail");
780        Self(bytes, PhantomData)
781    }
782}
783
784impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
785    pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
786        Decodable::consensus_decode_whole(&self.0, modules)
787    }
788
789    /// In cases where we know exactly which module kind we expect but don't
790    /// have access to all decoders this function can be used instead.
791    ///
792    /// Note that it just assumes the decoded module instance id to be valid
793    /// since it cannot validate against the decoder registry. The lack of
794    /// access to a decoder registry also makes decoding structs impossible that
795    /// themselves contain module dyn-types (e.g. a module output containing a
796    /// fedimint transaction).
797    pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
798        let mut reader = std::io::Cursor::new(&self.0);
799        let module_instance = ModuleInstanceId::consensus_decode_partial(
800            &mut reader,
801            &ModuleDecoderRegistry::default(),
802        )?;
803
804        let total_len =
805            u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
806
807        // No recursive module decoding is supported since we give an empty decoder
808        // registry to the decode function
809        decoder.decode_complete(
810            &mut reader,
811            total_len,
812            module_instance,
813            &ModuleRegistry::default(),
814        )
815    }
816}
817
818impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
819where
820    T: Encodable + Decodable,
821{
822    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
823        f.write_str("SerdeModuleEncoding2(")?;
824        fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
825        f.write_str(")")?;
826        Ok(())
827    }
828}
829
830impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
831    fn from(value: &T) -> Self {
832        let mut bytes = vec![];
833        fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
834            .expect("Writing to buffer can never fail");
835        Self(bytes, PhantomData)
836    }
837}
838
839impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
840    pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
841        Decodable::consensus_decode_whole(&self.0, modules)
842    }
843
844    /// In cases where we know exactly which module kind we expect but don't
845    /// have access to all decoders this function can be used instead.
846    ///
847    /// Note that it just assumes the decoded module instance id to be valid
848    /// since it cannot validate against the decoder registry. The lack of
849    /// access to a decoder registry also makes decoding structs impossible that
850    /// themselves contain module dyn-types (e.g. a module output containing a
851    /// fedimint transaction).
852    pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
853        let mut reader = std::io::Cursor::new(&self.0);
854        let module_instance = ModuleInstanceId::consensus_decode_partial(
855            &mut reader,
856            &ModuleDecoderRegistry::default(),
857        )?;
858
859        let total_len =
860            u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
861
862        // No recursive module decoding is supported since we give an empty decoder
863        // registry to the decode function
864        decoder.decode_complete(
865            &mut reader,
866            total_len,
867            module_instance,
868            &ModuleRegistry::default(),
869        )
870    }
871}