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