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