1use std::collections::BTreeSet;
2use std::fmt::Debug;
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use fedimint_bip39::{Bip39RootSecretStrategy, Mnemonic};
7use fedimint_client::db::ClientConfigKey;
8use fedimint_client::derivable_secret::{ChildId, DerivableSecret};
9use fedimint_client::module::init::ClientModuleInitRegistry;
10use fedimint_client::secret::{PlainRootSecretStrategy, RootSecretStrategy};
11use fedimint_client::{Client, ClientBuilder};
12use fedimint_core::config::FederationId;
13use fedimint_core::core::ModuleKind;
14use fedimint_core::db::{Database, IDatabaseTransactionOpsCoreTyped};
15use fedimint_core::module::registry::ModuleDecoderRegistry;
16
17use crate::db::{FederationConfig, GatewayDbExt};
18use crate::error::AdminGatewayError;
19use crate::gateway_module_v2::GatewayClientInitV2;
20use crate::state_machine::GatewayClientInit;
21use crate::{AdminResult, Gateway};
22
23#[derive(Debug, Clone)]
24pub struct GatewayClientBuilder {
25 work_dir: PathBuf,
26 registry: ClientModuleInitRegistry,
27 primary_module_kind: ModuleKind,
28}
29
30impl GatewayClientBuilder {
31 pub fn new(
32 work_dir: PathBuf,
33 registry: ClientModuleInitRegistry,
34 primary_module_kind: ModuleKind,
35 ) -> Self {
36 Self {
37 work_dir,
38 registry,
39 primary_module_kind,
40 }
41 }
42
43 pub fn data_dir(&self) -> PathBuf {
44 self.work_dir.clone()
45 }
46
47 async fn client_plainrootsecret(&self, db: &Database) -> AdminResult<DerivableSecret> {
50 let client_secret = Client::load_decodable_client_secret::<[u8; 64]>(db)
51 .await
52 .map_err(AdminGatewayError::ClientCreationError)?;
53 Ok(PlainRootSecretStrategy::to_root_secret(&client_secret))
54 }
55
56 async fn create_client_builder(
59 &self,
60 db: Database,
61 federation_config: &FederationConfig,
62 gateway: Arc<Gateway>,
63 ) -> AdminResult<ClientBuilder> {
64 let FederationConfig {
65 federation_index,
66 connector,
67 ..
68 } = federation_config.to_owned();
69
70 let mut registry = self.registry.clone();
71
72 if gateway.is_running_lnv1() {
73 registry.attach(GatewayClientInit {
74 federation_index,
75 gateway: gateway.clone(),
76 });
77 }
78
79 if gateway.is_running_lnv2() {
80 registry.attach(GatewayClientInitV2 {
81 gateway: gateway.clone(),
82 });
83 }
84
85 let mut client_builder = Client::builder(db)
86 .await
87 .map_err(AdminGatewayError::ClientCreationError)?;
88 client_builder.with_module_inits(registry);
89 client_builder.with_primary_module_kind(self.primary_module_kind.clone());
90 client_builder.with_connector(connector);
91 Ok(client_builder)
92 }
93
94 pub async fn recover(
99 &self,
100 config: FederationConfig,
101 gateway: Arc<Gateway>,
102 mnemonic: &Mnemonic,
103 ) -> AdminResult<()> {
104 let client_config = config
105 .connector
106 .download_from_invite_code(&config.invite_code)
107 .await
108 .map_err(AdminGatewayError::ClientCreationError)?;
109 let federation_id = config.invite_code.federation_id();
110 let db = gateway.gateway_db.get_client_database(&federation_id);
111 let client_builder = self
112 .create_client_builder(db, &config, gateway.clone())
113 .await?;
114 let secret = Self::derive_federation_secret(mnemonic, &federation_id);
115 let backup = client_builder
116 .download_backup_from_federation(
117 &secret,
118 &client_config,
119 config.invite_code.api_secret(),
120 )
121 .await
122 .map_err(AdminGatewayError::ClientCreationError)?;
123 let client = client_builder
124 .recover(
125 secret.clone(),
126 client_config,
127 config.invite_code.api_secret(),
128 backup,
129 )
130 .await
131 .map(Arc::new)
132 .map_err(AdminGatewayError::ClientCreationError)?;
133 client
134 .wait_for_all_recoveries()
135 .await
136 .map_err(AdminGatewayError::ClientCreationError)?;
137 Ok(())
138 }
139
140 pub async fn build(
143 &self,
144 config: FederationConfig,
145 gateway: Arc<Gateway>,
146 mnemonic: &Mnemonic,
147 ) -> AdminResult<fedimint_client::ClientHandleArc> {
148 let invite_code = config.invite_code.clone();
149 let federation_id = invite_code.federation_id();
150 let db_path = self.work_dir.join(format!("{federation_id}.db"));
151
152 let (db, root_secret) = if db_path.exists() {
153 let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone())
154 .map_err(AdminGatewayError::ClientCreationError)?;
155 let db = Database::new(rocksdb, ModuleDecoderRegistry::default());
156 let root_secret = self.client_plainrootsecret(&db).await?;
157 (db, root_secret)
158 } else {
159 let db = gateway.gateway_db.get_client_database(&federation_id);
160 let secret = Self::derive_federation_secret(mnemonic, &federation_id);
161 (db, secret)
162 };
163
164 Self::verify_client_config(&db, federation_id).await?;
165
166 let client_builder = self.create_client_builder(db, &config, gateway).await?;
167
168 if Client::is_initialized(client_builder.db_no_decoders()).await {
169 client_builder.open(root_secret).await
170 } else {
171 let client_config = config
172 .connector
173 .download_from_invite_code(&invite_code)
174 .await
175 .map_err(AdminGatewayError::ClientCreationError)?;
176 client_builder
177 .join(root_secret, client_config.clone(), invite_code.api_secret())
178 .await
179 }
180 .map(Arc::new)
181 .map_err(AdminGatewayError::ClientCreationError)
182 }
183
184 async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
187 let mut dbtx = db.begin_transaction_nc().await;
188 if let Some(config) = dbtx.get_value(&ClientConfigKey).await {
189 if config.calculate_federation_id() != federation_id {
190 return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
191 "Federation Id did not match saved federation ID".to_string()
192 )));
193 }
194 }
195 Ok(())
196 }
197
198 fn derive_federation_secret(
201 mnemonic: &Mnemonic,
202 federation_id: &FederationId,
203 ) -> DerivableSecret {
204 let global_root_secret = Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic);
205 let multi_federation_root_secret = global_root_secret.child_key(ChildId(0));
206 let federation_root_secret = multi_federation_root_secret.federation_key(federation_id);
207 let federation_wallet_root_secret = federation_root_secret.child_key(ChildId(0));
208 federation_wallet_root_secret.child_key(ChildId(0))
209 }
210
211 pub fn legacy_federations(&self, all_federations: BTreeSet<FederationId>) -> Vec<FederationId> {
214 all_federations
215 .into_iter()
216 .filter(|federation_id| {
217 let db_path = self.work_dir.join(format!("{federation_id}.db"));
218 db_path.exists()
219 })
220 .collect::<Vec<FederationId>>()
221 }
222}