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    /// Reads a plain root secret from a database to construct a database.
53    /// Only used for "legacy" federations before v0.5.0
54    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    /// Constructs the client builder with the modules, database, and connector
62    /// used to create clients for connected federations.
63    async fn create_client_builder(
64        &self,
65        federation_config: &FederationConfig,
66        gateway: Arc<Gateway>,
67    ) -> AdminResult<ClientBuilder> {
68        let FederationConfig {
69            federation_index,
70            connector,
71            ..
72        } = federation_config.to_owned();
73
74        let mut registry = self.registry.clone();
75
76        registry.attach(GatewayClientInit {
77            federation_index,
78            lightning_manager: gateway.clone(),
79        });
80
81        registry.attach(GatewayClientInitV2 {
82            gateway: gateway.clone(),
83        });
84
85        let mut client_builder = Client::builder()
86            .await
87            .map_err(AdminGatewayError::ClientCreationError)?
88            .with_iroh_enable_dht(true)
89            .with_iroh_enable_next(true);
90        client_builder.with_module_inits(registry);
91        client_builder.with_primary_module_kind(self.primary_module_kind.clone());
92        client_builder.with_connector(connector);
93        Ok(client_builder)
94    }
95
96    /// Recovers a client with the provided mnemonic. This function will wait
97    /// for the recoveries to finish, but a new client must be created
98    /// afterwards and waited on until the state machines have finished
99    /// for a balance to be present.
100    pub async fn recover(
101        &self,
102        config: FederationConfig,
103        gateway: Arc<Gateway>,
104        mnemonic: &Mnemonic,
105    ) -> AdminResult<()> {
106        let federation_id = config.invite_code.federation_id();
107        let db = gateway.gateway_db.get_client_database(&federation_id);
108        let client_builder = self.create_client_builder(&config, gateway.clone()).await?;
109        let root_secret = RootSecret::StandardDoubleDerive(
110            Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
111        );
112        let client = client_builder
113            .preview(&config.invite_code)
114            .await?
115            .recover(db, root_secret, None)
116            .await
117            .map(Arc::new)
118            .map_err(AdminGatewayError::ClientCreationError)?;
119        client
120            .wait_for_all_recoveries()
121            .await
122            .map_err(AdminGatewayError::ClientCreationError)?;
123        Ok(())
124    }
125
126    /// Builds a new client with the provided `FederationConfig` and `Mnemonic`.
127    /// Only used for newly joined federations.
128    pub async fn build(
129        &self,
130        config: FederationConfig,
131        gateway: Arc<Gateway>,
132        mnemonic: &Mnemonic,
133    ) -> AdminResult<fedimint_client::ClientHandleArc> {
134        let invite_code = config.invite_code.clone();
135        let federation_id = invite_code.federation_id();
136        let db_path = self.work_dir.join(format!("{federation_id}.db"));
137
138        let (db, root_secret) = if db_path.exists() {
139            let db = match self.db_backend {
140                DatabaseBackend::RocksDb => {
141                    let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone())
142                        .await
143                        .map_err(AdminGatewayError::ClientCreationError)?;
144                    Database::new(rocksdb, ModuleDecoderRegistry::default())
145                }
146                DatabaseBackend::CursedRedb => {
147                    let cursed_redb = fedimint_cursed_redb::MemAndRedb::new(db_path.clone())
148                        .await
149                        .map_err(AdminGatewayError::ClientCreationError)?;
150                    Database::new(cursed_redb, ModuleDecoderRegistry::default())
151                }
152            };
153            let root_secret = RootSecret::Custom(self.client_plainrootsecret(&db).await?);
154            (db, root_secret)
155        } else {
156            let db = gateway.gateway_db.get_client_database(&federation_id);
157
158            let root_secret = RootSecret::StandardDoubleDerive(
159                Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic),
160            );
161            (db, root_secret)
162        };
163
164        Self::verify_client_config(&db, federation_id).await?;
165
166        let client_builder = self.create_client_builder(&config, gateway).await?;
167
168        if Client::is_initialized(&db).await {
169            client_builder.open(db, root_secret).await
170        } else {
171            client_builder
172                .preview(&invite_code)
173                .await?
174                .join(db, root_secret)
175                .await
176        }
177        .map(Arc::new)
178        .map_err(AdminGatewayError::ClientCreationError)
179    }
180
181    /// Verifies that the saved `ClientConfig` contains the expected
182    /// federation's config.
183    async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
184        let mut dbtx = db.begin_transaction_nc().await;
185        if let Some(config) = dbtx.get_value(&ClientConfigKey).await
186            && config.calculate_federation_id() != federation_id
187        {
188            return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
189                "Federation Id did not match saved federation ID".to_string()
190            )));
191        }
192        Ok(())
193    }
194
195    /// Returns a vector of "legacy" federations which did not derive their
196    /// client secret's from the gateway's mnemonic.
197    pub fn legacy_federations(&self, all_federations: BTreeSet<FederationId>) -> Vec<FederationId> {
198        all_federations
199            .into_iter()
200            .filter(|federation_id| {
201                let db_path = self.work_dir.join(format!("{federation_id}.db"));
202                db_path.exists()
203            })
204            .collect::<Vec<FederationId>>()
205    }
206}