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