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