Skip to main content

fedimint_gateway_server_db/
lib.rs

1use std::collections::{BTreeMap, BTreeSet};
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, Encodable, Decodable)]
434struct FederationConfigKeyPrefixV2;
435
436#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
437struct FederationConfigKeyV2 {
438    id: FederationId,
439}
440
441/// The federation config as stored before `payment_policies` was added.
442#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
443pub struct FederationConfigV2 {
444    pub invite_code: InviteCode,
445    #[serde(alias = "mint_channel_id")]
446    pub federation_index: u64,
447    pub lightning_fee: PaymentFee,
448    pub transaction_fee: PaymentFee,
449    #[allow(deprecated)] // only here for decoding backward-compat
450    pub _connector: ConnectorType,
451}
452
453#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
454struct FederationConfigKey {
455    id: FederationId,
456}
457
458impl_db_record!(
459    key = FederationConfigKeyV0,
460    value = FederationConfigV0,
461    db_prefix = DbKeyPrefix::FederationConfig,
462);
463
464impl_db_record!(
465    key = FederationConfigKeyV1,
466    value = FederationConfigV1,
467    db_prefix = DbKeyPrefix::FederationConfig,
468);
469
470impl_db_record!(
471    key = FederationConfigKeyV2,
472    value = FederationConfigV2,
473    db_prefix = DbKeyPrefix::FederationConfig,
474);
475
476impl_db_record!(
477    key = FederationConfigKey,
478    value = FederationConfig,
479    db_prefix = DbKeyPrefix::FederationConfig,
480);
481
482impl_db_lookup!(
483    key = FederationConfigKeyV0,
484    query_prefix = FederationConfigKeyPrefixV0
485);
486impl_db_lookup!(
487    key = FederationConfigKeyV1,
488    query_prefix = FederationConfigKeyPrefixV1
489);
490impl_db_lookup!(
491    key = FederationConfigKeyV2,
492    query_prefix = FederationConfigKeyPrefixV2
493);
494impl_db_lookup!(
495    key = FederationConfigKey,
496    query_prefix = FederationConfigKeyPrefix
497);
498
499#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
500struct GatewayPublicKeyV0;
501
502#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
503struct GatewayPublicKey {
504    protocol: RegisteredProtocol,
505}
506
507#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
508struct GatewayPublicKeyPrefix;
509
510impl_db_record!(
511    key = GatewayPublicKeyV0,
512    value = Keypair,
513    db_prefix = DbKeyPrefix::GatewayPublicKey,
514);
515
516impl_db_record!(
517    key = GatewayPublicKey,
518    value = Keypair,
519    db_prefix = DbKeyPrefix::GatewayPublicKey,
520);
521
522impl_db_lookup!(
523    key = GatewayPublicKey,
524    query_prefix = GatewayPublicKeyPrefix
525);
526
527#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
528struct GatewayConfigurationKeyV0;
529
530#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
531struct GatewayConfigurationV0 {
532    password: String,
533    num_route_hints: u32,
534    #[serde(with = "serde_routing_fees")]
535    routing_fees: RoutingFees,
536    network: NetworkLegacyEncodingWrapper,
537}
538
539#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
540pub struct GatewayConfigurationKeyV1;
541
542#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
543pub struct GatewayConfigurationV1 {
544    pub hashed_password: sha256::Hash,
545    pub num_route_hints: u32,
546    #[serde(with = "serde_routing_fees")]
547    pub routing_fees: RoutingFees,
548    pub network: NetworkLegacyEncodingWrapper,
549    pub password_salt: [u8; 16],
550}
551
552#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
553pub struct GatewayConfigurationKeyV2;
554
555#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
556pub struct GatewayConfigurationV2 {
557    pub num_route_hints: u32,
558    #[serde(with = "serde_routing_fees")]
559    pub routing_fees: RoutingFees,
560    pub network: NetworkLegacyEncodingWrapper,
561}
562
563impl_db_record!(
564    key = GatewayConfigurationKeyV0,
565    value = GatewayConfigurationV0,
566    db_prefix = DbKeyPrefix::GatewayConfiguration,
567);
568
569impl_db_record!(
570    key = GatewayConfigurationKeyV1,
571    value = GatewayConfigurationV1,
572    db_prefix = DbKeyPrefix::GatewayConfiguration,
573);
574
575impl_db_record!(
576    key = GatewayConfigurationKeyV2,
577    value = GatewayConfigurationV2,
578    db_prefix = DbKeyPrefix::GatewayConfiguration,
579);
580
581#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
582struct PreimageAuthentication {
583    payment_hash: sha256::Hash,
584}
585
586impl_db_record!(
587    key = PreimageAuthentication,
588    value = sha256::Hash,
589    db_prefix = DbKeyPrefix::PreimageAuthentication
590);
591
592#[allow(dead_code)] // used in tests
593#[derive(Debug, Encodable, Decodable)]
594struct PreimageAuthenticationPrefix;
595
596impl_db_lookup!(
597    key = PreimageAuthentication,
598    query_prefix = PreimageAuthenticationPrefix
599);
600
601#[derive(Debug, Encodable, Decodable)]
602struct IrohKey;
603
604impl_db_record!(
605    key = IrohKey,
606    value = iroh::SecretKey,
607    db_prefix = DbKeyPrefix::Iroh
608);
609
610#[derive(Debug, Encodable, Decodable)]
611pub struct FederationBackupKey {
612    federation_id: FederationId,
613}
614
615#[derive(Debug, Encodable, Decodable)]
616pub struct FederationBackupPrefix;
617
618#[derive(Debug)]
619/// Gateway backup timestamp stored in the legacy timestamp representation.
620pub struct FederationBackupTime(Option<SystemTime>);
621
622impl Encodable for FederationBackupTime {
623    fn consensus_encode<W: std::io::Write>(&self, writer: &mut W) -> Result<(), std::io::Error> {
624        encode_legacy_option_system_time(&self.0, writer)
625    }
626}
627
628impl Decodable for FederationBackupTime {
629    fn consensus_decode_partial_from_finite_reader<D: std::io::Read>(
630        decoder: &mut D,
631        modules: &ModuleDecoderRegistry,
632    ) -> Result<Self, DecodeError> {
633        Ok(Self(with_decoding_context(
634            decode_legacy_option_system_time_from_finite_reader(decoder, modules),
635            "Decoding tuple block FederationBackupTime field field_0",
636        )?))
637    }
638}
639
640impl_db_record!(
641    key = FederationBackupKey,
642    value = FederationBackupTime,
643    db_prefix = DbKeyPrefix::FederationBackup,
644);
645
646impl_db_lookup!(
647    key = FederationBackupKey,
648    query_prefix = FederationBackupPrefix,
649);
650
651pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, GeneralDbMigrationFn> {
652    let mut migrations: BTreeMap<DatabaseVersion, GeneralDbMigrationFn> = BTreeMap::new();
653    migrations.insert(
654        DatabaseVersion(0),
655        Box::new(|ctx| migrate_to_v1(ctx).boxed()),
656    );
657    migrations.insert(
658        DatabaseVersion(1),
659        Box::new(|ctx| migrate_to_v2(ctx).boxed()),
660    );
661    migrations.insert(
662        DatabaseVersion(2),
663        Box::new(|ctx| migrate_to_v3(ctx).boxed()),
664    );
665    migrations.insert(
666        DatabaseVersion(3),
667        Box::new(|ctx| migrate_to_v4(ctx).boxed()),
668    );
669    migrations.insert(
670        DatabaseVersion(4),
671        Box::new(|ctx| migrate_to_v5(ctx).boxed()),
672    );
673    migrations.insert(
674        DatabaseVersion(5),
675        Box::new(|ctx| migrate_to_v6(ctx).boxed()),
676    );
677    migrations.insert(
678        DatabaseVersion(6),
679        Box::new(|ctx| migrate_to_v7(ctx).boxed()),
680    );
681    migrations.insert(
682        DatabaseVersion(7),
683        Box::new(|ctx| migrate_to_v8(ctx).boxed()),
684    );
685    migrations.insert(
686        DatabaseVersion(8),
687        Box::new(|ctx| migrate_to_v9(ctx).boxed()),
688    );
689    migrations
690}
691
692async fn migrate_to_v1(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
693    /// Creates a password hash by appending a 4 byte salt to the plaintext
694    /// password.
695    fn hash_password(plaintext_password: &str, salt: [u8; 16]) -> sha256::Hash {
696        let mut bytes = Vec::new();
697        bytes.append(&mut plaintext_password.consensus_encode_to_vec());
698        bytes.append(&mut salt.consensus_encode_to_vec());
699        sha256::Hash::hash(&bytes)
700    }
701
702    let mut dbtx = ctx.dbtx();
703
704    // If there is no old gateway configuration, there is nothing to do.
705    if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
706        let password_salt: [u8; 16] = rand::thread_rng().r#gen();
707        let hashed_password = hash_password(&old_gateway_config.password, password_salt);
708        let new_gateway_config = GatewayConfigurationV1 {
709            hashed_password,
710            num_route_hints: old_gateway_config.num_route_hints,
711            routing_fees: old_gateway_config.routing_fees,
712            network: old_gateway_config.network,
713            password_salt,
714        };
715        dbtx.insert_entry(&GatewayConfigurationKeyV1, &new_gateway_config)
716            .await;
717    }
718
719    Ok(())
720}
721
722async fn migrate_to_v2(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
723    let mut dbtx = ctx.dbtx();
724
725    // If there is no old federation configuration, there is nothing to do.
726    for (old_federation_id, _old_federation_config) in dbtx.load_federation_configs_v0().await {
727        if let Some(old_federation_config) = dbtx
728            .remove_entry(&FederationConfigKeyV0 {
729                id: old_federation_id,
730            })
731            .await
732        {
733            let new_federation_config = FederationConfigV1 {
734                invite_code: old_federation_config.invite_code,
735                federation_index: old_federation_config.federation_index,
736                timelock_delta: old_federation_config.timelock_delta,
737                fees: old_federation_config.fees,
738            };
739            let new_federation_key = FederationConfigKeyV1 {
740                id: old_federation_id,
741            };
742            dbtx.insert_entry(&new_federation_key, &new_federation_config)
743                .await;
744        }
745    }
746    Ok(())
747}
748
749async fn migrate_to_v3(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
750    let mut dbtx = ctx.dbtx();
751
752    // If there is no old gateway configuration, there is nothing to do.
753    if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV1).await {
754        let new_gateway_config = GatewayConfigurationV2 {
755            num_route_hints: old_gateway_config.num_route_hints,
756            routing_fees: old_gateway_config.routing_fees,
757            network: old_gateway_config.network,
758        };
759        dbtx.insert_entry(&GatewayConfigurationKeyV2, &new_gateway_config)
760            .await;
761    }
762
763    Ok(())
764}
765
766async fn migrate_to_v4(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
767    let mut dbtx = ctx.dbtx();
768
769    dbtx.remove_entry(&GatewayConfigurationKeyV2).await;
770
771    let configs = dbtx
772        .find_by_prefix(&FederationConfigKeyPrefixV1)
773        .await
774        .collect::<Vec<_>>()
775        .await;
776    for (fed_id, _old_config) in configs {
777        if let Some(old_federation_config) = dbtx.remove_entry(&fed_id).await {
778            let new_fed_config = FederationConfigV2 {
779                invite_code: old_federation_config.invite_code,
780                federation_index: old_federation_config.federation_index,
781                lightning_fee: old_federation_config.fees.into(),
782                transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
783                // Note: deprecated, unused
784                _connector: ConnectorType::Tcp,
785            };
786            let new_key = FederationConfigKeyV2 { id: fed_id.id };
787            dbtx.insert_new_entry(&new_key, &new_fed_config).await;
788        }
789    }
790    Ok(())
791}
792
793/// Introduced in v0.5, there is a db key clash between the `FederationConfig`
794/// record and the isolated databases used for each client. We must migrate the
795/// isolated databases to be behind the `ClientDatabase` prefix to allow the
796/// gateway to properly read the federation configs.
797async fn migrate_to_v5(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
798    let mut dbtx = ctx.dbtx();
799    migrate_federation_configs(&mut dbtx).await
800}
801
802async fn migrate_federation_configs(
803    dbtx: &mut DatabaseTransaction<'_>,
804) -> Result<(), DbMigrationError> {
805    // We need to migrate all isolated database entries to be behind the 0x10
806    // prefix. The problem is, if there is a `FederationId` that starts with
807    // 0x04, we cannot read the `FederationId` because the database will be confused
808    // between the isolated DB and the `FederationConfigKey` record. To solve this,
809    // we try and decode each key as a Federation ID and each value as a
810    // FederationConfig. If that is successful and the federation ID in the
811    // config matches the key, then we skip that record and migrate the rest of
812    // the entries.
813    //
814    // The records have the shape they had when this migration was written
815    // (`FederationConfigV2`); decoding them as a later shape would misread every
816    // federation config as an isolated database entry and move it away.
817    let problem_entries = dbtx
818        .raw_find_by_prefix(&[0x04])
819        .await?
820        .collect::<BTreeMap<_, _>>()
821        .await;
822    for (mut problem_key, value) in problem_entries {
823        // Try and decode the key as a FederationId and the value as a FederationConfig
824        // The key should be 33 bytes because a FederationID is 32 bytes and there is a
825        // 1 byte prefix.
826        if problem_key.len() == 33
827            && let Ok(federation_id) = FederationId::consensus_decode_whole(
828                &problem_key[1..33],
829                &ModuleDecoderRegistry::default(),
830            )
831            && let Ok(federation_config) = FederationConfigV2::consensus_decode_whole(
832                &value,
833                &ModuleDecoderRegistry::default(),
834            )
835            && federation_id == federation_config.invite_code.federation_id()
836        {
837            continue;
838        }
839
840        dbtx.raw_remove_entry(&problem_key).await?;
841        let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
842        new_key.append(&mut problem_key);
843        dbtx.raw_insert_bytes(&new_key, &value).await?;
844    }
845
846    // Migrate all entries of the isolated databases that don't overlap with
847    // `FederationConfig` entries.
848    let fed_ids = dbtx
849        .find_by_prefix(&FederationConfigKeyPrefixV2)
850        .await
851        .collect::<BTreeMap<_, _>>()
852        .await;
853    for fed_id in fed_ids.keys() {
854        let federation_id_bytes = fed_id.id.consensus_encode_to_vec();
855        let isolated_entries = dbtx
856            .raw_find_by_prefix(&federation_id_bytes)
857            .await?
858            .collect::<BTreeMap<_, _>>()
859            .await;
860        for (mut key, value) in isolated_entries {
861            dbtx.raw_remove_entry(&key).await?;
862            let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
863            new_key.append(&mut key);
864            dbtx.raw_insert_bytes(&new_key, &value).await?;
865        }
866    }
867
868    Ok(())
869}
870
871async fn migrate_to_v6(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
872    let mut dbtx = ctx.dbtx();
873
874    // The federation configs still have their v2 shape at this point.
875    let configs = dbtx
876        .find_by_prefix(&FederationConfigKeyPrefixV2)
877        .await
878        .collect::<Vec<_>>()
879        .await;
880    for (fed_id, _) in configs {
881        dbtx.insert_new_entry(
882            &FederationBackupKey {
883                federation_id: fed_id.id,
884            },
885            &FederationBackupTime(None),
886        )
887        .await;
888    }
889    Ok(())
890}
891
892async fn migrate_to_v7(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
893    let mut dbtx = ctx.dbtx();
894
895    let gateway_keypair = dbtx.remove_entry(&GatewayPublicKeyV0).await;
896    if let Some(gateway_keypair) = gateway_keypair {
897        dbtx.insert_new_entry(
898            &GatewayPublicKey {
899                protocol: RegisteredProtocol::Http,
900            },
901            &gateway_keypair,
902        )
903        .await;
904    }
905
906    Ok(())
907}
908
909/// Adds `payment_policies` to the federation configs. Every gateway performed
910/// every kind of payment before the setting existed, so the migrated records
911/// carry no restriction.
912async fn migrate_to_v9(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
913    let mut dbtx = ctx.dbtx();
914    migrate_federation_configs_payment_policies(&mut dbtx).await
915}
916
917async fn migrate_federation_configs_payment_policies(
918    dbtx: &mut DatabaseTransaction<'_>,
919) -> Result<(), DbMigrationError> {
920    let configs = dbtx
921        .find_by_prefix(&FederationConfigKeyPrefixV2)
922        .await
923        .collect::<Vec<_>>()
924        .await;
925
926    for (key, config) in configs {
927        dbtx.remove_entry(&key).await;
928        dbtx.insert_new_entry(
929            &FederationConfigKey { id: key.id },
930            &FederationConfig {
931                invite_code: config.invite_code,
932                federation_index: config.federation_index,
933                lightning_fee: config.lightning_fee,
934                transaction_fee: config.transaction_fee,
935                payment_policies: BTreeSet::new(),
936                _connector: config._connector,
937            },
938        )
939        .await;
940    }
941
942    Ok(())
943}
944
945/// Adds the invoice expiry to registered incoming contract records so stale
946/// records can be pruned.
947async fn migrate_to_v8(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), DbMigrationError> {
948    let mut dbtx = ctx.dbtx();
949    migrate_registered_incoming_contracts(&mut dbtx).await
950}
951
952async fn migrate_registered_incoming_contracts(
953    dbtx: &mut DatabaseTransaction<'_>,
954) -> Result<(), DbMigrationError> {
955    // Existing records predate the invoice expiry, so backfill it as if their
956    // invoice had just been created with the maximum allowed expiry; they are
957    // retained conservatively and eventually pruned.
958    let backfilled_expiry = duration_since_epoch()
959        .as_secs()
960        .saturating_add(u64::from(MAX_INVOICE_EXPIRY_SECS));
961
962    let contracts = dbtx
963        .find_by_prefix(&RegisteredIncomingContractKeyPrefixV0)
964        .await
965        .collect::<Vec<_>>()
966        .await;
967
968    for (key, record) in contracts {
969        dbtx.remove_entry(&key).await;
970        dbtx.insert_new_entry(
971            &RegisteredIncomingContractKey(key.0),
972            &RegisteredIncomingContract {
973                federation_id: record.federation_id,
974                incoming_amount_msats: record.incoming_amount_msats,
975                invoice_expires_at_secs: backfilled_expiry,
976                contract: record.contract,
977            },
978        )
979        .await;
980    }
981
982    Ok(())
983}
984
985#[derive(Debug, Encodable, Decodable)]
986struct RegisteredIncomingContractKey(pub PaymentImage);
987
988#[derive(Debug, Encodable, Decodable)]
989struct RegisteredIncomingContractKeyPrefix;
990
991#[derive(Debug, Encodable, Decodable)]
992pub struct RegisteredIncomingContract {
993    pub federation_id: FederationId,
994    /// The amount of the incoming contract, in msats.
995    pub incoming_amount_msats: u64,
996    /// Unix timestamp in seconds after which the bolt11 invoice created for
997    /// this contract can no longer be paid; records are pruned once it has
998    /// long passed.
999    pub invoice_expires_at_secs: u64,
1000    pub contract: IncomingContract,
1001}
1002
1003impl_db_record!(
1004    key = RegisteredIncomingContractKey,
1005    value = RegisteredIncomingContract,
1006    db_prefix = DbKeyPrefix::RegisteredIncomingContract,
1007);
1008
1009impl_db_lookup!(
1010    key = RegisteredIncomingContractKey,
1011    query_prefix = RegisteredIncomingContractKeyPrefix
1012);
1013
1014#[derive(Debug, Encodable, Decodable)]
1015struct RegisteredIncomingContractKeyV0(pub PaymentImage);
1016
1017#[derive(Debug, Encodable, Decodable)]
1018struct RegisteredIncomingContractKeyPrefixV0;
1019
1020#[derive(Debug, Encodable, Decodable)]
1021pub struct RegisteredIncomingContractV0 {
1022    pub federation_id: FederationId,
1023    pub incoming_amount_msats: u64,
1024    pub contract: IncomingContract,
1025}
1026
1027impl_db_record!(
1028    key = RegisteredIncomingContractKeyV0,
1029    value = RegisteredIncomingContractV0,
1030    db_prefix = DbKeyPrefix::RegisteredIncomingContract,
1031);
1032
1033impl_db_lookup!(
1034    key = RegisteredIncomingContractKeyV0,
1035    query_prefix = RegisteredIncomingContractKeyPrefixV0
1036);
1037
1038/// Records which send operation claimed an outgoing contract for a given
1039/// payment image. A single Lightning payment yields a single preimage, so the
1040/// gateway may claim at most one outgoing contract per payment image across all
1041/// of its federations; the value is the claiming operation so a retry of that
1042/// same operation is not mistaken for a duplicate.
1043#[derive(Debug, Encodable, Decodable)]
1044struct ClaimedOutgoingPaymentImageKey(pub PaymentImage);
1045
1046impl_db_record!(
1047    key = ClaimedOutgoingPaymentImageKey,
1048    value = OperationId,
1049    db_prefix = DbKeyPrefix::ClaimedOutgoingPaymentImage,
1050);
1051
1052#[cfg(test)]
1053mod migration_tests;