fedimint_client_module/module/
init.rs1pub mod recovery;
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::future::Future;
5use std::pin::Pin;
6use std::sync::Arc;
7
8use fedimint_api_client::api::{DynGlobalApi, DynModuleApi};
9use fedimint_bitcoind::DynBitcoindRpc;
10use fedimint_connectors::ConnectorRegistry;
11use fedimint_core::config::FederationId;
12use fedimint_core::core::ModuleKind;
13use fedimint_core::db::{Database, DatabaseVersion};
14use fedimint_core::module::{ApiAuth, ApiVersion, CommonModuleInit, ModuleInit, MultiApiVersion};
15use fedimint_core::task::{MaybeSend, ShuttingDownError, TaskGroup, TaskHandle};
16use fedimint_core::util::SafeUrl;
17use fedimint_core::{ChainId, NumPeers, apply, async_trait_maybe_send};
18use fedimint_derive_secret::DerivableSecret;
19use fedimint_logging::LOG_CLIENT;
20use tokio::sync::oneshot;
21use tracing::{Span, warn};
22
23use super::ClientContext;
24use super::recovery::RecoveryProgress;
25use crate::db::ClientModuleMigrationFn;
26use crate::module::ClientModule;
27use crate::sm::ModuleNotifier;
28
29pub type BitcoindRpcFactory = Box<
34 dyn FnOnce(ChainId) -> Pin<Box<dyn Future<Output = Option<DynBitcoindRpc>> + Send>>
35 + Send
36 + Sync,
37>;
38
39pub type BitcoindRpcNoChainIdFactory = Arc<
45 dyn Fn(SafeUrl) -> Pin<Box<dyn Future<Output = Option<DynBitcoindRpc>> + Send>> + Send + Sync,
46>;
47
48pub struct ClientModuleInitArgs<C>
49where
50 C: ClientModuleInit,
51{
52 pub federation_id: FederationId,
53 pub peer_num: usize,
54 pub cfg: <<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig,
55 pub db: Database,
56 pub core_api_version: ApiVersion,
57 pub module_api_version: ApiVersion,
58 pub module_root_secret: DerivableSecret,
59 pub notifier: ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States>,
60 pub api: DynGlobalApi,
61 pub admin_auth: Option<ApiAuth>,
62 pub module_api: DynModuleApi,
63 pub context: ClientContext<<C as ClientModuleInit>::Module>,
64 pub task_group: TaskGroup,
65 pub client_span: Span,
70 pub connector_registry: ConnectorRegistry,
71 pub user_bitcoind_rpc: Option<DynBitcoindRpc>,
77 pub user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
84}
85
86impl<C> ClientModuleInitArgs<C>
87where
88 C: ClientModuleInit,
89{
90 pub fn federation_id(&self) -> &FederationId {
91 &self.federation_id
92 }
93
94 pub fn peer_num(&self) -> usize {
95 self.peer_num
96 }
97
98 pub fn cfg(&self) -> &<<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig {
99 &self.cfg
100 }
101
102 pub fn db(&self) -> &Database {
103 &self.db
104 }
105
106 pub fn core_api_version(&self) -> &ApiVersion {
107 &self.core_api_version
108 }
109
110 pub fn module_api_version(&self) -> &ApiVersion {
111 &self.module_api_version
112 }
113
114 pub fn module_root_secret(&self) -> &DerivableSecret {
115 &self.module_root_secret
116 }
117
118 pub fn notifier(
119 &self,
120 ) -> &ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States> {
121 &self.notifier
122 }
123
124 pub fn api(&self) -> &DynGlobalApi {
125 &self.api
126 }
127
128 pub fn admin_auth(&self) -> Option<&ApiAuth> {
129 self.admin_auth.as_ref()
130 }
131
132 pub fn module_api(&self) -> &DynModuleApi {
133 &self.module_api
134 }
135
136 pub fn context(&self) -> ClientContext<<C as ClientModuleInit>::Module> {
143 self.context.clone()
144 }
145
146 pub fn task_group(&self) -> &TaskGroup {
147 &self.task_group
148 }
149
150 pub fn client_span(&self) -> &Span {
152 &self.client_span
153 }
154
155 pub fn spawn_cancellable<R>(
159 &self,
160 name: impl Into<String>,
161 future: impl std::future::Future<Output = R> + MaybeSend + 'static,
162 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
163 where
164 R: MaybeSend + 'static,
165 {
166 self.task_group
167 .spawn_cancellable_with_span(self.client_span.clone(), name, future)
168 }
169
170 pub fn spawn<Fut, R>(
172 &self,
173 name: impl Into<String>,
174 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
175 ) -> oneshot::Receiver<R>
176 where
177 Fut: std::future::Future<Output = R> + MaybeSend + 'static,
178 R: MaybeSend + 'static,
179 {
180 self.task_group
181 .spawn_with_span(self.client_span.clone(), name, f)
182 }
183
184 pub fn connector_registry(&self) -> &ConnectorRegistry {
185 &self.connector_registry
186 }
187
188 pub fn user_bitcoind_rpc(&self) -> Option<&DynBitcoindRpc> {
193 self.user_bitcoind_rpc.as_ref()
194 }
195
196 pub fn user_bitcoind_rpc_no_chain_id(&self) -> Option<&BitcoindRpcNoChainIdFactory> {
202 self.user_bitcoind_rpc_no_chain_id.as_ref()
203 }
204}
205
206pub struct ClientModuleRecoverArgs<C>
207where
208 C: ClientModuleInit,
209{
210 pub federation_id: FederationId,
211 pub num_peers: NumPeers,
212 pub cfg: <<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig,
213 pub db: Database,
214 pub core_api_version: ApiVersion,
215 pub module_api_version: ApiVersion,
216 pub module_root_secret: DerivableSecret,
217 pub notifier: ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States>,
218 pub api: DynGlobalApi,
219 pub admin_auth: Option<ApiAuth>,
220 pub module_api: DynModuleApi,
221 pub context: ClientContext<<C as ClientModuleInit>::Module>,
222 pub progress_tx: tokio::sync::watch::Sender<RecoveryProgress>,
223 pub task_group: TaskGroup,
224 pub client_span: Span,
226 pub user_bitcoind_rpc: Option<DynBitcoindRpc>,
232 pub user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
239}
240
241impl<C> ClientModuleRecoverArgs<C>
242where
243 C: ClientModuleInit,
244{
245 pub fn federation_id(&self) -> &FederationId {
246 &self.federation_id
247 }
248
249 pub fn num_peers(&self) -> NumPeers {
250 self.num_peers
251 }
252
253 pub fn cfg(&self) -> &<<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig {
254 &self.cfg
255 }
256
257 pub fn db(&self) -> &Database {
258 &self.db
259 }
260
261 pub fn task_group(&self) -> &TaskGroup {
262 &self.task_group
263 }
264
265 pub fn client_span(&self) -> &Span {
267 &self.client_span
268 }
269
270 pub fn spawn_cancellable<R>(
273 &self,
274 name: impl Into<String>,
275 future: impl std::future::Future<Output = R> + MaybeSend + 'static,
276 ) -> oneshot::Receiver<Result<R, ShuttingDownError>>
277 where
278 R: MaybeSend + 'static,
279 {
280 self.task_group
281 .spawn_cancellable_with_span(self.client_span.clone(), name, future)
282 }
283
284 pub fn spawn<Fut, R>(
286 &self,
287 name: impl Into<String>,
288 f: impl FnOnce(TaskHandle) -> Fut + MaybeSend + 'static,
289 ) -> oneshot::Receiver<R>
290 where
291 Fut: std::future::Future<Output = R> + MaybeSend + 'static,
292 R: MaybeSend + 'static,
293 {
294 self.task_group
295 .spawn_with_span(self.client_span.clone(), name, f)
296 }
297
298 pub fn core_api_version(&self) -> &ApiVersion {
299 &self.core_api_version
300 }
301
302 pub fn module_api_version(&self) -> &ApiVersion {
303 &self.module_api_version
304 }
305
306 pub fn module_root_secret(&self) -> &DerivableSecret {
307 &self.module_root_secret
308 }
309
310 pub fn notifier(
311 &self,
312 ) -> &ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States> {
313 &self.notifier
314 }
315
316 pub fn api(&self) -> &DynGlobalApi {
317 &self.api
318 }
319
320 pub fn admin_auth(&self) -> Option<&ApiAuth> {
321 self.admin_auth.as_ref()
322 }
323
324 pub fn module_api(&self) -> &DynModuleApi {
325 &self.module_api
326 }
327
328 pub fn context(&self) -> ClientContext<<C as ClientModuleInit>::Module> {
333 self.context.clone()
334 }
335
336 pub fn update_recovery_progress(&self, progress: RecoveryProgress) {
337 #[allow(clippy::disallowed_methods)]
339 if progress.is_done() {
340 warn!(target: LOG_CLIENT, "Module trying to send a completed recovery progress. Ignoring");
343 } else if progress.is_none() {
344 warn!(target: LOG_CLIENT, "Module trying to send a none recovery progress. Ignoring");
347 } else if self.progress_tx.send(progress).is_err() {
348 warn!(target: LOG_CLIENT, "Module trying to send a recovery progress but nothing is listening");
349 }
350 }
351
352 pub fn user_bitcoind_rpc(&self) -> Option<&DynBitcoindRpc> {
357 self.user_bitcoind_rpc.as_ref()
358 }
359
360 pub fn user_bitcoind_rpc_no_chain_id(&self) -> Option<&BitcoindRpcNoChainIdFactory> {
366 self.user_bitcoind_rpc_no_chain_id.as_ref()
367 }
368}
369
370#[apply(async_trait_maybe_send!)]
371pub trait ClientModuleInit: ModuleInit + Sized {
372 type Module: ClientModule;
373
374 fn supported_api_versions(&self) -> MultiApiVersion;
377
378 fn kind() -> ModuleKind {
379 <Self::Module as ClientModule>::kind()
380 }
381
382 async fn recover(
388 &self,
389 _args: &ClientModuleRecoverArgs<Self>,
390 _snapshot: Option<&<Self::Module as ClientModule>::Backup>,
391 ) -> anyhow::Result<()> {
392 warn!(
393 target: LOG_CLIENT,
394 kind = %<Self::Module as ClientModule>::kind(),
395 "Module does not support recovery, completing without doing anything"
396 );
397 Ok(())
398 }
399
400 async fn init(&self, args: &ClientModuleInitArgs<Self>) -> anyhow::Result<Self::Module>;
402
403 fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn> {
407 BTreeMap::new()
408 }
409
410 fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
420 None
421 }
422}