Skip to main content

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