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 }