Skip to main content

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