Skip to main content

fedimint_client/
module_init.rs

1use std::collections::{BTreeMap, BTreeSet};
2use std::fmt;
3use std::sync::Arc;
4
5use fedimint_api_client::api::DynGlobalApi;
6use fedimint_bitcoind::DynBitcoindRpc;
7use fedimint_client_module::db::ClientModuleMigrationFn;
8use fedimint_client_module::module::init::{
9    BitcoindRpcNoChainIdFactory, ClientModuleInit, ClientModuleInitArgs, ClientModuleRecoverArgs,
10    ClientModuleRecoveryPrepareArgs, RecoveryMode,
11};
12use fedimint_client_module::module::recovery::{DynModuleBackup, RecoveryProgress};
13use fedimint_client_module::module::{ClientContext, DynClientModule, FinalClientIface};
14use fedimint_client_module::{ClientModule, ModuleInstanceId, ModuleKind};
15use fedimint_connectors::ConnectorRegistry;
16use fedimint_core::config::{ClientModuleConfig, FederationId, ModuleInitRegistry};
17use fedimint_core::core::Decoder;
18use fedimint_core::db::{Database, DatabaseVersion};
19use fedimint_core::module::{
20    ApiAuth, ApiVersion, CommonModuleInit, IDynCommonModuleInit, ModuleInit, MultiApiVersion,
21};
22use fedimint_core::task::{MaybeSend, MaybeSync, TaskGroup};
23use fedimint_core::{Amount, NumPeers, apply, async_trait_maybe_send, dyn_newtype_define};
24use fedimint_derive_secret::DerivableSecret;
25use tokio::sync::watch;
26use tracing::Span;
27
28use crate::error::ClientModuleError;
29use crate::sm::notifier::Notifier;
30
31pub type ClientModuleInitRegistry = ModuleInitRegistry<DynClientModuleInit>;
32
33#[apply(async_trait_maybe_send!)]
34pub trait IClientModuleInit: IDynCommonModuleInit + fmt::Debug + MaybeSend + MaybeSync {
35    fn decoder(&self) -> Decoder;
36
37    fn module_kind(&self) -> ModuleKind;
38
39    fn as_common(&self) -> &(dyn IDynCommonModuleInit + Send + Sync + 'static);
40
41    /// See [`ClientModuleInit::supported_api_versions`]
42    fn supported_api_versions(&self) -> MultiApiVersion;
43
44    /// See [`ClientModuleInit::recovery_mode`]
45    fn recovery_mode(&self) -> RecoveryMode;
46
47    /// See [`ClientModuleInit::prepare_recovery`]
48    async fn prepare_recovery(
49        &self,
50        db: Database,
51        instance_id: ModuleInstanceId,
52        api: DynGlobalApi,
53    ) -> Result<(), ClientModuleError>;
54
55    #[allow(clippy::too_many_arguments)]
56    async fn recover(
57        &self,
58        final_client: FinalClientIface,
59        federation_id: FederationId,
60        num_peers: NumPeers,
61        cfg: ClientModuleConfig,
62        db: Database,
63        instance_id: ModuleInstanceId,
64        core_api_version: ApiVersion,
65        module_api_version: ApiVersion,
66        module_root_secret: DerivableSecret,
67        notifier: Notifier,
68        api: DynGlobalApi,
69        admin_auth: Option<ApiAuth>,
70        snapshot: Option<&DynModuleBackup>,
71        progress_tx: watch::Sender<RecoveryProgress>,
72        task_group: TaskGroup,
73        client_span: Span,
74        user_bitcoind_rpc: Option<DynBitcoindRpc>,
75        user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
76    ) -> Result<Option<Amount>, ClientModuleError>;
77
78    #[allow(clippy::too_many_arguments)]
79    async fn init(
80        &self,
81        final_client: FinalClientIface,
82        federation_id: FederationId,
83        peer_num: usize,
84        cfg: ClientModuleConfig,
85        db: Database,
86        instance_id: ModuleInstanceId,
87        core_api_version: ApiVersion,
88        module_api_version: ApiVersion,
89        module_root_secret: DerivableSecret,
90        notifier: Notifier,
91        api: DynGlobalApi,
92        admin_auth: Option<ApiAuth>,
93        task_group: TaskGroup,
94        client_span: Span,
95        connector_registry: ConnectorRegistry,
96        user_bitcoind_rpc: Option<DynBitcoindRpc>,
97        user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
98    ) -> Result<DynClientModule, ClientModuleError>;
99
100    fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn>;
101
102    /// See [`ClientModuleInit::used_db_prefixes`]
103    fn used_db_prefixes(&self) -> Option<BTreeSet<u8>>;
104}
105
106#[apply(async_trait_maybe_send!)]
107impl<T> IClientModuleInit for T
108where
109    T: ClientModuleInit + 'static + MaybeSend + Sync,
110{
111    fn decoder(&self) -> Decoder {
112        <<T as ClientModuleInit>::Module as ClientModule>::decoder()
113    }
114
115    fn module_kind(&self) -> ModuleKind {
116        <Self as ModuleInit>::Common::KIND
117    }
118
119    fn as_common(&self) -> &(dyn IDynCommonModuleInit + Send + Sync + 'static) {
120        self
121    }
122
123    fn supported_api_versions(&self) -> MultiApiVersion {
124        <Self as ClientModuleInit>::supported_api_versions(self)
125    }
126
127    fn recovery_mode(&self) -> RecoveryMode {
128        <Self as ClientModuleInit>::recovery_mode(self)
129    }
130
131    async fn prepare_recovery(
132        &self,
133        db: Database,
134        instance_id: ModuleInstanceId,
135        api: DynGlobalApi,
136    ) -> Result<(), ClientModuleError> {
137        let (module_db, _global_dbtx_access_token) = db.with_prefix_module_id(instance_id);
138
139        <Self as ClientModuleInit>::prepare_recovery(
140            self,
141            &ClientModuleRecoveryPrepareArgs {
142                db: module_db,
143                module_api: api.with_module(instance_id),
144            },
145        )
146        .await
147    }
148
149    async fn recover(
150        &self,
151        final_client: FinalClientIface,
152        federation_id: FederationId,
153        num_peers: NumPeers,
154        cfg: ClientModuleConfig,
155        db: Database,
156        instance_id: ModuleInstanceId,
157        core_api_version: ApiVersion,
158        module_api_version: ApiVersion,
159        module_root_secret: DerivableSecret,
160        // TODO: make dyn type for notifier
161        notifier: Notifier,
162        api: DynGlobalApi,
163        admin_auth: Option<ApiAuth>,
164        snapshot: Option<&DynModuleBackup>,
165        progress_tx: watch::Sender<RecoveryProgress>,
166        task_group: TaskGroup,
167        client_span: Span,
168        user_bitcoind_rpc: Option<DynBitcoindRpc>,
169        user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
170    ) -> Result<Option<Amount>, ClientModuleError> {
171        let typed_cfg = cfg
172            .cast::<<<T as ModuleInit>::Common as CommonModuleInit>::ClientConfig>()
173            .map_err(ClientModuleError::other)?;
174        let snapshot: Option<&<<Self as ClientModuleInit>::Module as ClientModule>::Backup> =
175            snapshot.map(|s| {
176                s.as_any()
177                    .downcast_ref()
178                    .expect("can't convert client module backup to desired type")
179            });
180
181        let (module_db, global_dbtx_access_token) = db.with_prefix_module_id(instance_id);
182        Ok(<Self as ClientModuleInit>::recover(
183            self,
184            &ClientModuleRecoverArgs {
185                federation_id,
186                num_peers,
187                cfg: typed_cfg.clone(),
188                db: module_db.clone(),
189                core_api_version,
190                module_api_version,
191                module_root_secret,
192                notifier: notifier.module_notifier(instance_id, final_client.clone()),
193                api: api.clone(),
194                admin_auth,
195                module_api: api.with_module(instance_id),
196                context: ClientContext::new(
197                    final_client,
198                    instance_id,
199                    global_dbtx_access_token,
200                    module_db,
201                ),
202                progress_tx,
203                task_group,
204                client_span,
205                user_bitcoind_rpc,
206                user_bitcoind_rpc_no_chain_id,
207            },
208            snapshot,
209        )
210        .await?)
211    }
212
213    async fn init(
214        &self,
215        final_client: FinalClientIface,
216        federation_id: FederationId,
217        peer_num: usize,
218        cfg: ClientModuleConfig,
219        db: Database,
220        instance_id: ModuleInstanceId,
221        core_api_version: ApiVersion,
222        module_api_version: ApiVersion,
223        module_root_secret: DerivableSecret,
224        // TODO: make dyn type for notifier
225        notifier: Notifier,
226        api: DynGlobalApi,
227        admin_auth: Option<ApiAuth>,
228        task_group: TaskGroup,
229        client_span: Span,
230        connector_registry: ConnectorRegistry,
231        user_bitcoind_rpc: Option<DynBitcoindRpc>,
232        user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
233    ) -> Result<DynClientModule, ClientModuleError> {
234        let typed_cfg = cfg
235            .cast::<<<T as ModuleInit>::Common as CommonModuleInit>::ClientConfig>()
236            .map_err(ClientModuleError::other)?;
237        let (module_db, global_dbtx_access_token) = db.with_prefix_module_id(instance_id);
238        Ok(<Self as ClientModuleInit>::init(
239            self,
240            &ClientModuleInitArgs {
241                federation_id,
242                peer_num,
243                cfg: typed_cfg.clone(),
244                db: module_db.clone(),
245                core_api_version,
246                module_api_version,
247                module_root_secret,
248                notifier: notifier.module_notifier(instance_id, final_client.clone()),
249                api: api.clone(),
250                admin_auth,
251                module_api: api.with_module(instance_id),
252                context: ClientContext::new(
253                    final_client,
254                    instance_id,
255                    global_dbtx_access_token,
256                    module_db,
257                ),
258                task_group,
259                client_span,
260                connector_registry,
261                user_bitcoind_rpc,
262                user_bitcoind_rpc_no_chain_id,
263            },
264        )
265        .await?
266        .into())
267    }
268
269    fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn> {
270        <Self as ClientModuleInit>::get_database_migrations(self)
271    }
272
273    fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
274        <Self as ClientModuleInit>::used_db_prefixes(self)
275    }
276}
277
278dyn_newtype_define!(
279    #[derive(Clone)]
280    pub DynClientModuleInit(Arc<IClientModuleInit>)
281);
282
283impl AsRef<dyn IDynCommonModuleInit + Send + Sync + 'static> for DynClientModuleInit {
284    fn as_ref(&self) -> &(dyn IDynCommonModuleInit + Send + Sync + 'static) {
285        self.inner.as_common()
286    }
287}
288
289impl AsRef<dyn IClientModuleInit + 'static> for DynClientModuleInit {
290    fn as_ref(&self) -> &(dyn IClientModuleInit + 'static) {
291        self.inner.as_ref()
292    }
293}