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