ln_gateway/
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::derivable_secret::{ChildId, DerivableSecret};
9use fedimint_client::module::init::ClientModuleInitRegistry;
10use fedimint_client::secret::{PlainRootSecretStrategy, RootSecretStrategy};
11use fedimint_client::{Client, ClientBuilder};
12use fedimint_core::config::FederationId;
13use fedimint_core::core::ModuleKind;
14use fedimint_core::db::{Database, IDatabaseTransactionOpsCoreTyped};
15use fedimint_core::module::registry::ModuleDecoderRegistry;
16
17use crate::db::{FederationConfig, GatewayDbExt};
18use crate::error::AdminGatewayError;
19use crate::gateway_module_v2::GatewayClientInitV2;
20use crate::state_machine::GatewayClientInit;
21use crate::{AdminResult, Gateway};
22
23#[derive(Debug, Clone)]
24pub struct GatewayClientBuilder {
25    work_dir: PathBuf,
26    registry: ClientModuleInitRegistry,
27    primary_module_kind: ModuleKind,
28}
29
30impl GatewayClientBuilder {
31    pub fn new(
32        work_dir: PathBuf,
33        registry: ClientModuleInitRegistry,
34        primary_module_kind: ModuleKind,
35    ) -> Self {
36        Self {
37            work_dir,
38            registry,
39            primary_module_kind,
40        }
41    }
42
43    pub fn data_dir(&self) -> PathBuf {
44        self.work_dir.clone()
45    }
46
47    /// Reads a plain root secret from a database to construct a database.
48    /// Only used for "legacy" federations before v0.5.0
49    async fn client_plainrootsecret(&self, db: &Database) -> AdminResult<DerivableSecret> {
50        let client_secret = Client::load_decodable_client_secret::<[u8; 64]>(db)
51            .await
52            .map_err(AdminGatewayError::ClientCreationError)?;
53        Ok(PlainRootSecretStrategy::to_root_secret(&client_secret))
54    }
55
56    /// Constructs the client builder with the modules, database, and connector
57    /// used to create clients for connected federations.
58    async fn create_client_builder(
59        &self,
60        db: Database,
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        if gateway.is_running_lnv1() {
73            registry.attach(GatewayClientInit {
74                federation_index,
75                gateway: gateway.clone(),
76            });
77        }
78
79        if gateway.is_running_lnv2() {
80            registry.attach(GatewayClientInitV2 {
81                gateway: gateway.clone(),
82            });
83        }
84
85        let mut client_builder = Client::builder(db)
86            .await
87            .map_err(AdminGatewayError::ClientCreationError)?;
88        client_builder.with_module_inits(registry);
89        client_builder.with_primary_module_kind(self.primary_module_kind.clone());
90        client_builder.with_connector(connector);
91        Ok(client_builder)
92    }
93
94    /// Recovers a client with the provided mnemonic. This function will wait
95    /// for the recoveries to finish, but a new client must be created
96    /// afterwards and waited on until the state machines have finished
97    /// for a balance to be present.
98    pub async fn recover(
99        &self,
100        config: FederationConfig,
101        gateway: Arc<Gateway>,
102        mnemonic: &Mnemonic,
103    ) -> AdminResult<()> {
104        let client_config = config
105            .connector
106            .download_from_invite_code(&config.invite_code)
107            .await
108            .map_err(AdminGatewayError::ClientCreationError)?;
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 secret = Self::derive_federation_secret(mnemonic, &federation_id);
115        let backup = client_builder
116            .download_backup_from_federation(
117                &secret,
118                &client_config,
119                config.invite_code.api_secret(),
120            )
121            .await
122            .map_err(AdminGatewayError::ClientCreationError)?;
123        let client = client_builder
124            .recover(
125                secret.clone(),
126                client_config,
127                config.invite_code.api_secret(),
128                backup,
129            )
130            .await
131            .map(Arc::new)
132            .map_err(AdminGatewayError::ClientCreationError)?;
133        client
134            .wait_for_all_recoveries()
135            .await
136            .map_err(AdminGatewayError::ClientCreationError)?;
137        Ok(())
138    }
139
140    /// Builds a new client with the provided `FederationConfig` and `Mnemonic`.
141    /// Only used for newly joined federations.
142    pub async fn build(
143        &self,
144        config: FederationConfig,
145        gateway: Arc<Gateway>,
146        mnemonic: &Mnemonic,
147    ) -> AdminResult<fedimint_client::ClientHandleArc> {
148        let invite_code = config.invite_code.clone();
149        let federation_id = invite_code.federation_id();
150        let db_path = self.work_dir.join(format!("{federation_id}.db"));
151
152        let (db, root_secret) = if db_path.exists() {
153            let rocksdb = fedimint_rocksdb::RocksDb::open(db_path.clone())
154                .map_err(AdminGatewayError::ClientCreationError)?;
155            let db = Database::new(rocksdb, ModuleDecoderRegistry::default());
156            let root_secret = self.client_plainrootsecret(&db).await?;
157            (db, root_secret)
158        } else {
159            let db = gateway.gateway_db.get_client_database(&federation_id);
160            let secret = Self::derive_federation_secret(mnemonic, &federation_id);
161            (db, secret)
162        };
163
164        Self::verify_client_config(&db, federation_id).await?;
165
166        let client_builder = self.create_client_builder(db, &config, gateway).await?;
167
168        if Client::is_initialized(client_builder.db_no_decoders()).await {
169            client_builder.open(root_secret).await
170        } else {
171            let client_config = config
172                .connector
173                .download_from_invite_code(&invite_code)
174                .await
175                .map_err(AdminGatewayError::ClientCreationError)?;
176            client_builder
177                .join(root_secret, client_config.clone(), invite_code.api_secret())
178                .await
179        }
180        .map(Arc::new)
181        .map_err(AdminGatewayError::ClientCreationError)
182    }
183
184    /// Verifies that the saved `ClientConfig` contains the expected
185    /// federation's config.
186    async fn verify_client_config(db: &Database, federation_id: FederationId) -> AdminResult<()> {
187        let mut dbtx = db.begin_transaction_nc().await;
188        if let Some(config) = dbtx.get_value(&ClientConfigKey).await {
189            if config.calculate_federation_id() != federation_id {
190                return Err(AdminGatewayError::ClientCreationError(anyhow::anyhow!(
191                    "Federation Id did not match saved federation ID".to_string()
192                )));
193            }
194        }
195        Ok(())
196    }
197
198    /// Derives a per-federation secret according to Fedimint's multi-federation
199    /// secret derivation policy.
200    fn derive_federation_secret(
201        mnemonic: &Mnemonic,
202        federation_id: &FederationId,
203    ) -> DerivableSecret {
204        let global_root_secret = Bip39RootSecretStrategy::<12>::to_root_secret(mnemonic);
205        let multi_federation_root_secret = global_root_secret.child_key(ChildId(0));
206        let federation_root_secret = multi_federation_root_secret.federation_key(federation_id);
207        let federation_wallet_root_secret = federation_root_secret.child_key(ChildId(0));
208        federation_wallet_root_secret.child_key(ChildId(0))
209    }
210
211    /// Returns a vector of "legacy" federations which did not derive their
212    /// client secret's from the gateway's mnemonic.
213    pub fn legacy_federations(&self, all_federations: BTreeSet<FederationId>) -> Vec<FederationId> {
214        all_federations
215            .into_iter()
216            .filter(|federation_id| {
217                let db_path = self.work_dir.join(format!("{federation_id}.db"));
218                db_path.exists()
219            })
220            .collect::<Vec<FederationId>>()
221    }
222}