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