Skip to main content

fedimint_server_core/
lib.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.
7
8pub mod bitcoin_rpc;
9pub mod config;
10pub mod dashboard_ui;
11mod init;
12pub mod migration;
13pub mod setup_ui;
14
15use std::any::Any;
16use std::collections::BTreeMap;
17use std::fmt::Debug;
18use std::sync::Arc;
19
20use fedimint_core::core::{
21    Decoder, DynInput, DynInputError, DynModuleConsensusItem, DynOutput, DynOutputError,
22    DynOutputOutcome, ModuleInstanceId, ModuleKind,
23};
24use fedimint_core::db::DatabaseTransaction;
25use fedimint_core::module::audit::Audit;
26use fedimint_core::module::registry::{ModuleDecoderRegistry, ModuleRegistry};
27use fedimint_core::module::{
28    ApiEndpoint, ApiEndpointContext, ApiRequestErased, ApiVersion, CommonModuleInit, InputMeta,
29    ModuleCommon, ModuleInit, MultiApiVersion, TransactionItemAmounts,
30};
31use fedimint_core::{InPoint, OutPoint, PeerId, apply, async_trait_maybe_send, dyn_newtype_define};
32pub use init::*;
33
34#[apply(async_trait_maybe_send!)]
35pub trait ServerModule: Debug + Sized {
36    type Common: ModuleCommon;
37
38    type Init: ServerModuleInit;
39
40    fn module_kind() -> ModuleKind {
41        // Note: All modules should define kinds as &'static str, so this doesn't
42        // allocate
43        <Self::Init as ModuleInit>::Common::KIND
44    }
45
46    /// Returns a decoder for the following associated types of this module:
47    /// * `ClientConfig`
48    /// * `Input`
49    /// * `Output`
50    /// * `OutputOutcome`
51    /// * `ConsensusItem`
52    /// * `InputError`
53    /// * `OutputError`
54    fn decoder() -> Decoder {
55        Self::Common::decoder_builder().build()
56    }
57
58    /// This module's contribution to the next consensus proposal. This method
59    /// is only guaranteed to be called once every few seconds. Consensus items
60    /// are not meant to be latency critical; do not create them as
61    /// a response to a processed transaction. Only use consensus items to
62    /// establish consensus on a value that is required to verify
63    /// transactions, like unix time, block heights and feerates, and model all
64    /// other state changes trough transactions. The intention for this method
65    /// is to always return all available consensus items even if they are
66    /// redundant while process_consensus_item returns an error for the
67    /// redundant proposals.
68    ///
69    /// If you think you actually do require latency critical consensus items or
70    /// have trouble designing your module in order to avoid them please contact
71    /// the Fedimint developers.
72    async fn consensus_proposal<'a>(
73        &'a self,
74        dbtx: &mut DatabaseTransaction<'_>,
75    ) -> Vec<<Self::Common as ModuleCommon>::ConsensusItem>;
76
77    /// This function is called once for every consensus item. The function
78    /// should return Ok if and only if the consensus item changes
79    /// the system state. *Therefore this method should return an error in case
80    /// of merely redundant consensus items such that they will be purged from
81    /// the history of the federation.* This enables consensus_proposal to
82    /// return all available consensus item without wasting disk
83    /// space with redundant consensus items.
84    async fn process_consensus_item<'a, 'b>(
85        &'a self,
86        dbtx: &mut DatabaseTransaction<'b>,
87        consensus_item: <Self::Common as ModuleCommon>::ConsensusItem,
88        peer_id: PeerId,
89    ) -> anyhow::Result<()>;
90
91    // Use this function to parallelise stateless cryptographic verification of
92    // inputs across a transaction. All inputs of a transaction are verified
93    // before any input is processed.
94    fn verify_input(
95        &self,
96        _input: &<Self::Common as ModuleCommon>::Input,
97    ) -> Result<(), <Self::Common as ModuleCommon>::InputError> {
98        Ok(())
99    }
100
101    /// Try to spend a transaction input. On success all necessary updates will
102    /// be part of the database transaction. On failure (e.g. double spend)
103    /// the database transaction is rolled back and the operation will take
104    /// no effect.
105    async fn process_input<'a, 'b, 'c>(
106        &'a self,
107        dbtx: &mut DatabaseTransaction<'c>,
108        input: &'b <Self::Common as ModuleCommon>::Input,
109        in_point: InPoint,
110    ) -> Result<InputMeta, <Self::Common as ModuleCommon>::InputError>;
111
112    /// Try to create an output (e.g. issue notes, peg-out BTC, …). On success
113    /// all necessary updates to the database will be part of the database
114    /// transaction. On failure (e.g. double spend) the database transaction
115    /// is rolled back and the operation will take no effect.
116    ///
117    /// The supplied `out_point` identifies the operation (e.g. a peg-out or
118    /// note issuance) and can be used to retrieve its outcome later using
119    /// `output_status`.
120    async fn process_output<'a, 'b>(
121        &'a self,
122        dbtx: &mut DatabaseTransaction<'b>,
123        output: &'a <Self::Common as ModuleCommon>::Output,
124        out_point: OutPoint,
125    ) -> Result<TransactionItemAmounts, <Self::Common as ModuleCommon>::OutputError>;
126
127    /// **Deprecated**: Modules should not be using it. Instead, they should
128    /// implement their own custom endpoints with semantics, versioning,
129    /// serialization, etc. that suits them. Potentially multiple or none.
130    ///
131    /// Depending on the module this might contain data needed by the client to
132    /// access funds or give an estimate of when funds will be available.
133    ///
134    /// Returns `None` if the output is unknown, **NOT** if it is just not ready
135    /// yet.
136    ///
137    /// Since this has become deprecated you may return `None` even if the
138    /// output is known as long as the output outcome is not used inside the
139    /// module.
140    #[deprecated(note = "https://github.com/fedimint/fedimint/issues/6671")]
141    async fn output_status(
142        &self,
143        dbtx: &mut DatabaseTransaction<'_>,
144        out_point: OutPoint,
145    ) -> Option<<Self::Common as ModuleCommon>::OutputOutcome>;
146
147    /// Verify submission-only checks for an input
148    ///
149    /// Most modules should not need to know or implement it, so the default
150    /// implementation just returns OK.
151    ///
152    /// In special circumstances it is useful to enforce requirements on the
153    /// included transaction outside of the consensus, in a similar way
154    /// Bitcoin enforces mempool policies.
155    ///
156    /// This functionality might be removed in the future versions, as more
157    /// checks become part of the consensus, so it is advised not to use it.
158    #[doc(hidden)]
159    async fn verify_input_submission<'a, 'b, 'c>(
160        &'a self,
161        _dbtx: &mut DatabaseTransaction<'c>,
162        _input: &'b <Self::Common as ModuleCommon>::Input,
163    ) -> Result<(), <Self::Common as ModuleCommon>::InputError> {
164        Ok(())
165    }
166
167    /// Verify submission-only checks for an output
168    ///
169    /// See [`Self::verify_input_submission`] for more information.
170    #[doc(hidden)]
171    async fn verify_output_submission<'a, 'b>(
172        &'a self,
173        _dbtx: &mut DatabaseTransaction<'b>,
174        _output: &'a <Self::Common as ModuleCommon>::Output,
175        _out_point: OutPoint,
176    ) -> Result<(), <Self::Common as ModuleCommon>::OutputError> {
177        Ok(())
178    }
179
180    /// Queries the database and returns all assets and liabilities of the
181    /// module.
182    ///
183    /// Summing over all modules, if liabilities > assets then an error has
184    /// occurred in the database and consensus should halt.
185    async fn audit(
186        &self,
187        dbtx: &mut DatabaseTransaction<'_>,
188        audit: &mut Audit,
189        module_instance_id: ModuleInstanceId,
190    );
191
192    /// Returns a list of custom API endpoints defined by the module. These are
193    /// made available both to users as well as to other modules. They thus
194    /// should be deterministic, only dependant on their input and the
195    /// current epoch.
196    fn api_endpoints(&self) -> Vec<ApiEndpoint<Self>>;
197
198    /// Returns the module API versions supported by this module instance.
199    ///
200    /// By default this derives the advertised API version from the maximum API
201    /// version found in the endpoint annotations. This works as long as every
202    /// API minor bump introduces at least one endpoint. Modules that need to
203    /// advertise a version bump for semantic changes to existing endpoints can
204    /// override this method without rewriting the endpoints' introduced-at
205    /// annotations.
206    fn supported_api_versions(&self) -> MultiApiVersion {
207        api_versions_from_endpoints(self.api_endpoints())
208    }
209}
210
211/// Backend side module interface
212///
213/// Server side Fedimint module needs to implement this trait.
214#[apply(async_trait_maybe_send!)]
215pub trait IServerModule: Debug {
216    fn as_any(&self) -> &dyn Any;
217
218    /// Returns the decoder belonging to the server module
219    fn decoder(&self) -> Decoder;
220
221    fn module_kind(&self) -> ModuleKind;
222
223    /// This module's contribution to the next consensus proposal
224    async fn consensus_proposal(
225        &self,
226        dbtx: &mut DatabaseTransaction<'_>,
227        module_instance_id: ModuleInstanceId,
228    ) -> Vec<DynModuleConsensusItem>;
229
230    /// This function is called once for every consensus item. The function
231    /// returns an error if any only if the consensus item does not change
232    /// our state and therefore may be safely discarded by the atomic broadcast.
233    async fn process_consensus_item<'a, 'b>(
234        &self,
235        dbtx: &mut DatabaseTransaction<'a>,
236        consensus_item: &'b DynModuleConsensusItem,
237        peer_id: PeerId,
238    ) -> anyhow::Result<()>;
239
240    // Use this function to parallelise stateless cryptographic verification of
241    // inputs across a transaction. All inputs of a transaction are verified
242    // before any input is processed.
243    fn verify_input(&self, input: &DynInput) -> Result<(), DynInputError>;
244
245    /// Try to spend a transaction input. On success all necessary updates will
246    /// be part of the database transaction. On failure (e.g. double spend)
247    /// the database transaction is rolled back and the operation will take
248    /// no effect.
249    async fn process_input<'a, 'b, 'c>(
250        &'a self,
251        dbtx: &mut DatabaseTransaction<'c>,
252        input: &'b DynInput,
253        in_point: InPoint,
254    ) -> Result<InputMeta, DynInputError>;
255
256    /// Try to create an output (e.g. issue notes, peg-out BTC, …). On success
257    /// all necessary updates to the database will be part of the database
258    /// transaction. On failure (e.g. double spend) the database transaction
259    /// is rolled back and the operation will take no effect.
260    ///
261    /// The supplied `out_point` identifies the operation (e.g. a peg-out or
262    /// note issuance) and can be used to retrieve its outcome later using
263    /// `output_status`.
264    async fn process_output<'a>(
265        &self,
266        dbtx: &mut DatabaseTransaction<'a>,
267        output: &DynOutput,
268        out_point: OutPoint,
269    ) -> Result<TransactionItemAmounts, DynOutputError>;
270
271    /// See [`ServerModule::verify_input_submission`]
272    #[doc(hidden)]
273    async fn verify_input_submission<'a, 'b, 'c>(
274        &'a self,
275        dbtx: &mut DatabaseTransaction<'c>,
276        input: &'b DynInput,
277    ) -> Result<(), DynInputError>;
278
279    /// See [`ServerModule::verify_output_submission`]
280    #[doc(hidden)]
281    async fn verify_output_submission<'a>(
282        &self,
283        _dbtx: &mut DatabaseTransaction<'a>,
284        _output: &DynOutput,
285        _out_point: OutPoint,
286    ) -> Result<(), DynOutputError>;
287
288    /// See [`ServerModule::output_status`]
289    #[deprecated(note = "https://github.com/fedimint/fedimint/issues/6671")]
290    async fn output_status(
291        &self,
292        dbtx: &mut DatabaseTransaction<'_>,
293        out_point: OutPoint,
294        module_instance_id: ModuleInstanceId,
295    ) -> Option<DynOutputOutcome>;
296
297    /// Queries the database and returns all assets and liabilities of the
298    /// module.
299    ///
300    /// Summing over all modules, if liabilities > assets then an error has
301    /// occurred in the database and consensus should halt.
302    async fn audit(
303        &self,
304        dbtx: &mut DatabaseTransaction<'_>,
305        audit: &mut Audit,
306        module_instance_id: ModuleInstanceId,
307    );
308
309    /// Returns a list of custom API endpoints defined by the module. These are
310    /// made available both to users as well as to other modules. They thus
311    /// should be deterministic, only dependant on their input and the
312    /// current epoch.
313    fn api_endpoints(&self) -> Vec<ApiEndpoint<DynServerModule>>;
314
315    /// Returns the module API versions supported by this module instance.
316    fn supported_api_versions(&self) -> MultiApiVersion;
317}
318
319dyn_newtype_define!(
320    #[derive(Clone)]
321    pub DynServerModule(Arc<IServerModule>)
322);
323
324#[apply(async_trait_maybe_send!)]
325impl<T> IServerModule for T
326where
327    T: ServerModule + 'static + Sync,
328{
329    fn decoder(&self) -> Decoder {
330        <T::Common as ModuleCommon>::decoder_builder().build()
331    }
332
333    fn as_any(&self) -> &dyn Any {
334        self
335    }
336
337    fn module_kind(&self) -> ModuleKind {
338        <Self as ServerModule>::module_kind()
339    }
340
341    /// This module's contribution to the next consensus proposal
342    async fn consensus_proposal(
343        &self,
344        dbtx: &mut DatabaseTransaction<'_>,
345        module_instance_id: ModuleInstanceId,
346    ) -> Vec<DynModuleConsensusItem> {
347        <Self as ServerModule>::consensus_proposal(self, dbtx)
348            .await
349            .into_iter()
350            .map(|v| DynModuleConsensusItem::from_typed(module_instance_id, v))
351            .collect()
352    }
353
354    /// This function is called once for every consensus item. The function
355    /// returns an error if any only if the consensus item does not change
356    /// our state and therefore may be safely discarded by the atomic broadcast.
357    async fn process_consensus_item<'a, 'b>(
358        &self,
359        dbtx: &mut DatabaseTransaction<'a>,
360        consensus_item: &'b DynModuleConsensusItem,
361        peer_id: PeerId,
362    ) -> anyhow::Result<()> {
363        <Self as ServerModule>::process_consensus_item(
364            self,
365            dbtx,
366            Clone::clone(
367                consensus_item.as_any()
368                    .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::ConsensusItem>()
369                    .expect("incorrect consensus item type passed to module plugin"),
370            ),
371            peer_id
372        )
373        .await
374    }
375
376    // Use this function to parallelise stateless cryptographic verification of
377    // inputs across a transaction. All inputs of a transaction are verified
378    // before any input is processed.
379    fn verify_input(&self, input: &DynInput) -> Result<(), DynInputError> {
380        <Self as ServerModule>::verify_input(
381            self,
382            input
383                .as_any()
384                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Input>()
385                .expect("incorrect input type passed to module plugin"),
386        )
387        .map_err(|v| DynInputError::from_typed(input.module_instance_id(), v))
388    }
389
390    /// Try to spend a transaction input. On success all necessary updates will
391    /// be part of the database transaction. On failure (e.g. double spend)
392    /// the database transaction is rolled back and the operation will take
393    /// no effect.
394    async fn process_input<'a, 'b, 'c>(
395        &'a self,
396        dbtx: &mut DatabaseTransaction<'c>,
397        input: &'b DynInput,
398        in_point: InPoint,
399    ) -> Result<InputMeta, DynInputError> {
400        <Self as ServerModule>::process_input(
401            self,
402            dbtx,
403            input
404                .as_any()
405                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Input>()
406                .expect("incorrect input type passed to module plugin"),
407            in_point,
408        )
409        .await
410        .map_err(|v| DynInputError::from_typed(input.module_instance_id(), v))
411    }
412
413    /// Try to create an output (e.g. issue notes, peg-out BTC, …). On success
414    /// all necessary updates to the database will be part of the database
415    /// transaction. On failure (e.g. double spend) the database transaction
416    /// is rolled back and the operation will take no effect.
417    ///
418    /// The supplied `out_point` identifies the operation (e.g. a peg-out or
419    /// note issuance) and can be used to retrieve its outcome later using
420    /// `output_status`.
421    async fn process_output<'a>(
422        &self,
423        dbtx: &mut DatabaseTransaction<'a>,
424        output: &DynOutput,
425        out_point: OutPoint,
426    ) -> Result<TransactionItemAmounts, DynOutputError> {
427        <Self as ServerModule>::process_output(
428            self,
429            dbtx,
430            output
431                .as_any()
432                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Output>()
433                .expect("incorrect output type passed to module plugin"),
434            out_point,
435        )
436        .await
437        .map_err(|v| DynOutputError::from_typed(output.module_instance_id(), v))
438    }
439
440    async fn verify_input_submission<'a, 'b, 'c>(
441        &'a self,
442        dbtx: &mut DatabaseTransaction<'c>,
443        input: &'b DynInput,
444    ) -> Result<(), DynInputError> {
445        <Self as ServerModule>::verify_input_submission(
446            self,
447            dbtx,
448            input
449                .as_any()
450                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Input>()
451                .expect("incorrect input type passed to module plugin"),
452        )
453        .await
454        .map_err(|v| DynInputError::from_typed(input.module_instance_id(), v))
455    }
456
457    async fn verify_output_submission<'a>(
458        &self,
459        dbtx: &mut DatabaseTransaction<'a>,
460        output: &DynOutput,
461        out_point: OutPoint,
462    ) -> Result<(), DynOutputError> {
463        <Self as ServerModule>::verify_output_submission(
464            self,
465            dbtx,
466            output
467                .as_any()
468                .downcast_ref::<<<Self as ServerModule>::Common as ModuleCommon>::Output>()
469                .expect("incorrect output type passed to module plugin"),
470            out_point,
471        )
472        .await
473        .map_err(|v| DynOutputError::from_typed(output.module_instance_id(), v))
474    }
475
476    /// See [`ServerModule::output_status`]
477    async fn output_status(
478        &self,
479        dbtx: &mut DatabaseTransaction<'_>,
480        out_point: OutPoint,
481        module_instance_id: ModuleInstanceId,
482    ) -> Option<DynOutputOutcome> {
483        #[allow(deprecated)]
484        <Self as ServerModule>::output_status(self, dbtx, out_point)
485            .await
486            .map(|v| DynOutputOutcome::from_typed(module_instance_id, v))
487    }
488
489    /// Queries the database and returns all assets and liabilities of the
490    /// module.
491    ///
492    /// Summing over all modules, if liabilities > assets then an error has
493    /// occurred in the database and consensus should halt.
494    async fn audit(
495        &self,
496        dbtx: &mut DatabaseTransaction<'_>,
497        audit: &mut Audit,
498        module_instance_id: ModuleInstanceId,
499    ) {
500        <Self as ServerModule>::audit(self, dbtx, audit, module_instance_id).await;
501    }
502
503    fn api_endpoints(&self) -> Vec<ApiEndpoint<DynServerModule>> {
504        <Self as ServerModule>::api_endpoints(self)
505            .into_iter()
506            .map(
507                |ApiEndpoint {
508                     path,
509                     version,
510                     handler,
511                 }| ApiEndpoint {
512                    path,
513                    version,
514                    handler: Box::new(
515                        move |module: &DynServerModule,
516                              context: ApiEndpointContext,
517                              value: ApiRequestErased| {
518                            let typed_module = module
519                                .as_any()
520                                .downcast_ref::<T>()
521                                .expect("the dispatcher should always call with the right module");
522                            Box::pin(handler(typed_module, context, value))
523                        },
524                    ),
525                },
526            )
527            .collect()
528    }
529
530    fn supported_api_versions(&self) -> MultiApiVersion {
531        <Self as ServerModule>::supported_api_versions(self)
532    }
533}
534
535fn api_versions_from_endpoints<M>(
536    endpoints: impl IntoIterator<Item = ApiEndpoint<M>>,
537) -> MultiApiVersion {
538    let mut api_map: BTreeMap<u32, u32> = BTreeMap::new();
539    for endpoint in endpoints {
540        let minor = api_map.entry(endpoint.version.major).or_insert(0);
541        *minor = (*minor).max(endpoint.version.minor);
542    }
543
544    if api_map.is_empty() {
545        api_map.insert(0, 0);
546    }
547
548    MultiApiVersion::try_from_iter(
549        api_map
550            .into_iter()
551            .map(|(major, minor)| ApiVersion { major, minor }),
552    )
553    .expect("api versions are grouped by major before constructing MultiApiVersion")
554}
555
556#[cfg(test)]
557mod tests;
558
559/// Collection of server modules
560pub type ServerModuleRegistry = ModuleRegistry<DynServerModule>;
561
562pub trait ServerModuleRegistryExt {
563    fn decoder_registry(&self) -> ModuleDecoderRegistry;
564}
565
566impl ServerModuleRegistryExt for ServerModuleRegistry {
567    /// Generate a `ModuleDecoderRegistry` from this `ModuleRegistry`
568    fn decoder_registry(&self) -> ModuleDecoderRegistry {
569        // TODO: cache decoders
570        self.iter_modules()
571            .map(|(id, kind, module)| (id, kind.clone(), module.decoder()))
572            .collect::<ModuleDecoderRegistry>()
573    }
574}