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