Skip to main content

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