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 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 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 async fn delete_registered_incoming_contract(
92 &mut self,
93 payment_image: PaymentImage,
94 ) -> Option<RegisteredIncomingContract>;
95
96 async fn prune_registered_incoming_contracts(&mut self, expired_before_secs: u64) -> usize;
99
100 async fn claim_outgoing_payment_image(
106 &mut self,
107 payment_image: PaymentImage,
108 operation_id: OperationId,
109 ) -> OperationId;
110
111 async fn dump_database(
114 &mut self,
115 prefix_names: Vec<String>,
116 ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>>;
117
118 async fn load_or_create_iroh_key(&mut self) -> iroh::SecretKey;
121
122 async fn load_backup_records(&mut self) -> BTreeMap<FederationId, Option<SystemTime>>;
124
125 async fn load_backup_record(
127 &mut self,
128 federation_id: FederationId,
129 ) -> Option<Option<SystemTime>>;
130
131 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 #[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)] #[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)]
589pub 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 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 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 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 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 _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
759async 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 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 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 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
868async 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 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 pub incoming_amount_msats: u64,
919 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#[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;