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