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