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