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::error::AdminGatewayError;
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 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 db: Database,
62 federation_config: &FederationConfig,
63 gateway: Arc<Gateway>,
64 ) -> AdminResult<ClientBuilder> {
65 let FederationConfig {
66 federation_index,
67 connector,
68 ..
69 } = federation_config.to_owned();
70
71 let mut registry = self.registry.clone();
72
73 if gateway.is_running_lnv1() {
74 registry.attach(GatewayClientInit {
75 federation_index,
76 lightning_manager: gateway.clone(),
77 });
78 }
79
80 if gateway.is_running_lnv2() {
81 registry.attach(GatewayClientInitV2 {
82 gateway: gateway.clone(),
83 });
84 }
85
86 let mut client_builder = Client::builder(db)
87 .await
88 .map_err(AdminGatewayError::ClientCreationError)?;
89 client_builder.with_module_inits(registry);
90 client_builder.with_primary_module_kind(self.primary_module_kind.clone());
91 client_builder.with_connector(connector);
92 Ok(client_builder)
93 }
94
95 pub async fn recover(
100 &self,
101 config: FederationConfig,
102 gateway: Arc<Gateway>,
103 mnemonic: &Mnemonic,
104 ) -> AdminResult<()> {
105 let federation_id = config.invite_code.federation_id();
106 let db = gateway.gateway_db.get_client_database(&federation_id);
107 let client_builder = self
108 .create_client_builder(db, &config, gateway.clone())
109 .await?;
110 let root_secret = RootSecret::StandardDoubleDerive(
111 Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
112 );
113 let client = client_builder
114 .preview(&config.invite_code)
115 .await?
116 .recover(root_secret, None)
117 .await
118 .map(Arc::new)
119 .map_err(AdminGatewayError::ClientCreationError)?;
120 client
121 .wait_for_all_recoveries()
122 .await
123 .map_err(AdminGatewayError::ClientCreationError)?;
124 Ok(())
125 }
126
127 pub async fn build(
130 &self,
131 config: FederationConfig,
132 gateway: Arc<Gateway>,
133 mnemonic: &Mnemonic,
134 ) -> AdminResult<fedimint_client::ClientHandleArc> {
135 let invite_code = config.invite_code.clone();
136 let federation_id = invite_code.federation_id();
137 let db_path = self.work_dir.join(format!("{federation_id}.db"));
138
139 let (db, root_secret) = if db_path.exists() {
140 let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone())
141 .await
142 .map_err(AdminGatewayError::ClientCreationError)?;
143 let db = Database::new(rocksdb, ModuleDecoderRegistry::default());
144 let root_secret = RootSecret::Custom(self.client_plainrootsecret(&db).await?);
145 (db, root_secret)
146 } else {
147 let db = gateway.gateway_db.get_client_database(&federation_id);
148
149 let root_secret = RootSecret::StandardDoubleDerive(
150 Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
151 );
152 (db, root_secret)
153 };
154
155 Self::verify_client_config(&db, federation_id).await?;
156
157 let client_builder = self.create_client_builder(db, &config, gateway).await?;
158
159 if Client::is_initialized(client_builder.db_no_decoders()).await {
160 client_builder.open(root_secret).await
161 } else {
162 client_builder
163 .preview(&invite_code)
164 .await?
165 .join(root_secret)
166 .await
167 }
168 .map(Arc::new)
169 .map_err(AdminGatewayError::ClientCreationError)
170 }
171
172 async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
175 let mut dbtx = db.begin_transaction_nc().await;
176 if let Some(config) = dbtx.get_value(&ClientConfigKey).await {
177 if config.calculate_federation_id() != federation_id {
178 return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
179 "Federation Id did not match saved federation ID".to_string()
180 )));
181 }
182 }
183 Ok(())
184 }
185
186 pub fn legacy_federations(&self, all_federations: BTreeSet<FederationId>) -> Vec<FederationId> {
189 all_federations
190 .into_iter()
191 .filter(|federation_id| {
192 let db_path = self.work_dir.join(format!("{federation_id}.db"));
193 db_path.exists()
194 })
195 .collect::<Vec<FederationId>>()
196 }
197}