fedimint_gateway_server_db/
lib.rs

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