fedimint_core/core/
server.rs

1//! Fedimint Core Server module interface
2//!
3//! Fedimint supports externally implemented modules.
4//!
5//! This (Rust) module defines common interoperability types
6//! and functionality that are only used on the server side.
7use std::fmt::Debug;
8use std::sync::Arc;
9
10use fedimint_core::module::audit::Audit;
11use fedimint_core::{apply, async_trait_maybe_send, OutPoint, PeerId};
12
13use super::ModuleKind;
14use crate::core::{
15    Any, Decoder, DynInput, DynInputError, DynModuleConsensusItem, DynOutput, DynOutputError,
16    DynOutputOutcome,
17};
18use crate::db::DatabaseTransaction;
19use crate::dyn_newtype_define;
20use crate::module::registry::ModuleInstanceId;
21use crate::module::{
22    ApiEndpoint, ApiEndpointContext, ApiRequestErased, InputMeta, ModuleCommon, ServerModule,
23    TransactionItemAmount,
24};
25
26/// Backend side module interface
27///
28/// Server side Fedimint module needs to implement this trait.
29#[apply(async_trait_maybe_send!)]
30pub trait IServerModule: Debug {
31    fn as_any(&self) -> &dyn Any;
32
33    /// Returns the decoder belonging to the server module
34    fn decoder(&self) -> Decoder;
35
36    fn module_kind(&self) -> ModuleKind;
37
38    /// This module's contribution to the next consensus proposal
39    async fn consensus_proposal(
40        &self,
41        dbtx: &mut DatabaseTransaction<'_>,
42        module_instance_id: ModuleInstanceId,
43    ) -> Vec<DynModuleConsensusItem>;
44
45    /// This function is called once for every consensus item. The function
46    /// returns an error if any only if the consensus item does not change
47    /// our state and therefore may be safely discarded by the atomic broadcast.
48    async fn process_consensus_item<'a, 'b>(
49        &self,
50        dbtx: &mut DatabaseTransaction<'a>,
51        consensus_item: &'b DynModuleConsensusItem,
52        peer_id: PeerId,
53    ) -> anyhow::Result<()>;
54
55    // Use this function to parallelise stateless cryptographic verification of
56    // inputs across a transaction. All inputs of a transaction are verified
57    // before any input is processed.
58    fn verify_input(&self, input: &DynInput) -> Result<(), DynInputError>;
59
60    /// Try to spend a transaction input. On success all necessary updates will
61    /// be part of the database transaction. On failure (e.g. double spend)
62    /// the database transaction is rolled back and the operation will take
63    /// no effect.
64    async fn process_input<'a, 'b, 'c>(
65        &'a self,
66        dbtx: &mut DatabaseTransaction<'c>,
67        input: &'b DynInput,
68    ) -> Result<InputMeta, DynInputError>;
69
70    /// Try to create an output (e.g. issue notes, peg-out BTC, …). On success
71    /// all necessary updates to the database will be part of the database
72    /// transaction. On failure (e.g. double spend) the database transaction
73    /// is rolled back and the operation will take no effect.
74    ///
75    /// The supplied `out_point` identifies the operation (e.g. a peg-out or
76    /// note issuance) and can be used to retrieve its outcome later using
77    /// `output_status`.
78    async fn process_output<'a>(
79        &self,
80        dbtx: &mut DatabaseTransaction<'a>,
81        output: &DynOutput,
82        out_point: OutPoint,
83    ) -> Result<TransactionItemAmount, DynOutputError>;
84
85    /// Retrieve the current status of the output. Depending on the module this
86    /// might contain data needed by the client to access funds or give an
87    /// estimate of when funds will be available. Returns `None` if the
88    /// output is unknown, **NOT** if it is just not ready yet.
89    async fn output_status(
90        &self,
91        dbtx: &mut DatabaseTransaction<'_>,
92        out_point: OutPoint,
93        module_instance_id: ModuleInstanceId,
94    ) -> Option<DynOutputOutcome>;
95
96    /// Queries the database and returns all assets and liabilities of the
97    /// module.
98    ///
99    /// Summing over all modules, if liabilities > assets then an error has
100    /// occurred in the database and consensus should halt.
101    async fn audit(
102        &self,
103        dbtx: &mut DatabaseTransaction<'_>,
104        audit: &mut Audit,
105        module_instance_id: ModuleInstanceId,
106    );
107
108    /// Returns a list of custom API endpoints defined by the module. These are
109    /// made available both to users as well as to other modules. They thus
110    /// should be deterministic, only dependant on their input and the
111    /// current epoch.
112    fn api_endpoints(&self) -> Vec<ApiEndpoint<DynServerModule>>;
113}
114
115dyn_newtype_define!(
116    #[derive(Clone)]
117    pub DynServerModule(Arc<IServerModule>)
118);
119
120#[apply(async_trait_maybe_send!)]
121impl<T> IServerModule for T
122where
123    T: ServerModule + 'static + Sync,
124{
125    fn decoder(&self) -> Decoder {
126        <T::Common as ModuleCommon>::decoder_builder().build()
127    }
128
129    fn as_any(&self) -> &dyn Any {
130        self
131    }
132
133    fn module_kind(&self) -> ModuleKind {
134        <Self as ServerModule>::module_kind()
135    }
136
137    /// This module's contribution to the next consensus proposal
138    async fn consensus_proposal(
139        &self,
140        dbtx: &mut DatabaseTransaction<'_>,
141        module_instance_id: ModuleInstanceId,
142    ) -> Vec<DynModuleConsensusItem> {
143        <Self as ServerModule>::consensus_proposal(self, dbtx)
144            .await
145            .into_iter()
146            .map(|v| DynModuleConsensusItem::from_typed(module_instance_id, v))
147            .collect()
148    }
149
150    /// This function is called once for every consensus item. The function
151    /// returns an error if any only if the consensus item does not change
152    /// our state and therefore may be safely discarded by the atomic broadcast.
153    async fn process_consensus_item<'a, 'b>(
154        &self,
155        dbtx: &mut DatabaseTransaction<'a>,
156        consensus_item: &'b DynModuleConsensusItem,
157        peer_id: PeerId,
158    ) -> anyhow::Result<()> {
159        <Self as ServerModule>::process_consensus_item(
160            self,
161            dbtx,
162            Clone::clone(
163                consensus_item.as_any()
164                    .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::ConsensusItem>()
165                    .expect("incorrect consensus item type passed to module plugin"),
166            ),
167            peer_id
168        )
169        .await
170    }
171
172    // Use this function to parallelise stateless cryptographic verification of
173    // inputs across a transaction. All inputs of a transaction are verified
174    // before any input is processed.
175    fn verify_input(&self, input: &DynInput) -> Result<(), DynInputError> {
176        <Self as ServerModule>::verify_input(
177            self,
178            input
179                .as_any()
180                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Input>()
181                .expect("incorrect input type passed to module plugin"),
182        )
183        .map_err(|v| DynInputError::from_typed(input.module_instance_id(), v))
184    }
185
186    /// Try to spend a transaction input. On success all necessary updates will
187    /// be part of the database transaction. On failure (e.g. double spend)
188    /// the database transaction is rolled back and the operation will take
189    /// no effect.
190    async fn process_input<'a, 'b, 'c>(
191        &'a self,
192        dbtx: &mut DatabaseTransaction<'c>,
193        input: &'b DynInput,
194    ) -> Result<InputMeta, DynInputError> {
195        <Self as ServerModule>::process_input(
196            self,
197            dbtx,
198            input
199                .as_any()
200                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Input>()
201                .expect("incorrect input type passed to module plugin"),
202        )
203        .await
204        .map(Into::into)
205        .map_err(|v| DynInputError::from_typed(input.module_instance_id(), v))
206    }
207
208    /// Try to create an output (e.g. issue notes, peg-out BTC, …). On success
209    /// all necessary updates to the database will be part of the database
210    /// transaction. On failure (e.g. double spend) the database transaction
211    /// is rolled back and the operation will take no effect.
212    ///
213    /// The supplied `out_point` identifies the operation (e.g. a peg-out or
214    /// note issuance) and can be used to retrieve its outcome later using
215    /// `output_status`.
216    async fn process_output<'a>(
217        &self,
218        dbtx: &mut DatabaseTransaction<'a>,
219        output: &DynOutput,
220        out_point: OutPoint,
221    ) -> Result<TransactionItemAmount, DynOutputError> {
222        <Self as ServerModule>::process_output(
223            self,
224            dbtx,
225            output
226                .as_any()
227                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Output>()
228                .expect("incorrect output type passed to module plugin"),
229            out_point,
230        )
231        .await
232        .map_err(|v| DynOutputError::from_typed(output.module_instance_id(), v))
233    }
234
235    /// Retrieve the current status of the output. Depending on the module this
236    /// might contain data needed by the client to access funds or give an
237    /// estimate of when funds will be available. Returns `None` if the
238    /// output is unknown, **NOT** if it is just not ready yet.
239    async fn output_status(
240        &self,
241        dbtx: &mut DatabaseTransaction<'_>,
242        out_point: OutPoint,
243        module_instance_id: ModuleInstanceId,
244    ) -> Option<DynOutputOutcome> {
245        <Self as ServerModule>::output_status(self, dbtx, out_point)
246            .await
247            .map(|v| DynOutputOutcome::from_typed(module_instance_id, v))
248    }
249
250    /// Queries the database and returns all assets and liabilities of the
251    /// module.
252    ///
253    /// Summing over all modules, if liabilities > assets then an error has
254    /// occurred in the database and consensus should halt.
255    async fn audit(
256        &self,
257        dbtx: &mut DatabaseTransaction<'_>,
258        audit: &mut Audit,
259        module_instance_id: ModuleInstanceId,
260    ) {
261        <Self as ServerModule>::audit(self, dbtx, audit, module_instance_id).await;
262    }
263
264    fn api_endpoints(&self) -> Vec<ApiEndpoint<DynServerModule>> {
265        <Self as ServerModule>::api_endpoints(self)
266            .into_iter()
267            .map(|ApiEndpoint { path, handler }| ApiEndpoint {
268                path,
269                handler: Box::new(
270                    move |module: &DynServerModule,
271                          context: ApiEndpointContext<'_>,
272                          value: ApiRequestErased| {
273                        let typed_module = module
274                            .as_any()
275                            .downcast_ref::<T>()
276                            .expect("the dispatcher should always call with the right module");
277                        Box::pin(handler(typed_module, context, value))
278                    },
279                ),
280            })
281            .collect()
282    }
283}