fedimint_gateway_server/
client.rs1use 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::module_init::ClientModuleInitRegistry;
9use fedimint_client::{Client, ClientBuilder, RootSecret};
10use fedimint_client_module::secret::{PlainRootSecretStrategy, RootSecretStrategy};
11use fedimint_core::config::FederationId;
12use fedimint_core::db::{Database, IDatabaseTransactionOpsCoreTyped};
13use fedimint_core::module::registry::ModuleDecoderRegistry;
14use fedimint_derive_secret::DerivableSecret;
15use fedimint_gateway_common::FederationConfig;
16use fedimint_gateway_server_db::GatewayDbExt as _;
17use fedimint_gw_client::GatewayClientInit;
18use fedimint_gwv2_client::GatewayClientInitV2;
19
20use crate::config::DatabaseBackend;
21use crate::error::AdminGatewayError;
22use crate::{AdminResult, Gateway};
23
24#[derive(Debug, Clone)]
25pub struct GatewayClientBuilder {
26 work_dir: PathBuf,
27 registry: ClientModuleInitRegistry,
28 db_backend: DatabaseBackend,
29}
30
31impl GatewayClientBuilder {
32 pub fn new(
33 work_dir: PathBuf,
34 registry: ClientModuleInitRegistry,
35 db_backend: DatabaseBackend,
36 ) -> Self {
37 Self {
38 work_dir,
39 registry,
40 db_backend,
41 }
42 }
43
44 pub fn data_dir(&self) -> PathBuf {
45 self.work_dir.clone()
46 }
47
48 async fn client_plainrootsecret(&self, db: &Database) -> AdminResult<DerivableSecret> {
51 let client_secret = Client::load_decodable_client_secret::<[u8; 64]>(db)
52 .await
53 .map_err(AdminGatewayError::ClientCreationError)?;
54 Ok(PlainRootSecretStrategy::to_root_secret(&client_secret))
55 }
56
57 async fn create_client_builder(
60 &self,
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 registry.attach(GatewayClientInit {
73 federation_index,
74 lightning_manager: gateway.clone(),
75 });
76
77 registry.attach(GatewayClientInitV2 {
78 gateway: gateway.clone(),
79 });
80
81 let mut client_builder = Client::builder()
82 .await
83 .map_err(AdminGatewayError::ClientCreationError)?
84 .with_iroh_enable_dht(true)
85 .with_iroh_enable_next(true);
86 client_builder.with_module_inits(registry);
87 client_builder.with_connector(connector);
88 Ok(client_builder)
89 }
90
91 pub async fn recover(
96 &self,
97 config: FederationConfig,
98 gateway: Arc<Gateway>,
99 mnemonic: &Mnemonic,
100 ) -> AdminResult<()> {
101 let federation_id = config.invite_code.federation_id();
102 let db = gateway.gateway_db.get_client_database(&federation_id);
103 let client_builder = self.create_client_builder(&config, gateway.clone()).await?;
104 let root_secret = RootSecret::StandardDoubleDerive(
105 Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
106 );
107 let client = client_builder
108 .preview(&config.invite_code)
109 .await?
110 .recover(db, root_secret, None)
111 .await
112 .map(Arc::new)
113 .map_err(AdminGatewayError::ClientCreationError)?;
114 client
115 .wait_for_all_recoveries()
116 .await
117 .map_err(AdminGatewayError::ClientCreationError)?;
118 Ok(())
119 }
120
121 pub async fn build(
124 &self,
125 config: FederationConfig,
126 gateway: Arc<Gateway>,
127 mnemonic: &Mnemonic,
128 ) -> AdminResult<fedimint_client::ClientHandleArc> {
129 let invite_code = config.invite_code.clone();
130 let federation_id = invite_code.federation_id();
131 let db_path = self.work_dir.join(format!("{federation_id}.db"));
132
133 let (db, root_secret) = if db_path.exists() {
134 let db = match self.db_backend {
135 DatabaseBackend::RocksDb => {
136 let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone())
137 .await
138 .map_err(AdminGatewayError::ClientCreationError)?;
139 Database::new(rocksdb, ModuleDecoderRegistry::default())
140 }
141 DatabaseBackend::CursedRedb => {
142 let cursed_redb = fedimint_cursed_redb::MemAndRedb::new(db_path.clone())
143 .await
144 .map_err(AdminGatewayError::ClientCreationError)?;
145 Database::new(cursed_redb, ModuleDecoderRegistry::default())
146 }
147 };
148 let root_secret = RootSecret::Custom(self.client_plainrootsecret(&db).await?);
149 (db, root_secret)
150 } else {
151 let db = gateway.gateway_db.get_client_database(&federation_id);
152
153 let root_secret = RootSecret::StandardDoubleDerive(
154 Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
155 );
156 (db, root_secret)
157 };
158
159 Self::verify_client_config(&db, federation_id).await?;
160
161 let client_builder = self.create_client_builder(&config, gateway).await?;
162
163 if Client::is_initialized(&db).await {
164 client_builder.open(db, root_secret).await
165 } else {
166 client_builder
167 .preview(&invite_code)
168 .await?
169 .join(db, root_secret)
170 .await
171 }
172 .map(Arc::new)
173 .map_err(AdminGatewayError::ClientCreationError)
174 }
175
176 async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
179 let mut dbtx = db.begin_transaction_nc().await;
180 if let Some(config) = dbtx.get_value(&ClientConfigKey).await
181 && config.calculate_federation_id() != federation_id
182 {
183 return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
184 "Federation Id did not match saved federation ID".to_string()
185 )));
186 }
187 Ok(())
188 }
189
190 pub fn legacy_federations(&self, all_federations: BTreeSet<FederationId>) -> Vec<FederationId> {
193 all_federations
194 .into_iter()
195 .filter(|federation_id| {
196 let db_path = self.work_dir.join(format!("{federation_id}.db"));
197 db_path.exists()
198 })
199 .collect::<Vec<FederationId>>()
200 }
201}