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