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