Skip to main content

fedimint_client/
module_init.rs

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    /// See [`ClientModuleInit::supported_api_versions`]
40    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    /// See [`ClientModuleInit::used_db_prefixes`]
90    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        // TODO: make dyn type for notifier
126        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        // TODO: make dyn type for notifier
188        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}