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