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