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#[derive(Debug, Encodable, Decodable)]
402struct PreimageAuthenticationPrefix;
403
404impl_db_lookup!(
405    key = PreimageAuthentication,
406    query_prefix = PreimageAuthenticationPrefix
407);
408
409pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, GeneralDbMigrationFn> {
410    let mut migrations: BTreeMap<DatabaseVersion, GeneralDbMigrationFn> = BTreeMap::new();
411    migrations.insert(
412        DatabaseVersion(0),
413        Box::new(|ctx| migrate_to_v1(ctx).boxed()),
414    );
415    migrations.insert(
416        DatabaseVersion(1),
417        Box::new(|ctx| migrate_to_v2(ctx).boxed()),
418    );
419    migrations.insert(
420        DatabaseVersion(2),
421        Box::new(|ctx| migrate_to_v3(ctx).boxed()),
422    );
423    migrations.insert(
424        DatabaseVersion(3),
425        Box::new(|ctx| migrate_to_v4(ctx).boxed()),
426    );
427    migrations.insert(
428        DatabaseVersion(4),
429        Box::new(|ctx| migrate_to_v5(ctx).boxed()),
430    );
431    migrations
432}
433
434async fn migrate_to_v1(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
435    /// Creates a password hash by appending a 4 byte salt to the plaintext
436    /// password.
437    fn hash_password(plaintext_password: &str, salt: [u8; 16]) -> sha256::Hash {
438        let mut bytes = Vec::new();
439        bytes.append(&mut plaintext_password.consensus_encode_to_vec());
440        bytes.append(&mut salt.consensus_encode_to_vec());
441        sha256::Hash::hash(&bytes)
442    }
443
444    let mut dbtx = ctx.dbtx();
445
446    // If there is no old gateway configuration, there is nothing to do.
447    if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
448        let password_salt: [u8; 16] = rand::thread_rng().r#gen();
449        let hashed_password = hash_password(&old_gateway_config.password, password_salt);
450        let new_gateway_config = GatewayConfigurationV1 {
451            hashed_password,
452            num_route_hints: old_gateway_config.num_route_hints,
453            routing_fees: old_gateway_config.routing_fees,
454            network: old_gateway_config.network,
455            password_salt,
456        };
457        dbtx.insert_entry(&GatewayConfigurationKeyV1, &new_gateway_config)
458            .await;
459    }
460
461    Ok(())
462}
463
464async fn migrate_to_v2(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
465    let mut dbtx = ctx.dbtx();
466
467    // If there is no old federation configuration, there is nothing to do.
468    for (old_federation_id, _old_federation_config) in dbtx.load_federation_configs_v0().await {
469        if let Some(old_federation_config) = dbtx
470            .remove_entry(&FederationConfigKeyV0 {
471                id: old_federation_id,
472            })
473            .await
474        {
475            let new_federation_config = FederationConfigV1 {
476                invite_code: old_federation_config.invite_code,
477                federation_index: old_federation_config.federation_index,
478                timelock_delta: old_federation_config.timelock_delta,
479                fees: old_federation_config.fees,
480                connector: Connector::default(),
481            };
482            let new_federation_key = FederationConfigKeyV1 {
483                id: old_federation_id,
484            };
485            dbtx.insert_entry(&new_federation_key, &new_federation_config)
486                .await;
487        }
488    }
489    Ok(())
490}
491
492async fn migrate_to_v3(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
493    let mut dbtx = ctx.dbtx();
494
495    // If there is no old gateway configuration, there is nothing to do.
496    if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV1).await {
497        let new_gateway_config = GatewayConfigurationV2 {
498            num_route_hints: old_gateway_config.num_route_hints,
499            routing_fees: old_gateway_config.routing_fees,
500            network: old_gateway_config.network,
501        };
502        dbtx.insert_entry(&GatewayConfigurationKeyV2, &new_gateway_config)
503            .await;
504    }
505
506    Ok(())
507}
508
509async fn migrate_to_v4(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
510    let mut dbtx = ctx.dbtx();
511
512    dbtx.remove_entry(&GatewayConfigurationKeyV2).await;
513
514    let configs = dbtx
515        .find_by_prefix(&FederationConfigKeyPrefixV1)
516        .await
517        .collect::<Vec<_>>()
518        .await;
519    for (fed_id, _old_config) in configs {
520        if let Some(old_federation_config) = dbtx.remove_entry(&fed_id).await {
521            let new_fed_config = FederationConfig {
522                invite_code: old_federation_config.invite_code,
523                federation_index: old_federation_config.federation_index,
524                lightning_fee: old_federation_config.fees.into(),
525                transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
526                connector: Connector::default(),
527            };
528            let new_key = FederationConfigKey { id: fed_id.id };
529            dbtx.insert_new_entry(&new_key, &new_fed_config).await;
530        }
531    }
532    Ok(())
533}
534
535/// Introduced in v0.5, there is a db key clash between the `FederationConfig`
536/// record and the isolated databases used for each client. We must migrate the
537/// isolated databases to be behind the `ClientDatabase` prefix to allow the
538/// gateway to properly read the federation configs.
539async fn migrate_to_v5(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
540    let mut dbtx = ctx.dbtx();
541    migrate_federation_configs(&mut dbtx).await
542}
543
544async fn migrate_federation_configs(
545    dbtx: &mut DatabaseTransaction<'_>,
546) -> Result<(), anyhow::Error> {
547    // We need to migrate all isolated database entries to be behind the 0x10
548    // prefix. The problem is, if there is a `FederationId` that starts with
549    // 0x04, we cannot read the `FederationId` because the database will be confused
550    // between the isolated DB and the `FederationConfigKey` record. To solve this,
551    // we try and decode each key as a Federation ID and each value as a
552    // FederationConfig. If that is successful and the federation ID in the
553    // config matches the key, then we skip that record and migrate the rest of
554    // the entries.
555    let problem_entries = dbtx
556        .raw_find_by_prefix(&[0x04])
557        .await?
558        .collect::<BTreeMap<_, _>>()
559        .await;
560    for (mut problem_key, value) in problem_entries {
561        // Try and decode the key as a FederationId and the value as a FederationConfig
562        // The key should be 33 bytes because a FederationID is 32 bytes and there is a
563        // 1 byte prefix.
564        if problem_key.len() == 33 {
565            if let Ok(federation_id) = FederationId::consensus_decode_whole(
566                &problem_key[1..33],
567                &ModuleDecoderRegistry::default(),
568            ) {
569                if let Ok(federation_config) = FederationConfig::consensus_decode_whole(
570                    &value,
571                    &ModuleDecoderRegistry::default(),
572                ) {
573                    if federation_id == federation_config.invite_code.federation_id() {
574                        continue;
575                    }
576                }
577            }
578        }
579
580        dbtx.raw_remove_entry(&problem_key).await?;
581        let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
582        new_key.append(&mut problem_key);
583        dbtx.raw_insert_bytes(&new_key, &value).await?;
584    }
585
586    // Migrate all entries of the isolated databases that don't overlap with
587    // `FederationConfig` entries.
588    let fed_ids = dbtx
589        .find_by_prefix(&FederationConfigKeyPrefix)
590        .await
591        .collect::<BTreeMap<_, _>>()
592        .await;
593    for fed_id in fed_ids.keys() {
594        let federation_id_bytes = fed_id.id.consensus_encode_to_vec();
595        let isolated_entries = dbtx
596            .raw_find_by_prefix(&federation_id_bytes)
597            .await?
598            .collect::<BTreeMap<_, _>>()
599            .await;
600        for (mut key, value) in isolated_entries {
601            dbtx.raw_remove_entry(&key).await?;
602            let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
603            new_key.append(&mut key);
604            dbtx.raw_insert_bytes(&new_key, &value).await?;
605        }
606    }
607
608    Ok(())
609}
610
611#[derive(Debug, Encodable, Decodable)]
612struct RegisteredIncomingContractKey(pub PaymentImage);
613
614#[derive(Debug, Encodable, Decodable)]
615pub struct RegisteredIncomingContract {
616    pub federation_id: FederationId,
617    /// The amount of the incoming contract, in msats.
618    pub incoming_amount_msats: u64,
619    pub contract: IncomingContract,
620}
621
622impl_db_record!(
623    key = RegisteredIncomingContractKey,
624    value = RegisteredIncomingContract,
625    db_prefix = DbKeyPrefix::RegisteredIncomingContract,
626);
627
628#[cfg(test)]
629mod migration_tests;