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