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::TaskGroup;
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 tracing::warn;
21
22use super::ClientContext;
23use super::recovery::RecoveryProgress;
24use crate::db::ClientModuleMigrationFn;
25use crate::module::ClientModule;
26use crate::sm::ModuleNotifier;
27
28pub type BitcoindRpcFactory = Box<
33 dyn FnOnce(ChainId) -> Pin<Box<dyn Future<Output = Option<DynBitcoindRpc>> + Send>>
34 + Send
35 + Sync,
36>;
37
38pub type BitcoindRpcNoChainIdFactory = Arc<
44 dyn Fn(SafeUrl) -> Pin<Box<dyn Future<Output = Option<DynBitcoindRpc>> + Send>> + Send + Sync,
45>;
46
47pub struct ClientModuleInitArgs<C>
48where
49 C: ClientModuleInit,
50{
51 pub federation_id: FederationId,
52 pub peer_num: usize,
53 pub cfg: <<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig,
54 pub db: Database,
55 pub core_api_version: ApiVersion,
56 pub module_api_version: ApiVersion,
57 pub module_root_secret: DerivableSecret,
58 pub notifier: ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States>,
59 pub api: DynGlobalApi,
60 pub admin_auth: Option<ApiAuth>,
61 pub module_api: DynModuleApi,
62 pub context: ClientContext<<C as ClientModuleInit>::Module>,
63 pub task_group: TaskGroup,
64 pub connector_registry: ConnectorRegistry,
65 pub user_bitcoind_rpc: Option<DynBitcoindRpc>,
71 pub user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
78}
79
80impl<C> ClientModuleInitArgs<C>
81where
82 C: ClientModuleInit,
83{
84 pub fn federation_id(&self) -> &FederationId {
85 &self.federation_id
86 }
87
88 pub fn peer_num(&self) -> usize {
89 self.peer_num
90 }
91
92 pub fn cfg(&self) -> &<<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig {
93 &self.cfg
94 }
95
96 pub fn db(&self) -> &Database {
97 &self.db
98 }
99
100 pub fn core_api_version(&self) -> &ApiVersion {
101 &self.core_api_version
102 }
103
104 pub fn module_api_version(&self) -> &ApiVersion {
105 &self.module_api_version
106 }
107
108 pub fn module_root_secret(&self) -> &DerivableSecret {
109 &self.module_root_secret
110 }
111
112 pub fn notifier(
113 &self,
114 ) -> &ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States> {
115 &self.notifier
116 }
117
118 pub fn api(&self) -> &DynGlobalApi {
119 &self.api
120 }
121
122 pub fn admin_auth(&self) -> Option<&ApiAuth> {
123 self.admin_auth.as_ref()
124 }
125
126 pub fn module_api(&self) -> &DynModuleApi {
127 &self.module_api
128 }
129
130 pub fn context(&self) -> ClientContext<<C as ClientModuleInit>::Module> {
137 self.context.clone()
138 }
139
140 pub fn task_group(&self) -> &TaskGroup {
141 &self.task_group
142 }
143
144 pub fn connector_registry(&self) -> &ConnectorRegistry {
145 &self.connector_registry
146 }
147
148 pub fn user_bitcoind_rpc(&self) -> Option<&DynBitcoindRpc> {
153 self.user_bitcoind_rpc.as_ref()
154 }
155
156 pub fn user_bitcoind_rpc_no_chain_id(&self) -> Option<&BitcoindRpcNoChainIdFactory> {
162 self.user_bitcoind_rpc_no_chain_id.as_ref()
163 }
164}
165
166pub struct ClientModuleRecoverArgs<C>
167where
168 C: ClientModuleInit,
169{
170 pub federation_id: FederationId,
171 pub num_peers: NumPeers,
172 pub cfg: <<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig,
173 pub db: Database,
174 pub core_api_version: ApiVersion,
175 pub module_api_version: ApiVersion,
176 pub module_root_secret: DerivableSecret,
177 pub notifier: ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States>,
178 pub api: DynGlobalApi,
179 pub admin_auth: Option<ApiAuth>,
180 pub module_api: DynModuleApi,
181 pub context: ClientContext<<C as ClientModuleInit>::Module>,
182 pub progress_tx: tokio::sync::watch::Sender<RecoveryProgress>,
183 pub task_group: TaskGroup,
184 pub user_bitcoind_rpc: Option<DynBitcoindRpc>,
190 pub user_bitcoind_rpc_no_chain_id: Option<BitcoindRpcNoChainIdFactory>,
197}
198
199impl<C> ClientModuleRecoverArgs<C>
200where
201 C: ClientModuleInit,
202{
203 pub fn federation_id(&self) -> &FederationId {
204 &self.federation_id
205 }
206
207 pub fn num_peers(&self) -> NumPeers {
208 self.num_peers
209 }
210
211 pub fn cfg(&self) -> &<<C as ModuleInit>::Common as CommonModuleInit>::ClientConfig {
212 &self.cfg
213 }
214
215 pub fn db(&self) -> &Database {
216 &self.db
217 }
218
219 pub fn task_group(&self) -> &TaskGroup {
220 &self.task_group
221 }
222
223 pub fn core_api_version(&self) -> &ApiVersion {
224 &self.core_api_version
225 }
226
227 pub fn module_api_version(&self) -> &ApiVersion {
228 &self.module_api_version
229 }
230
231 pub fn module_root_secret(&self) -> &DerivableSecret {
232 &self.module_root_secret
233 }
234
235 pub fn notifier(
236 &self,
237 ) -> &ModuleNotifier<<<C as ClientModuleInit>::Module as ClientModule>::States> {
238 &self.notifier
239 }
240
241 pub fn api(&self) -> &DynGlobalApi {
242 &self.api
243 }
244
245 pub fn admin_auth(&self) -> Option<&ApiAuth> {
246 self.admin_auth.as_ref()
247 }
248
249 pub fn module_api(&self) -> &DynModuleApi {
250 &self.module_api
251 }
252
253 pub fn context(&self) -> ClientContext<<C as ClientModuleInit>::Module> {
258 self.context.clone()
259 }
260
261 pub fn update_recovery_progress(&self, progress: RecoveryProgress) {
262 #[allow(clippy::disallowed_methods)]
264 if progress.is_done() {
265 warn!(target: LOG_CLIENT, "Module trying to send a completed recovery progress. Ignoring");
268 } else if progress.is_none() {
269 warn!(target: LOG_CLIENT, "Module trying to send a none recovery progress. Ignoring");
272 } else if self.progress_tx.send(progress).is_err() {
273 warn!(target: LOG_CLIENT, "Module trying to send a recovery progress but nothing is listening");
274 }
275 }
276
277 pub fn user_bitcoind_rpc(&self) -> Option<&DynBitcoindRpc> {
282 self.user_bitcoind_rpc.as_ref()
283 }
284
285 pub fn user_bitcoind_rpc_no_chain_id(&self) -> Option<&BitcoindRpcNoChainIdFactory> {
291 self.user_bitcoind_rpc_no_chain_id.as_ref()
292 }
293}
294
295#[apply(async_trait_maybe_send!)]
296pub trait ClientModuleInit: ModuleInit + Sized {
297 type Module: ClientModule;
298
299 fn supported_api_versions(&self) -> MultiApiVersion;
302
303 fn kind() -> ModuleKind {
304 <Self::Module as ClientModule>::kind()
305 }
306
307 async fn recover(
313 &self,
314 _args: &ClientModuleRecoverArgs<Self>,
315 _snapshot: Option<&<Self::Module as ClientModule>::Backup>,
316 ) -> anyhow::Result<()> {
317 warn!(
318 target: LOG_CLIENT,
319 kind = %<Self::Module as ClientModule>::kind(),
320 "Module does not support recovery, completing without doing anything"
321 );
322 Ok(())
323 }
324
325 async fn init(&self, args: &ClientModuleInitArgs<Self>) -> anyhow::Result<Self::Module>;
327
328 fn get_database_migrations(&self) -> BTreeMap<DatabaseVersion, ClientModuleMigrationFn> {
332 BTreeMap::new()
333 }
334
335 fn used_db_prefixes(&self) -> Option<BTreeSet<u8>> {
345 None
346 }
347}