fedimint_server_core/
init.rs

1// TODO: remove and fix nits
2#![allow(clippy::pedantic)]
3
4use std::collections::{BTreeMap, BTreeSet};
5use std::marker::PhantomData;
6use std::sync::Arc;
7use std::{any, marker};
8
9use fedimint_api_client::api::DynModuleApi;
10use fedimint_core::config::{
11    ClientModuleConfig, CommonModuleInitRegistry, ConfigGenModuleParams, ModuleInitParams,
12    ModuleInitRegistry, ServerModuleConfig, ServerModuleConsensusConfig,
13};
14use fedimint_core::core::{ModuleInstanceId, ModuleKind};
15use fedimint_core::db::{Database, DatabaseVersion};
16use fedimint_core::module::{
17    CommonModuleInit, CoreConsensusVersion, IDynCommonModuleInit, ModuleConsensusVersion,
18    ModuleInit, SupportedModuleApiVersions,
19};
20use fedimint_core::task::TaskGroup;
21use fedimint_core::{NumPeers, PeerId, apply, async_trait_maybe_send, dyn_newtype_define};
22
23use crate::config::PeerHandleOps;
24use crate::migration::{
25    DynServerDbMigrationFn, ServerDbMigrationFnContext, ServerModuleDbMigrationContext,
26    ServerModuleDbMigrationFn,
27};
28use crate::{DynServerModule, ServerModule};
29
30/// Interface for Module Generation
31///
32/// This trait contains the methods responsible for the module's
33/// - initialization
34/// - config generation
35/// - config validation
36///
37/// Once the module configuration is ready, the module can be instantiated via
38/// `[Self::init]`.
39#[apply(async_trait_maybe_send!)]
40pub trait IServerModuleInit: IDynCommonModuleInit {
41    fn as_common(&self) -> &(dyn IDynCommonModuleInit + Send + Sync + 'static);
42
43    fn supported_api_versions(&self) -> SupportedModuleApiVersions;
44
45    /// Initialize the [`DynServerModule`] instance from its config
46    #[allow(clippy::too_many_arguments)]
47    async fn init(
48        &self,
49        peer_num: NumPeers,
50        cfg: ServerModuleConfig,
51        db: Database,
52        task_group: &TaskGroup,
53        our_peer_id: PeerId,
54        module_api: DynModuleApi,
55        shared: SharedAnymap,
56    ) -> anyhow::Result<DynServerModule>;
57
58    fn validate_params(&self, params: &ConfigGenModuleParams) -> anyhow::Result<()>;
59
60    fn trusted_dealer_gen(
61        &self,
62        peers: &[PeerId],
63        params: &ConfigGenModuleParams,
64    ) -> BTreeMap<PeerId, ServerModuleConfig>;
65
66    async fn distributed_gen(
67        &self,
68        peers: &(dyn PeerHandleOps + Send + Sync),
69        params: &ConfigGenModuleParams,
70    ) -> anyhow::Result<ServerModuleConfig>;
71
72    fn validate_config(&self, identity: &PeerId, config: ServerModuleConfig) -> anyhow::Result<()>;
73
74    fn get_client_config(
75        &self,
76        module_instance_id: ModuleInstanceId,
77        config: &ServerModuleConsensusConfig,
78    ) -> anyhow::Result<ClientModuleConfig>;
79
80    /// Retrieves the migrations map from the server module to be applied to the
81    /// database before the module is initialized. The migrations map is
82    /// indexed on the from version.
83    fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, DynServerDbMigrationFn>;
84
85    /// See [`ServerModuleInit::used_db_prefixes`]
86    fn used_db_prefixes(&self) -> Option<BTreeSet<u8>>;
87}
88
89/// A type that can be used as module-shared value inside
90/// [`ServerModuleInitArgs`]
91pub trait ServerModuleShared: any::Any + Send + Sync {
92    fn new(task_group: TaskGroup) -> Self;
93}
94
95type SharedAnymap = Arc<std::sync::RwLock<BTreeMap<any::TypeId, Box<dyn any::Any + Send + Sync>>>>;
96
97pub struct ServerModuleInitArgs<S>
98where
99    S: ServerModuleInit,
100{
101    cfg: ServerModuleConfig,
102    db: Database,
103    task_group: TaskGroup,
104    our_peer_id: PeerId,
105    num_peers: NumPeers,
106    module_api: DynModuleApi,
107    // Things that can be shared between modules
108    //
109    // If two modules can coordinate on using a shared type, they can
110    // share something e.g. for caching or resource usage purposes.
111    shared: SharedAnymap,
112    // ClientModuleInitArgs needs a bound because sometimes we need
113    // to pass associated-types data, so let's just put it here right away
114    _marker: marker::PhantomData<S>,
115}
116
117impl<S> ServerModuleInitArgs<S>
118where
119    S: ServerModuleInit,
120{
121    pub fn cfg(&self) -> &ServerModuleConfig {
122        &self.cfg
123    }
124
125    pub fn db(&self) -> &Database {
126        &self.db
127    }
128
129    pub fn num_peers(&self) -> NumPeers {
130        self.num_peers
131    }
132
133    pub fn task_group(&self) -> &TaskGroup {
134        &self.task_group
135    }
136
137    pub fn our_peer_id(&self) -> PeerId {
138        self.our_peer_id
139    }
140
141    pub fn module_api(&self) -> &DynModuleApi {
142        &self.module_api
143    }
144
145    pub fn with_shared<T, R>(&self, f: impl FnOnce(&T) -> R) -> R
146    where
147        T: ServerModuleShared,
148    {
149        let type_id = any::TypeId::of::<T>();
150        let mut write = self.shared.write().expect("Locking failed");
151
152        let _ = write
153            .entry(type_id)
154            .or_insert_with(|| Box::new(<T as ServerModuleShared>::new(self.task_group.clone())));
155        // TODO: When stabilized:
156        // let read = std::sync::RwLockWriteGuard::<'_, _>::downgrade(write);
157        drop(write);
158        let read = self.shared.read().expect("Locking failed");
159        let t: &T = read
160            .get(&type_id)
161            .expect("Must be there, just inserted")
162            .as_ref()
163            .downcast_ref()
164            .expect("Can't fail, must be of expected type");
165
166        f(t)
167    }
168
169    pub fn shared<T>(&self) -> T
170    where
171        T: ServerModuleShared + Clone,
172    {
173        self.with_shared::<T, T>(|t| t.clone())
174    }
175}
176/// Module Generation trait with associated types
177///
178/// Needs to be implemented by module generation type
179///
180/// For examples, take a look at one of the `MintConfigGenerator`,
181/// `WalletConfigGenerator`, or `LightningConfigGenerator` structs.
182#[apply(async_trait_maybe_send!)]
183pub trait ServerModuleInit: ModuleInit + Sized {
184    type Module: ServerModule + Send + Sync;
185    type Params: ModuleInitParams;
186
187    /// Version of the module consensus supported by this implementation given a
188    /// certain [`CoreConsensusVersion`].
189    ///
190    /// Refer to [`ModuleConsensusVersion`] for more information about
191    /// versioning.
192    ///
193    /// One module implementation ([`ServerModuleInit`] of a given
194    /// [`ModuleKind`]) can potentially implement multiple versions of the
195    /// consensus, and depending on the config module instance config,
196    /// instantiate the desired one. This method should expose all the
197    /// available versions, purely for information, setup UI and sanity
198    /// checking purposes.
199    fn versions(&self, core: CoreConsensusVersion) -> &[ModuleConsensusVersion];
200
201    fn supported_api_versions(&self) -> SupportedModuleApiVersions;
202
203    fn kind() -> ModuleKind {
204        <Self as ModuleInit>::Common::KIND
205    }
206
207    /// Initialize the module instance from its config
208    async fn init(&self, args: &ServerModuleInitArgs<Self>) -> anyhow::Result<Self::Module>;
209
210    fn parse_params(&self, params: &ConfigGenModuleParams) -> anyhow::Result<Self::Params> {
211        params.to_typed::<Self::Params>()
212    }
213
214    fn trusted_dealer_gen(
215        &self,
216        peers: &[PeerId],
217        params: &ConfigGenModuleParams,
218    ) -> BTreeMap<PeerId, ServerModuleConfig>;
219
220    async fn distributed_gen(
221        &self,
222        peers: &(dyn PeerHandleOps + Send + Sync),
223        params: &ConfigGenModuleParams,
224    ) -> anyhow::Result<ServerModuleConfig>;
225
226    fn validate_config(&self, identity: &PeerId, config: ServerModuleConfig) -> anyhow::Result<()>;
227
228    /// Converts the consensus config into the client config
229    fn get_client_config(
230        &self,
231        config: &ServerModuleConsensusConfig,
232    ) -> anyhow::Result<<<Self as ModuleInit>::Common as CommonModuleInit>::ClientConfig>;
233
234    /// Retrieves the migrations map from the server module to be applied to the
235    /// database before the module is initialized. The migrations map is
236    /// indexed on the from version.
237    fn get_database_migrations(
238        &self,
239    ) -> BTreeMap<DatabaseVersion, ServerModuleDbMigrationFn<Self::Module>> {
240        BTreeMap::new()
241    }
242
243    /// Db prefixes used by the module
244    ///
245    /// If `Some` is returned, it should contain list of database
246    /// prefixes actually used by the module for it's keys.
247    ///
248    /// In (some subset of) non-production tests,
249    /// module database will be scanned for presence of keys
250    /// that do not belong to this list to verify integrity
251    /// of data and possibly catch any unforeseen bugs.
252    fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
253        None
254    }
255}
256
257#[apply(async_trait_maybe_send!)]
258impl<T> IServerModuleInit for T
259where
260    T: ServerModuleInit + 'static + Sync,
261{
262    fn as_common(&self) -> &(dyn IDynCommonModuleInit + Send + Sync + 'static) {
263        self
264    }
265
266    fn supported_api_versions(&self) -> SupportedModuleApiVersions {
267        <Self as ServerModuleInit>::supported_api_versions(self)
268    }
269
270    async fn init(
271        &self,
272        num_peers: NumPeers,
273        cfg: ServerModuleConfig,
274        db: Database,
275        task_group: &TaskGroup,
276        our_peer_id: PeerId,
277        module_api: DynModuleApi,
278        shared: SharedAnymap,
279    ) -> anyhow::Result<DynServerModule> {
280        let module = <Self as ServerModuleInit>::init(
281            self,
282            &ServerModuleInitArgs {
283                num_peers,
284                cfg,
285                db,
286                task_group: task_group.clone(),
287                our_peer_id,
288                _marker: PhantomData,
289                module_api,
290                shared,
291            },
292        )
293        .await?;
294
295        Ok(DynServerModule::from(module))
296    }
297
298    fn validate_params(&self, params: &ConfigGenModuleParams) -> anyhow::Result<()> {
299        <Self as ServerModuleInit>::parse_params(self, params)?;
300        Ok(())
301    }
302
303    fn trusted_dealer_gen(
304        &self,
305        peers: &[PeerId],
306        params: &ConfigGenModuleParams,
307    ) -> BTreeMap<PeerId, ServerModuleConfig> {
308        <Self as ServerModuleInit>::trusted_dealer_gen(self, peers, params)
309    }
310
311    async fn distributed_gen(
312        &self,
313        peers: &(dyn PeerHandleOps + Send + Sync),
314        params: &ConfigGenModuleParams,
315    ) -> anyhow::Result<ServerModuleConfig> {
316        <Self as ServerModuleInit>::distributed_gen(self, peers, params).await
317    }
318
319    fn validate_config(&self, identity: &PeerId, config: ServerModuleConfig) -> anyhow::Result<()> {
320        <Self as ServerModuleInit>::validate_config(self, identity, config)
321    }
322
323    fn get_client_config(
324        &self,
325        module_instance_id: ModuleInstanceId,
326        config: &ServerModuleConsensusConfig,
327    ) -> anyhow::Result<ClientModuleConfig> {
328        ClientModuleConfig::from_typed(
329            module_instance_id,
330            <Self as ServerModuleInit>::kind(),
331            config.version,
332            <Self as ServerModuleInit>::get_client_config(self, config)?,
333        )
334    }
335    fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, DynServerDbMigrationFn> {
336        <Self as ServerModuleInit>::get_database_migrations(self)
337            .into_iter()
338            .map(|(k, f)| {
339                (k, {
340                    let closure: DynServerDbMigrationFn =
341                        Box::new(move |ctx: ServerDbMigrationFnContext<'_>| {
342                            let map = ctx.map(ServerModuleDbMigrationContext::new);
343                            Box::pin(f(map))
344                        });
345                    closure
346                })
347            })
348            .collect()
349    }
350
351    fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
352        <Self as ServerModuleInit>::used_db_prefixes(self)
353    }
354}
355
356dyn_newtype_define!(
357    #[derive(Clone)]
358    pub DynServerModuleInit(Arc<IServerModuleInit>)
359);
360
361impl AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static> for DynServerModuleInit {
362    fn as_ref(&self) -> &(dyn IDynCommonModuleInit + Send + Sync + 'static) {
363        self.inner.as_common()
364    }
365}
366
367pub type ServerModuleInitRegistry = ModuleInitRegistry<DynServerModuleInit>;
368
369pub trait ServerModuleInitRegistryExt {
370    fn to_common(&self) -> CommonModuleInitRegistry;
371}
372
373impl ServerModuleInitRegistryExt for ServerModuleInitRegistry {
374    fn to_common(&self) -> CommonModuleInitRegistry {
375        self.iter().map(|(_k, v)| v.to_dyn_common()).collect()
376    }
377}