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::error::Error;
23use std::fmt::{self, Debug, Formatter};
24use std::marker::PhantomData;
25use std::pin::Pin;
26use std::sync::Arc;
27use std::sync::atomic::{AtomicU64, Ordering};
28
29use fedimint_logging::LOG_NET_API;
30use futures::Future;
31use jsonrpsee_core::JsonValue;
32use registry::ModuleRegistry;
33use serde::{Deserialize, Serialize};
34use tracing::Instrument;
35
36// TODO: Make this module public and remove theDkgPeerMessage`pub use` below
37mod version;
38pub use self::version::*;
39use crate::core::{
40    ClientConfig, Decoder, DecoderBuilder, Input, InputError, ModuleConsensusItem,
41    ModuleInstanceId, ModuleKind, Output, OutputError, OutputOutcome,
42};
43use crate::db::{
44    Committable, Database, DatabaseKey, DatabaseKeyWithNotify, DatabaseRecord, DatabaseTransaction,
45    NonCommittable,
46};
47use crate::encoding::{Decodable, DecodeError, Encodable};
48use crate::fmt_utils::AbbreviateHexBytes;
49use crate::task::MaybeSend;
50use crate::util::FmtCompact;
51use crate::{Amount, apply, async_trait_maybe_send, maybe_add_send, maybe_add_send_sync};
52
53#[derive(Debug, PartialEq, Eq)]
54pub struct InputMeta {
55    pub amount: TransactionItemAmount,
56    pub pub_key: secp256k1::PublicKey,
57}
58
59/// Information about the amount represented by an input or output.
60///
61/// * For **inputs** the amount is funding the transaction while the fee is
62///   consuming funding
63/// * For **outputs** the amount and the fee consume funding
64#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
65pub struct TransactionItemAmount {
66    pub amount: Amount,
67    pub fee: Amount,
68}
69
70impl TransactionItemAmount {
71    pub const ZERO: Self = Self {
72        amount: Amount::ZERO,
73        fee: Amount::ZERO,
74    };
75}
76
77/// All requests from client to server contain these fields
78#[derive(Debug, Serialize, Deserialize, Clone)]
79pub struct ApiRequest<T> {
80    /// Hashed user password if the API requires authentication
81    pub auth: Option<ApiAuth>,
82    /// Parameters required by the API
83    pub params: T,
84}
85
86pub type ApiRequestErased = ApiRequest<JsonValue>;
87
88impl Default for ApiRequestErased {
89    fn default() -> Self {
90        Self {
91            auth: None,
92            params: JsonValue::Null,
93        }
94    }
95}
96
97impl ApiRequestErased {
98    pub fn new<T: Serialize>(params: T) -> Self {
99        Self {
100            auth: None,
101            params: serde_json::to_value(params)
102                .expect("parameter serialization error - this should not happen"),
103        }
104    }
105
106    pub fn to_json(&self) -> JsonValue {
107        serde_json::to_value(self).expect("parameter serialization error - this should not happen")
108    }
109
110    pub fn with_auth(self, auth: ApiAuth) -> Self {
111        Self {
112            auth: Some(auth),
113            params: self.params,
114        }
115    }
116
117    pub fn to_typed<T: serde::de::DeserializeOwned>(
118        self,
119    ) -> Result<ApiRequest<T>, serde_json::Error> {
120        Ok(ApiRequest {
121            auth: self.auth,
122            params: serde_json::from_value::<T>(self.params)?,
123        })
124    }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub enum ApiMethod {
129    Core(String),
130    Module(ModuleInstanceId, String),
131}
132
133impl fmt::Display for ApiMethod {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        match self {
136            Self::Core(s) => f.write_str(s),
137            Self::Module(module_id, s) => f.write_fmt(format_args!("{module_id}-{s}")),
138        }
139    }
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct IrohApiRequest {
144    pub method: ApiMethod,
145    pub request: ApiRequestErased,
146}
147
148pub const FEDIMINT_API_ALPN: &[u8] = b"FEDIMINT_API_ALPN";
149
150/// Authentication uses the hashed user password in PHC format
151#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
152pub struct ApiAuth(pub String);
153
154impl Debug for ApiAuth {
155    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
156        write!(f, "ApiAuth(****)")
157    }
158}
159
160#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct ApiError {
162    pub code: i32,
163    pub message: String,
164}
165
166impl Error for ApiError {}
167
168impl fmt::Display for ApiError {
169    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
170        f.write_fmt(format_args!("{} {}", self.code, self.message))
171    }
172}
173
174pub type ApiResult<T> = Result<T, ApiError>;
175
176impl ApiError {
177    pub fn new(code: i32, message: String) -> Self {
178        Self { code, message }
179    }
180
181    pub fn not_found(message: String) -> Self {
182        Self::new(404, message)
183    }
184
185    pub fn bad_request(message: String) -> Self {
186        Self::new(400, message)
187    }
188
189    pub fn unauthorized() -> Self {
190        Self::new(401, "Invalid authorization".to_string())
191    }
192
193    pub fn server_error(message: String) -> Self {
194        Self::new(500, message)
195    }
196}
197
198/// State made available to all API endpoints for handling a request
199pub struct ApiEndpointContext<'dbtx> {
200    db: Database,
201    dbtx: DatabaseTransaction<'dbtx, Committable>,
202    has_auth: bool,
203    request_auth: Option<ApiAuth>,
204}
205
206impl<'a> ApiEndpointContext<'a> {
207    /// `db` and `dbtx` should be isolated.
208    pub fn new(
209        db: Database,
210        dbtx: DatabaseTransaction<'a, Committable>,
211        has_auth: bool,
212        request_auth: Option<ApiAuth>,
213    ) -> Self {
214        Self {
215            db,
216            dbtx,
217            has_auth,
218            request_auth,
219        }
220    }
221
222    /// Database tx handle, will be committed
223    pub fn dbtx<'s, 'mtx>(&'s mut self) -> DatabaseTransaction<'mtx, NonCommittable>
224    where
225        'a: 'mtx,
226        's: 'mtx,
227    {
228        // dbtx is already isolated.
229        self.dbtx.to_ref_nc()
230    }
231
232    /// Returns the auth set on the request (regardless of whether it was
233    /// correct)
234    pub fn request_auth(&self) -> Option<ApiAuth> {
235        self.request_auth.clone()
236    }
237
238    /// Whether the request was authenticated as the guardian who controls this
239    /// fedimint server
240    pub fn has_auth(&self) -> bool {
241        self.has_auth
242    }
243
244    pub fn db(&self) -> Database {
245        self.db.clone()
246    }
247
248    /// Waits for key to be present in database.
249    pub fn wait_key_exists<K>(&self, key: K) -> impl Future<Output = K::Value> + use<K>
250    where
251        K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
252    {
253        let db = self.db.clone();
254        // self contains dbtx which is !Send
255        // try removing this and see the error.
256        async move { db.wait_key_exists(&key).await }
257    }
258
259    /// Waits for key to have a value that matches.
260    pub fn wait_value_matches<K>(
261        &self,
262        key: K,
263        matcher: impl Fn(&K::Value) -> bool + Copy,
264    ) -> impl Future<Output = K::Value>
265    where
266        K: DatabaseKey + DatabaseRecord + DatabaseKeyWithNotify,
267    {
268        let db = self.db.clone();
269        async move { db.wait_key_check(&key, |v| v.filter(matcher)).await.0 }
270    }
271
272    /// Attempts to commit the dbtx or returns an `ApiError`
273    pub async fn commit_tx_result(self, path: &'static str) -> Result<(), ApiError> {
274        self.dbtx.commit_tx_result().await.map_err(|err| {
275            tracing::warn!(
276                target: fedimint_logging::LOG_NET_API,
277                path,
278                "API server error when writing to database: {:?}",
279                err
280            );
281            ApiError {
282                code: 500,
283                message: "API server error when writing to database".to_string(),
284            }
285        })
286    }
287}
288
289#[apply(async_trait_maybe_send!)]
290pub trait TypedApiEndpoint {
291    type State: Sync;
292
293    /// example: /transaction
294    const PATH: &'static str;
295
296    type Param: serde::de::DeserializeOwned + Send;
297    type Response: serde::Serialize;
298
299    async fn handle<'state, 'context, 'dbtx>(
300        state: &'state Self::State,
301        context: &'context mut ApiEndpointContext<'dbtx>,
302        request: Self::Param,
303    ) -> Result<Self::Response, ApiError>
304    where
305        'dbtx: 'context;
306}
307
308pub use serde_json;
309
310/// # Example
311///
312/// ```rust
313/// # use fedimint_core::module::ApiVersion;
314/// # use fedimint_core::module::{api_endpoint, ApiEndpoint, registry::ModuleInstanceId};
315/// struct State;
316///
317/// let _: ApiEndpoint<State> = api_endpoint! {
318///     "/foobar",
319///     ApiVersion::new(0, 3),
320///     async |state: &State, _dbtx, params: ()| -> i32 {
321///         Ok(0)
322///     }
323/// };
324/// ```
325#[macro_export]
326macro_rules! __api_endpoint {
327    (
328        $path:expr_2021,
329        // Api Version this endpoint was introduced in, at the current consensus level
330        // Currently for documentation purposes only.
331        $version_introduced:expr_2021,
332        async |$state:ident: &$state_ty:ty, $context:ident, $param:ident: $param_ty:ty| -> $resp_ty:ty $body:block
333    ) => {{
334        struct Endpoint;
335
336        #[$crate::apply($crate::async_trait_maybe_send!)]
337        impl $crate::module::TypedApiEndpoint for Endpoint {
338            #[allow(deprecated)]
339            const PATH: &'static str = $path;
340            type State = $state_ty;
341            type Param = $param_ty;
342            type Response = $resp_ty;
343
344            async fn handle<'state, 'context, 'dbtx>(
345                $state: &'state Self::State,
346                $context: &'context mut $crate::module::ApiEndpointContext<'dbtx>,
347                $param: Self::Param,
348            ) -> ::std::result::Result<Self::Response, $crate::module::ApiError> {
349                {
350                    // just to enforce the correct type
351                    const __API_VERSION: $crate::module::ApiVersion = $version_introduced;
352                }
353                $body
354            }
355        }
356
357        $crate::module::ApiEndpoint::from_typed::<Endpoint>()
358    }};
359}
360
361pub use __api_endpoint as api_endpoint;
362
363use self::registry::ModuleDecoderRegistry;
364
365type HandlerFnReturn<'a> =
366    Pin<Box<maybe_add_send!(dyn Future<Output = Result<serde_json::Value, ApiError>> + 'a)>>;
367type HandlerFn<M> = Box<
368    maybe_add_send_sync!(
369        dyn for<'a> Fn(&'a M, ApiEndpointContext<'a>, ApiRequestErased) -> HandlerFnReturn<'a>
370    ),
371>;
372
373/// Definition of an API endpoint defined by a module `M`.
374pub struct ApiEndpoint<M> {
375    /// Path under which the API endpoint can be reached. It should start with a
376    /// `/` e.g. `/transaction`. E.g. this API endpoint would be reachable
377    /// under `module_module_instance_id_transaction` depending on the
378    /// module name returned by `[FedertionModule::api_base_name]`.
379    pub path: &'static str,
380    /// Handler for the API call that takes the following arguments:
381    ///   * Reference to the module which defined it
382    ///   * Request parameters parsed into JSON `[Value](serde_json::Value)`
383    pub handler: HandlerFn<M>,
384}
385
386/// Global request ID used for logging
387static REQ_ID: AtomicU64 = AtomicU64::new(0);
388
389// <()> is used to avoid specify state.
390impl ApiEndpoint<()> {
391    pub fn from_typed<E: TypedApiEndpoint>() -> ApiEndpoint<E::State>
392    where
393        <E as TypedApiEndpoint>::Response: MaybeSend,
394        E::Param: Debug,
395        E::Response: Debug,
396    {
397        async fn handle_request<'state, 'context, 'dbtx, E>(
398            state: &'state E::State,
399            context: &'context mut ApiEndpointContext<'dbtx>,
400            request: ApiRequest<E::Param>,
401        ) -> Result<E::Response, ApiError>
402        where
403            'dbtx: 'context,
404            E: TypedApiEndpoint,
405            E::Param: Debug,
406            E::Response: Debug,
407        {
408            tracing::debug!(target: LOG_NET_API, path = E::PATH, ?request, "received api request");
409            let result = E::handle(state, context, request.params).await;
410            match &result {
411                Err(err) => {
412                    tracing::warn!(target: LOG_NET_API, path = E::PATH, err = %err.fmt_compact(), "api request error");
413                }
414                _ => {
415                    tracing::trace!(target: LOG_NET_API, path = E::PATH, "api request complete");
416                }
417            }
418            result
419        }
420
421        ApiEndpoint {
422            path: E::PATH,
423            handler: Box::new(|m, mut context, request| {
424                Box::pin(async {
425                    let request = request
426                        .to_typed()
427                        .map_err(|e| ApiError::bad_request(e.to_string()))?;
428
429                    let span = tracing::info_span!(
430                        target: LOG_NET_API,
431                        "api_req",
432                        id = REQ_ID.fetch_add(1, Ordering::SeqCst),
433                        method = E::PATH,
434                    );
435                    let ret = handle_request::<E>(m, &mut context, request)
436                        .instrument(span)
437                        .await?;
438
439                    context.commit_tx_result(E::PATH).await?;
440
441                    Ok(serde_json::to_value(ret).expect("encoding error"))
442                })
443            }),
444        }
445    }
446}
447
448/// Operations common to Server and Client side module gen dyn newtypes
449///
450/// Due to conflict of `impl Trait for T` for both `ServerModuleInit` and
451/// `ClientModuleInit`, we can't really have a `ICommonModuleInit`, so to unify
452/// them in `ModuleInitRegistry` we move the common functionality to be an
453/// interface over their dyn newtype wrappers. A bit weird, but works.
454#[apply(async_trait_maybe_send!)]
455pub trait IDynCommonModuleInit: Debug {
456    fn decoder(&self) -> Decoder;
457
458    fn module_kind(&self) -> ModuleKind;
459
460    fn to_dyn_common(&self) -> DynCommonModuleInit;
461
462    async fn dump_database(
463        &self,
464        dbtx: &mut DatabaseTransaction<'_>,
465        prefix_names: Vec<String>,
466    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_>;
467}
468
469/// Trait implemented by every `*ModuleInit` (server or client side)
470pub trait ModuleInit: Debug + Clone + Send + Sync + 'static {
471    type Common: CommonModuleInit;
472
473    fn dump_database(
474        &self,
475        dbtx: &mut DatabaseTransaction<'_>,
476        prefix_names: Vec<String>,
477    ) -> maybe_add_send!(
478        impl Future<
479            Output = Box<
480                dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_,
481            >,
482        >
483    );
484}
485
486#[apply(async_trait_maybe_send!)]
487impl<T> IDynCommonModuleInit for T
488where
489    T: ModuleInit,
490{
491    fn decoder(&self) -> Decoder {
492        T::Common::decoder()
493    }
494
495    fn module_kind(&self) -> ModuleKind {
496        T::Common::KIND
497    }
498
499    fn to_dyn_common(&self) -> DynCommonModuleInit {
500        DynCommonModuleInit::from_inner(Arc::new(self.clone()))
501    }
502
503    async fn dump_database(
504        &self,
505        dbtx: &mut DatabaseTransaction<'_>,
506        prefix_names: Vec<String>,
507    ) -> Box<dyn Iterator<Item = (String, Box<dyn erased_serde::Serialize + Send>)> + '_> {
508        <Self as ModuleInit>::dump_database(self, dbtx, prefix_names).await
509    }
510}
511
512dyn_newtype_define!(
513    #[derive(Clone)]
514    pub DynCommonModuleInit(Arc<IDynCommonModuleInit>)
515);
516
517impl AsRef<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)> for DynCommonModuleInit {
518    fn as_ref(&self) -> &(maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)) {
519        self.inner.as_ref()
520    }
521}
522
523impl DynCommonModuleInit {
524    pub fn from_inner(
525        inner: Arc<maybe_add_send_sync!(dyn IDynCommonModuleInit + 'static)>,
526    ) -> Self {
527        Self { inner }
528    }
529}
530
531/// Logic and constant common between server side and client side modules
532#[apply(async_trait_maybe_send!)]
533pub trait CommonModuleInit: Debug + Sized {
534    const CONSENSUS_VERSION: ModuleConsensusVersion;
535    const KIND: ModuleKind;
536
537    type ClientConfig: ClientConfig;
538
539    fn decoder() -> Decoder;
540}
541
542/// Module associated types required by both client and server
543pub trait ModuleCommon {
544    type ClientConfig: ClientConfig;
545    type Input: Input;
546    type Output: Output;
547    type OutputOutcome: OutputOutcome;
548    type ConsensusItem: ModuleConsensusItem;
549    type InputError: InputError;
550    type OutputError: OutputError;
551
552    fn decoder_builder() -> DecoderBuilder {
553        let mut decoder_builder = Decoder::builder();
554        decoder_builder.with_decodable_type::<Self::ClientConfig>();
555        decoder_builder.with_decodable_type::<Self::Input>();
556        decoder_builder.with_decodable_type::<Self::Output>();
557        decoder_builder.with_decodable_type::<Self::OutputOutcome>();
558        decoder_builder.with_decodable_type::<Self::ConsensusItem>();
559        decoder_builder.with_decodable_type::<Self::InputError>();
560        decoder_builder.with_decodable_type::<Self::OutputError>();
561
562        decoder_builder
563    }
564
565    fn decoder() -> Decoder {
566        Self::decoder_builder().build()
567    }
568}
569
570/// Creates a struct that can be used to make our module-decodable structs
571/// interact with `serde`-based APIs (AlephBFT, jsonrpsee). It creates a wrapper
572/// that holds the data as serialized
573// bytes internally.
574#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
575pub struct SerdeModuleEncoding<T: Encodable + Decodable>(
576    #[serde(with = "::fedimint_core::encoding::as_hex")] Vec<u8>,
577    #[serde(skip)] PhantomData<T>,
578);
579
580/// Same as [`SerdeModuleEncoding`] but uses base64 instead of hex encoding.
581#[derive(Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
582pub struct SerdeModuleEncodingBase64<T: Encodable + Decodable>(
583    #[serde(with = "::fedimint_core::encoding::as_base64")] Vec<u8>,
584    #[serde(skip)] PhantomData<T>,
585);
586
587impl<T> fmt::Debug for SerdeModuleEncoding<T>
588where
589    T: Encodable + Decodable,
590{
591    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
592        f.write_str("SerdeModuleEncoding(")?;
593        fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
594        f.write_str(")")?;
595        Ok(())
596    }
597}
598
599impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncoding<T> {
600    fn from(value: &T) -> Self {
601        let mut bytes = vec![];
602        fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
603            .expect("Writing to buffer can never fail");
604        Self(bytes, PhantomData)
605    }
606}
607
608impl<T: Encodable + Decodable + 'static> SerdeModuleEncoding<T> {
609    pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
610        Decodable::consensus_decode_whole(&self.0, modules)
611    }
612
613    /// In cases where we know exactly which module kind we expect but don't
614    /// have access to all decoders this function can be used instead.
615    ///
616    /// Note that it just assumes the decoded module instance id to be valid
617    /// since it cannot validate against the decoder registry. The lack of
618    /// access to a decoder registry also makes decoding structs impossible that
619    /// themselves contain module dyn-types (e.g. a module output containing a
620    /// fedimint transaction).
621    pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
622        let mut reader = std::io::Cursor::new(&self.0);
623        let module_instance = ModuleInstanceId::consensus_decode_partial(
624            &mut reader,
625            &ModuleDecoderRegistry::default(),
626        )?;
627
628        let total_len =
629            u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
630
631        // No recursive module decoding is supported since we give an empty decoder
632        // registry to the decode function
633        decoder.decode_complete(
634            &mut reader,
635            total_len,
636            module_instance,
637            &ModuleRegistry::default(),
638        )
639    }
640}
641
642impl<T> fmt::Debug for SerdeModuleEncodingBase64<T>
643where
644    T: Encodable + Decodable,
645{
646    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
647        f.write_str("SerdeModuleEncoding2(")?;
648        fmt::Debug::fmt(&AbbreviateHexBytes(&self.0), f)?;
649        f.write_str(")")?;
650        Ok(())
651    }
652}
653
654impl<T: Encodable + Decodable> From<&T> for SerdeModuleEncodingBase64<T> {
655    fn from(value: &T) -> Self {
656        let mut bytes = vec![];
657        fedimint_core::encoding::Encodable::consensus_encode(value, &mut bytes)
658            .expect("Writing to buffer can never fail");
659        Self(bytes, PhantomData)
660    }
661}
662
663impl<T: Encodable + Decodable + 'static> SerdeModuleEncodingBase64<T> {
664    pub fn try_into_inner(&self, modules: &ModuleDecoderRegistry) -> Result<T, DecodeError> {
665        Decodable::consensus_decode_whole(&self.0, modules)
666    }
667
668    /// In cases where we know exactly which module kind we expect but don't
669    /// have access to all decoders this function can be used instead.
670    ///
671    /// Note that it just assumes the decoded module instance id to be valid
672    /// since it cannot validate against the decoder registry. The lack of
673    /// access to a decoder registry also makes decoding structs impossible that
674    /// themselves contain module dyn-types (e.g. a module output containing a
675    /// fedimint transaction).
676    pub fn try_into_inner_known_module_kind(&self, decoder: &Decoder) -> Result<T, DecodeError> {
677        let mut reader = std::io::Cursor::new(&self.0);
678        let module_instance = ModuleInstanceId::consensus_decode_partial(
679            &mut reader,
680            &ModuleDecoderRegistry::default(),
681        )?;
682
683        let total_len =
684            u64::consensus_decode_partial(&mut reader, &ModuleDecoderRegistry::default())?;
685
686        // No recursive module decoding is supported since we give an empty decoder
687        // registry to the decode function
688        decoder.decode_complete(
689            &mut reader,
690            total_len,
691            module_instance,
692            &ModuleRegistry::default(),
693        )
694    }
695}