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