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_connectors::ConnectorRegistry;
12use fedimint_core::config::FederationId;
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 db_backend: DatabaseBackend,
30 connectors: ConnectorRegistry,
31}
32
33impl GatewayClientBuilder {
34 pub async fn new(
35 work_dir: PathBuf,
36 registry: ClientModuleInitRegistry,
37 db_backend: DatabaseBackend,
38 ) -> anyhow::Result<Self> {
39 Ok(Self {
40 connectors: ConnectorRegistry::build_from_client_env().bind().await,
41 work_dir,
42 registry,
43 db_backend,
44 })
45 }
46
47 pub fn data_dir(&self) -> PathBuf {
48 self.work_dir.clone()
49 }
50
51 async fn client_plainrootsecret(&self, db: &Database) -> AdminResult<DerivableSecret> {
54 let client_secret = Client::load_decodable_client_secret::<[u8; 64]>(db)
55 .await
56 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?;
57 Ok(PlainRootSecretStrategy::to_root_secret(&client_secret))
58 }
59
60 async fn create_client_builder(
63 &self,
64 federation_config: &FederationConfig,
65 gateway: Arc<Gateway>,
66 ) -> AdminResult<ClientBuilder> {
67 let FederationConfig {
68 federation_index, ..
69 } = federation_config.to_owned();
70
71 let mut registry = self.registry.clone();
72
73 registry.attach(GatewayClientInit {
74 federation_index,
75 lightning_manager: gateway.clone(),
76 });
77
78 registry.attach(GatewayClientInitV2 {
79 gateway: gateway.clone(),
80 });
81
82 let mut client_builder = Client::builder().await.with_iroh_enable_dht(true);
83 client_builder.with_module_inits(registry);
84 Ok(client_builder)
85 }
86
87 pub async fn recover(
92 &self,
93 config: FederationConfig,
94 gateway: Arc<Gateway>,
95 mnemonic: &Mnemonic,
96 ) -> AdminResult<()> {
97 let federation_id = config.invite_code.federation_id();
98 let db = gateway.gateway_db.get_client_database(&federation_id);
99 let client_builder = self.create_client_builder(&config, gateway.clone()).await?;
100 let root_secret = RootSecret::StandardDoubleDerive(
101 Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
102 );
103 let client = client_builder
104 .preview(self.connectors.clone(), &config.invite_code)
105 .await
106 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?
107 .recover(db, root_secret, None)
108 .await
109 .map(Arc::new)
110 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?;
111 client
112 .wait_for_all_recoveries()
113 .await
114 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?;
115 Ok(())
116 }
117
118 pub async fn build(
121 &self,
122 config: FederationConfig,
123 gateway: Arc<Gateway>,
124 mnemonic: &Mnemonic,
125 ) -> AdminResult<fedimint_client::ClientHandleArc> {
126 let invite_code = config.invite_code.clone();
127 let federation_id = invite_code.federation_id();
128 let db_path = self.work_dir.join(format!("{federation_id}.db"));
129
130 let (db, root_secret) = if db_path.exists() {
131 let db = match self.db_backend {
132 DatabaseBackend::RocksDb => {
133 let rocksdb = fedimint_rocksdb::RocksDb::build(db_path.clone())
134 .open()
135 .await
136 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?;
137 Database::new(rocksdb, ModuleDecoderRegistry::default())
138 }
139 DatabaseBackend::CursedRedb => {
140 let cursed_redb = fedimint_cursed_redb::MemAndRedb::new(db_path.clone())
141 .await
142 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?;
143 Database::new(cursed_redb, ModuleDecoderRegistry::default())
144 }
145 };
146 let root_secret = RootSecret::Custom(self.client_plainrootsecret(&db).await?);
147 (db, root_secret)
148 } else {
149 let db = gateway.gateway_db.get_client_database(&federation_id);
150
151 let root_secret = RootSecret::StandardDoubleDerive(
152 Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
153 );
154 (db, root_secret)
155 };
156
157 Self::verify_client_config(&db, federation_id).await?;
158
159 let client_builder = self.create_client_builder(&config, gateway).await?;
160
161 if Client::is_initialized(&db).await {
162 client_builder
163 .open(self.connectors.clone(), db, root_secret)
164 .await
165 } else {
166 client_builder
167 .preview(self.connectors.clone(), &invite_code)
168 .await
169 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))?
170 .join(db, root_secret)
171 .await
172 }
173 .map(Arc::new)
174 .map_err(|err| AdminGatewayError::ClientCreationError(err.into()))
175 }
176
177 async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
180 let mut dbtx = db.begin_transaction_nc().await;
181 if let Some(config) = dbtx.get_value(&ClientConfigKey).await
182 && config.calculate_federation_id() != federation_id
183 {
184 return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
185 "Federation Id did not match saved federation ID".to_string()
186 )));
187 }
188 Ok(())
189 }
190
191 pub fn legacy_federations(&self, all_federations: BTreeSet<FederationId>) -> Vec<FederationId> {
194 all_federations
195 .into_iter()
196 .filter(|federation_id| {
197 let db_path = self.work_dir.join(format!("{federation_id}.db"));
198 db_path.exists()
199 })
200 .collect::<Vec<FederationId>>()
201 }
202}