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