fedimint_gateway_server_db/
lib.rs

1use std::collections::BTreeMap;
2
3use bitcoin::hashes::{Hash, sha256};
4use fedimint_api_client::api::net::Connector;
5use fedimint_core::config::FederationId;
6use fedimint_core::db::{
7    Database, DatabaseTransaction, DatabaseVersion, GeneralDbMigrationFn,
8    GeneralDbMigrationFnContext, IDatabaseTransactionOpsCore, IDatabaseTransactionOpsCoreTyped,
9};
10use fedimint_core::encoding::btc::NetworkLegacyEncodingWrapper;
11use fedimint_core::encoding::{Decodable, Encodable};
12use fedimint_core::invite_code::InviteCode;
13use fedimint_core::module::registry::ModuleDecoderRegistry;
14use fedimint_core::{Amount, impl_db_lookup, impl_db_record, push_db_pair_items, secp256k1};
15use fedimint_gateway_common::FederationConfig;
16use fedimint_ln_common::serde_routing_fees;
17use fedimint_lnv2_common::contracts::{IncomingContract, PaymentImage};
18use fedimint_lnv2_common::gateway_api::PaymentFee;
19use futures::{FutureExt, StreamExt};
20use lightning_invoice::RoutingFees;
21use rand::Rng;
22use rand::rngs::OsRng;
23use secp256k1::{Keypair, Secp256k1};
24use serde::{Deserialize, Serialize};
25use strum::IntoEnumIterator;
26use strum_macros::EnumIter;
27
28pub trait GatewayDbExt {
29    fn get_client_database(&self, federation_id: &FederationId) -> Database;
30}
31
32impl GatewayDbExt for Database {
33    fn get_client_database(&self, federation_id: &FederationId) -> Database {
34        let mut prefix = vec![DbKeyPrefix::ClientDatabase as u8];
35        prefix.append(&mut federation_id.consensus_encode_to_vec());
36        self.with_prefix(prefix)
37    }
38}
39
40#[allow(async_fn_in_trait)]
41pub trait GatewayDbtxNcExt {
42    async fn save_federation_config(&mut self, config: &FederationConfig);
43    async fn load_federation_configs_v0(&mut self) -> BTreeMap<FederationId, FederationConfigV0>;
44    async fn load_federation_configs(&mut self) -> BTreeMap<FederationId, FederationConfig>;
45    async fn load_federation_config(
46        &mut self,
47        federation_id: FederationId,
48    ) -> Option<FederationConfig>;
49    async fn remove_federation_config(&mut self, federation_id: FederationId);
50
51    /// Returns the keypair that uniquely identifies the gateway.
52    async fn load_gateway_keypair(&mut self) -> Option<Keypair>;
53
54    /// Returns the keypair that uniquely identifies the gateway.
55    ///
56    /// # Panics
57    /// Gateway keypair does not exist.
58    async fn load_gateway_keypair_assert_exists(&mut self) -> Keypair;
59
60    /// Returns the keypair that uniquely identifies the gateway, creating it if
61    /// it does not exist. Remember to commit the transaction after calling this
62    /// method.
63    async fn load_or_create_gateway_keypair(&mut self) -> Keypair;
64
65    async fn save_new_preimage_authentication(
66        &mut self,
67        payment_hash: sha256::Hash,
68        preimage_auth: sha256::Hash,
69    );
70
71    async fn load_preimage_authentication(
72        &mut self,
73        payment_hash: sha256::Hash,
74    ) -> Option<sha256::Hash>;
75
76    /// Saves a registered incoming contract, returning the previous contract
77    /// with the same payment hash if it existed.
78    async fn save_registered_incoming_contract(
79        &mut self,
80        federation_id: FederationId,
81        incoming_amount: Amount,
82        contract: IncomingContract,
83    ) -> Option<RegisteredIncomingContract>;
84
85    async fn load_registered_incoming_contract(
86        &mut self,
87        payment_image: PaymentImage,
88    ) -> Option<RegisteredIncomingContract>;
89
90    /// Reads and serializes structures from the gateway's database for the
91    /// purpose for serializing to JSON for inspection.
92    async fn dump_database(
93        &mut self,
94        prefix_names: Vec<String>,
95    ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>>;
96}
97
98impl<Cap: Send> GatewayDbtxNcExt for DatabaseTransaction<'_, Cap> {
99    async fn save_federation_config(&mut self, config: &FederationConfig) {
100        let id = config.invite_code.federation_id();
101        self.insert_entry(&FederationConfigKey { id }, config).await;
102    }
103
104    async fn load_federation_configs_v0(&mut self) -> BTreeMap<FederationId, FederationConfigV0> {
105        self.find_by_prefix(&FederationConfigKeyPrefixV0)
106            .await
107            .map(|(key, config): (FederationConfigKeyV0, FederationConfigV0)| (key.id, config))
108            .collect::<BTreeMap<FederationId, FederationConfigV0>>()
109            .await
110    }
111
112    async fn load_federation_configs(&mut self) -> BTreeMap<FederationId, FederationConfig> {
113        self.find_by_prefix(&FederationConfigKeyPrefix)
114            .await
115            .map(|(key, config): (FederationConfigKey, FederationConfig)| (key.id, config))
116            .collect::<BTreeMap<FederationId, FederationConfig>>()
117            .await
118    }
119
120    async fn load_federation_config(
121        &mut self,
122        federation_id: FederationId,
123    ) -> Option<FederationConfig> {
124        self.get_value(&FederationConfigKey { id: federation_id })
125            .await
126    }
127
128    async fn remove_federation_config(&mut self, federation_id: FederationId) {
129        self.remove_entry(&FederationConfigKey { id: federation_id })
130            .await;
131    }
132
133    async fn load_gateway_keypair(&mut self) -> Option<Keypair> {
134        self.get_value(&GatewayPublicKey).await
135    }
136
137    async fn load_gateway_keypair_assert_exists(&mut self) -> Keypair {
138        self.get_value(&GatewayPublicKey)
139            .await
140            .expect("Gateway keypair does not exist")
141    }
142
143    async fn load_or_create_gateway_keypair(&mut self) -> Keypair {
144        if let Some(key_pair) = self.get_value(&GatewayPublicKey).await {
145            key_pair
146        } else {
147            let context = Secp256k1::new();
148            let (secret_key, _public_key) = context.generate_keypair(&mut OsRng);
149            let key_pair = Keypair::from_secret_key(&context, &secret_key);
150            self.insert_new_entry(&GatewayPublicKey, &key_pair).await;
151            key_pair
152        }
153    }
154
155    async fn save_new_preimage_authentication(
156        &mut self,
157        payment_hash: sha256::Hash,
158        preimage_auth: sha256::Hash,
159    ) {
160        self.insert_new_entry(&PreimageAuthentication { payment_hash }, &preimage_auth)
161            .await;
162    }
163
164    async fn load_preimage_authentication(
165        &mut self,
166        payment_hash: sha256::Hash,
167    ) -> Option<sha256::Hash> {
168        self.get_value(&PreimageAuthentication { payment_hash })
169            .await
170    }
171
172    async fn save_registered_incoming_contract(
173        &mut self,
174        federation_id: FederationId,
175        incoming_amount: Amount,
176        contract: IncomingContract,
177    ) -> Option<RegisteredIncomingContract> {
178        self.insert_entry(
179            &RegisteredIncomingContractKey(contract.commitment.payment_image.clone()),
180            &RegisteredIncomingContract {
181                federation_id,
182                incoming_amount_msats: incoming_amount.msats,
183                contract,
184            },
185        )
186        .await
187    }
188
189    async fn load_registered_incoming_contract(
190        &mut self,
191        payment_image: PaymentImage,
192    ) -> Option<RegisteredIncomingContract> {
193        self.get_value(&RegisteredIncomingContractKey(payment_image))
194            .await
195    }
196
197    async fn dump_database(
198        &mut self,
199        prefix_names: Vec<String>,
200    ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> {
201        let mut gateway_items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> =
202            BTreeMap::new();
203        let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
204            prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
205        });
206
207        for table in filtered_prefixes {
208            match table {
209                DbKeyPrefix::FederationConfig => {
210                    push_db_pair_items!(
211                        self,
212                        FederationConfigKeyPrefix,
213                        FederationConfigKey,
214                        FederationConfig,
215                        gateway_items,
216                        "Federation Config"
217                    );
218                }
219                DbKeyPrefix::GatewayPublicKey => {
220                    if let Some(public_key) = self.load_gateway_keypair().await {
221                        gateway_items
222                            .insert("Gateway Public Key".to_string(), Box::new(public_key));
223                    }
224                }
225                _ => {}
226            }
227        }
228
229        gateway_items
230    }
231}
232
233#[repr(u8)]
234#[derive(Clone, EnumIter, Debug)]
235enum DbKeyPrefix {
236    FederationConfig = 0x04,
237    GatewayPublicKey = 0x06,
238    GatewayConfiguration = 0x07,
239    PreimageAuthentication = 0x08,
240    RegisteredIncomingContract = 0x09,
241    ClientDatabase = 0x10,
242}
243
244impl std::fmt::Display for DbKeyPrefix {
245    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
246        write!(f, "{self:?}")
247    }
248}
249
250#[derive(Debug, Encodable, Decodable)]
251struct FederationConfigKeyPrefixV0;
252
253#[derive(Debug, Encodable, Decodable)]
254struct FederationConfigKeyPrefixV1;
255
256#[derive(Debug, Encodable, Decodable)]
257struct FederationConfigKeyPrefix;
258
259#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
260struct FederationConfigKeyV0 {
261    id: FederationId,
262}
263
264#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
265pub struct FederationConfigV0 {
266    pub invite_code: InviteCode,
267    pub federation_index: u64,
268    pub timelock_delta: u64,
269    #[serde(with = "serde_routing_fees")]
270    pub fees: RoutingFees,
271}
272
273#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
274struct FederationConfigKeyV1 {
275    id: FederationId,
276}
277
278#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
279pub struct FederationConfigV1 {
280    pub invite_code: InviteCode,
281    // Unique integer identifier per-federation that is assigned when the gateways joins a
282    // federation.
283    #[serde(alias = "mint_channel_id")]
284    pub federation_index: u64,
285    pub timelock_delta: u64,
286    #[serde(with = "serde_routing_fees")]
287    pub fees: RoutingFees,
288    pub connector: Connector,
289}
290
291#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
292struct FederationConfigKey {
293    id: FederationId,
294}
295
296impl_db_record!(
297    key = FederationConfigKeyV0,
298    value = FederationConfigV0,
299    db_prefix = DbKeyPrefix::FederationConfig,
300);
301
302impl_db_record!(
303    key = FederationConfigKeyV1,
304    value = FederationConfigV1,
305    db_prefix = DbKeyPrefix::FederationConfig,
306);
307
308impl_db_record!(
309    key = FederationConfigKey,
310    value = FederationConfig,
311    db_prefix = DbKeyPrefix::FederationConfig,
312);
313
314impl_db_lookup!(
315    key = FederationConfigKeyV0,
316    query_prefix = FederationConfigKeyPrefixV0
317);
318impl_db_lookup!(
319    key = FederationConfigKeyV1,
320    query_prefix = FederationConfigKeyPrefixV1
321);
322impl_db_lookup!(
323    key = FederationConfigKey,
324    query_prefix = FederationConfigKeyPrefix
325);
326
327#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
328struct GatewayPublicKey;
329
330impl_db_record!(
331    key = GatewayPublicKey,
332    value = Keypair,
333    db_prefix = DbKeyPrefix::GatewayPublicKey,
334);
335
336#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
337struct GatewayConfigurationKeyV0;
338
339#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
340struct GatewayConfigurationV0 {
341    password: String,
342    num_route_hints: u32,
343    #[serde(with = "serde_routing_fees")]
344    routing_fees: RoutingFees,
345    network: NetworkLegacyEncodingWrapper,
346}
347
348#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
349pub struct GatewayConfigurationKeyV1;
350
351#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
352pub struct GatewayConfigurationV1 {
353    pub hashed_password: sha256::Hash,
354    pub num_route_hints: u32,
355    #[serde(with = "serde_routing_fees")]
356    pub routing_fees: RoutingFees,
357    pub network: NetworkLegacyEncodingWrapper,
358    pub password_salt: [u8; 16],
359}
360
361#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
362pub struct GatewayConfigurationKeyV2;
363
364#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
365pub struct GatewayConfigurationV2 {
366    pub num_route_hints: u32,
367    #[serde(with = "serde_routing_fees")]
368    pub routing_fees: RoutingFees,
369    pub network: NetworkLegacyEncodingWrapper,
370}
371
372impl_db_record!(
373    key = GatewayConfigurationKeyV0,
374    value = GatewayConfigurationV0,
375    db_prefix = DbKeyPrefix::GatewayConfiguration,
376);
377
378impl_db_record!(
379    key = GatewayConfigurationKeyV1,
380    value = GatewayConfigurationV1,
381    db_prefix = DbKeyPrefix::GatewayConfiguration,
382);
383
384impl_db_record!(
385    key = GatewayConfigurationKeyV2,
386    value = GatewayConfigurationV2,
387    db_prefix = DbKeyPrefix::GatewayConfiguration,
388);
389
390#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
391struct PreimageAuthentication {
392    payment_hash: sha256::Hash,
393}
394
395impl_db_record!(
396    key = PreimageAuthentication,
397    value = sha256::Hash,
398    db_prefix = DbKeyPrefix::PreimageAuthentication
399);
400
401#[allow(dead_code)] // used in tests
402#[derive(Debug, Encodable, Decodable)]
403struct PreimageAuthenticationPrefix;
404
405impl_db_lookup!(
406    key = PreimageAuthentication,
407    query_prefix = PreimageAuthenticationPrefix
408);
409
410pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, GeneralDbMigrationFn> {
411    let mut migrations: BTreeMap<DatabaseVersion, GeneralDbMigrationFn> = BTreeMap::new();
412    migrations.insert(
413        DatabaseVersion(0),
414        Box::new(|ctx| migrate_to_v1(ctx).boxed()),
415    );
416    migrations.insert(
417        DatabaseVersion(1),
418        Box::new(|ctx| migrate_to_v2(ctx).boxed()),
419    );
420    migrations.insert(
421        DatabaseVersion(2),
422        Box::new(|ctx| migrate_to_v3(ctx).boxed()),
423    );
424    migrations.insert(
425        DatabaseVersion(3),
426        Box::new(|ctx| migrate_to_v4(ctx).boxed()),
427    );
428    migrations.insert(
429        DatabaseVersion(4),
430        Box::new(|ctx| migrate_to_v5(ctx).boxed()),
431    );
432    migrations
433}
434
435async fn migrate_to_v1(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
436    /// Creates a password hash by appending a 4 byte salt to the plaintext
437    /// password.
438    fn hash_password(plaintext_password: &str, salt: [u8; 16]) -> sha256::Hash {
439        let mut bytes = Vec::new();
440        bytes.append(&mut plaintext_password.consensus_encode_to_vec());
441        bytes.append(&mut salt.consensus_encode_to_vec());
442        sha256::Hash::hash(&bytes)
443    }
444
445    let mut dbtx = ctx.dbtx();
446
447    // If there is no old gateway configuration, there is nothing to do.
448    if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
449        let password_salt: [u8; 16] = rand::thread_rng().r#gen();
450        let hashed_password = hash_password(&old_gateway_config.password, password_salt);
451        let new_gateway_config = GatewayConfigurationV1 {
452            hashed_password,
453            num_route_hints: old_gateway_config.num_route_hints,
454            routing_fees: old_gateway_config.routing_fees,
455            network: old_gateway_config.network,
456            password_salt,
457        };
458        dbtx.insert_entry(&GatewayConfigurationKeyV1, &new_gateway_config)
459            .await;
460    }
461
462    Ok(())
463}
464
465async fn migrate_to_v2(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
466    let mut dbtx = ctx.dbtx();
467
468    // If there is no old federation configuration, there is nothing to do.
469    for (old_federation_id, _old_federation_config) in dbtx.load_federation_configs_v0().await {
470        if let Some(old_federation_config) = dbtx
471            .remove_entry(&FederationConfigKeyV0 {
472                id: old_federation_id,
473            })
474            .await
475        {
476            let new_federation_config = FederationConfigV1 {
477                invite_code: old_federation_config.invite_code,
478                federation_index: old_federation_config.federation_index,
479                timelock_delta: old_federation_config.timelock_delta,
480                fees: old_federation_config.fees,
481                connector: Connector::default(),
482            };
483            let new_federation_key = FederationConfigKeyV1 {
484                id: old_federation_id,
485            };
486            dbtx.insert_entry(&new_federation_key, &new_federation_config)
487                .await;
488        }
489    }
490    Ok(())
491}
492
493async fn migrate_to_v3(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
494    let mut dbtx = ctx.dbtx();
495
496    // If there is no old gateway configuration, there is nothing to do.
497    if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV1).await {
498        let new_gateway_config = GatewayConfigurationV2 {
499            num_route_hints: old_gateway_config.num_route_hints,
500            routing_fees: old_gateway_config.routing_fees,
501            network: old_gateway_config.network,
502        };
503        dbtx.insert_entry(&GatewayConfigurationKeyV2, &new_gateway_config)
504            .await;
505    }
506
507    Ok(())
508}
509
510async fn migrate_to_v4(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
511    let mut dbtx = ctx.dbtx();
512
513    dbtx.remove_entry(&GatewayConfigurationKeyV2).await;
514
515    let configs = dbtx
516        .find_by_prefix(&FederationConfigKeyPrefixV1)
517        .await
518        .collect::<Vec<_>>()
519        .await;
520    for (fed_id, _old_config) in configs {
521        if let Some(old_federation_config) = dbtx.remove_entry(&fed_id).await {
522            let new_fed_config = FederationConfig {
523                invite_code: old_federation_config.invite_code,
524                federation_index: old_federation_config.federation_index,
525                lightning_fee: old_federation_config.fees.into(),
526                transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
527                connector: Connector::default(),
528            };
529            let new_key = FederationConfigKey { id: fed_id.id };
530            dbtx.insert_new_entry(&new_key, &new_fed_config).await;
531        }
532    }
533    Ok(())
534}
535
536/// Introduced in v0.5, there is a db key clash between the `FederationConfig`
537/// record and the isolated databases used for each client. We must migrate the
538/// isolated databases to be behind the `ClientDatabase` prefix to allow the
539/// gateway to properly read the federation configs.
540async fn migrate_to_v5(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
541    let mut dbtx = ctx.dbtx();
542    migrate_federation_configs(&mut dbtx).await
543}
544
545async fn migrate_federation_configs(
546    dbtx: &mut DatabaseTransaction<'_>,
547) -> Result<(), anyhow::Error> {
548    // We need to migrate all isolated database entries to be behind the 0x10
549    // prefix. The problem is, if there is a `FederationId` that starts with
550    // 0x04, we cannot read the `FederationId` because the database will be confused
551    // between the isolated DB and the `FederationConfigKey` record. To solve this,
552    // we try and decode each key as a Federation ID and each value as a
553    // FederationConfig. If that is successful and the federation ID in the
554    // config matches the key, then we skip that record and migrate the rest of
555    // the entries.
556    let problem_entries = dbtx
557        .raw_find_by_prefix(&[0x04])
558        .await?
559        .collect::<BTreeMap<_, _>>()
560        .await;
561    for (mut problem_key, value) in problem_entries {
562        // Try and decode the key as a FederationId and the value as a FederationConfig
563        // The key should be 33 bytes because a FederationID is 32 bytes and there is a
564        // 1 byte prefix.
565        if problem_key.len() == 33
566            && let Ok(federation_id) = FederationId::consensus_decode_whole(
567                &problem_key[1..33],
568                &ModuleDecoderRegistry::default(),
569            )
570            && let Ok(federation_config) =
571                FederationConfig::consensus_decode_whole(&value, &ModuleDecoderRegistry::default())
572            && federation_id == federation_config.invite_code.federation_id()
573        {
574            continue;
575        }
576
577        dbtx.raw_remove_entry(&problem_key).await?;
578        let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
579        new_key.append(&mut problem_key);
580        dbtx.raw_insert_bytes(&new_key, &value).await?;
581    }
582
583    // Migrate all entries of the isolated databases that don't overlap with
584    // `FederationConfig` entries.
585    let fed_ids = dbtx
586        .find_by_prefix(&FederationConfigKeyPrefix)
587        .await
588        .collect::<BTreeMap<_, _>>()
589        .await;
590    for fed_id in fed_ids.keys() {
591        let federation_id_bytes = fed_id.id.consensus_encode_to_vec();
592        let isolated_entries = dbtx
593            .raw_find_by_prefix(&federation_id_bytes)
594            .await?
595            .collect::<BTreeMap<_, _>>()
596            .await;
597        for (mut key, value) in isolated_entries {
598            dbtx.raw_remove_entry(&key).await?;
599            let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
600            new_key.append(&mut key);
601            dbtx.raw_insert_bytes(&new_key, &value).await?;
602        }
603    }
604
605    Ok(())
606}
607
608#[derive(Debug, Encodable, Decodable)]
609struct RegisteredIncomingContractKey(pub PaymentImage);
610
611#[derive(Debug, Encodable, Decodable)]
612pub struct RegisteredIncomingContract {
613    pub federation_id: FederationId,
614    /// The amount of the incoming contract, in msats.
615    pub incoming_amount_msats: u64,
616    pub contract: IncomingContract,
617}
618
619impl_db_record!(
620    key = RegisteredIncomingContractKey,
621    value = RegisteredIncomingContract,
622    db_prefix = DbKeyPrefix::RegisteredIncomingContract,
623);
624
625#[cfg(test)]
626mod migration_tests;