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 fn supported_api_versions(&self) -> MultiApiVersion;
43
44 fn recovery_mode(&self) -> RecoveryMode;
46
47 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 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 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 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}