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