1use std::collections::BTreeMap;
2
3use bitcoin::hashes::{Hash, sha256};
4use fedimint_api_client::api::net::Connector;
5use fedimint_core::config::FederationId;
6use fedimint_core::db::{
7 Database, DatabaseTransaction, DatabaseVersion, GeneralDbMigrationFn,
8 GeneralDbMigrationFnContext, IDatabaseTransactionOpsCore, IDatabaseTransactionOpsCoreTyped,
9};
10use fedimint_core::encoding::btc::NetworkLegacyEncodingWrapper;
11use fedimint_core::encoding::{Decodable, Encodable};
12use fedimint_core::invite_code::InviteCode;
13use fedimint_core::module::registry::ModuleDecoderRegistry;
14use fedimint_core::{Amount, impl_db_lookup, impl_db_record, push_db_pair_items, secp256k1};
15use fedimint_gateway_common::FederationConfig;
16use fedimint_ln_common::serde_routing_fees;
17use fedimint_lnv2_common::contracts::{IncomingContract, PaymentImage};
18use fedimint_lnv2_common::gateway_api::PaymentFee;
19use futures::{FutureExt, StreamExt};
20use lightning_invoice::RoutingFees;
21use rand::Rng;
22use rand::rngs::OsRng;
23use secp256k1::{Keypair, Secp256k1};
24use serde::{Deserialize, Serialize};
25use strum::IntoEnumIterator;
26use strum_macros::EnumIter;
27
28pub trait GatewayDbExt {
29 fn get_client_database(&self, federation_id: &FederationId) -> Database;
30}
31
32impl GatewayDbExt for Database {
33 fn get_client_database(&self, federation_id: &FederationId) -> Database {
34 let mut prefix = vec![DbKeyPrefix::ClientDatabase as u8];
35 prefix.append(&mut federation_id.consensus_encode_to_vec());
36 self.with_prefix(prefix)
37 }
38}
39
40#[allow(async_fn_in_trait)]
41pub trait GatewayDbtxNcExt {
42 async fn save_federation_config(&mut self, config: &FederationConfig);
43 async fn load_federation_configs_v0(&mut self) -> BTreeMap<FederationId, FederationConfigV0>;
44 async fn load_federation_configs(&mut self) -> BTreeMap<FederationId, FederationConfig>;
45 async fn load_federation_config(
46 &mut self,
47 federation_id: FederationId,
48 ) -> Option<FederationConfig>;
49 async fn remove_federation_config(&mut self, federation_id: FederationId);
50
51 async fn load_gateway_keypair(&mut self) -> Option<Keypair>;
53
54 async fn load_gateway_keypair_assert_exists(&mut self) -> Keypair;
59
60 async fn load_or_create_gateway_keypair(&mut self) -> Keypair;
64
65 async fn save_new_preimage_authentication(
66 &mut self,
67 payment_hash: sha256::Hash,
68 preimage_auth: sha256::Hash,
69 );
70
71 async fn load_preimage_authentication(
72 &mut self,
73 payment_hash: sha256::Hash,
74 ) -> Option<sha256::Hash>;
75
76 async fn save_registered_incoming_contract(
79 &mut self,
80 federation_id: FederationId,
81 incoming_amount: Amount,
82 contract: IncomingContract,
83 ) -> Option<RegisteredIncomingContract>;
84
85 async fn load_registered_incoming_contract(
86 &mut self,
87 payment_image: PaymentImage,
88 ) -> Option<RegisteredIncomingContract>;
89
90 async fn dump_database(
93 &mut self,
94 prefix_names: Vec<String>,
95 ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>>;
96}
97
98impl<Cap: Send> GatewayDbtxNcExt for DatabaseTransaction<'_, Cap> {
99 async fn save_federation_config(&mut self, config: &FederationConfig) {
100 let id = config.invite_code.federation_id();
101 self.insert_entry(&FederationConfigKey { id }, config).await;
102 }
103
104 async fn load_federation_configs_v0(&mut self) -> BTreeMap<FederationId, FederationConfigV0> {
105 self.find_by_prefix(&FederationConfigKeyPrefixV0)
106 .await
107 .map(|(key, config): (FederationConfigKeyV0, FederationConfigV0)| (key.id, config))
108 .collect::<BTreeMap<FederationId, FederationConfigV0>>()
109 .await
110 }
111
112 async fn load_federation_configs(&mut self) -> BTreeMap<FederationId, FederationConfig> {
113 self.find_by_prefix(&FederationConfigKeyPrefix)
114 .await
115 .map(|(key, config): (FederationConfigKey, FederationConfig)| (key.id, config))
116 .collect::<BTreeMap<FederationId, FederationConfig>>()
117 .await
118 }
119
120 async fn load_federation_config(
121 &mut self,
122 federation_id: FederationId,
123 ) -> Option<FederationConfig> {
124 self.get_value(&FederationConfigKey { id: federation_id })
125 .await
126 }
127
128 async fn remove_federation_config(&mut self, federation_id: FederationId) {
129 self.remove_entry(&FederationConfigKey { id: federation_id })
130 .await;
131 }
132
133 async fn load_gateway_keypair(&mut self) -> Option<Keypair> {
134 self.get_value(&GatewayPublicKey).await
135 }
136
137 async fn load_gateway_keypair_assert_exists(&mut self) -> Keypair {
138 self.get_value(&GatewayPublicKey)
139 .await
140 .expect("Gateway keypair does not exist")
141 }
142
143 async fn load_or_create_gateway_keypair(&mut self) -> Keypair {
144 if let Some(key_pair) = self.get_value(&GatewayPublicKey).await {
145 key_pair
146 } else {
147 let context = Secp256k1::new();
148 let (secret_key, _public_key) = context.generate_keypair(&mut OsRng);
149 let key_pair = Keypair::from_secret_key(&context, &secret_key);
150 self.insert_new_entry(&GatewayPublicKey, &key_pair).await;
151 key_pair
152 }
153 }
154
155 async fn save_new_preimage_authentication(
156 &mut self,
157 payment_hash: sha256::Hash,
158 preimage_auth: sha256::Hash,
159 ) {
160 self.insert_new_entry(&PreimageAuthentication { payment_hash }, &preimage_auth)
161 .await;
162 }
163
164 async fn load_preimage_authentication(
165 &mut self,
166 payment_hash: sha256::Hash,
167 ) -> Option<sha256::Hash> {
168 self.get_value(&PreimageAuthentication { payment_hash })
169 .await
170 }
171
172 async fn save_registered_incoming_contract(
173 &mut self,
174 federation_id: FederationId,
175 incoming_amount: Amount,
176 contract: IncomingContract,
177 ) -> Option<RegisteredIncomingContract> {
178 self.insert_entry(
179 &RegisteredIncomingContractKey(contract.commitment.payment_image.clone()),
180 &RegisteredIncomingContract {
181 federation_id,
182 incoming_amount_msats: incoming_amount.msats,
183 contract,
184 },
185 )
186 .await
187 }
188
189 async fn load_registered_incoming_contract(
190 &mut self,
191 payment_image: PaymentImage,
192 ) -> Option<RegisteredIncomingContract> {
193 self.get_value(&RegisteredIncomingContractKey(payment_image))
194 .await
195 }
196
197 async fn dump_database(
198 &mut self,
199 prefix_names: Vec<String>,
200 ) -> BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> {
201 let mut gateway_items: BTreeMap<String, Box<dyn erased_serde::Serialize + Send>> =
202 BTreeMap::new();
203 let filtered_prefixes = DbKeyPrefix::iter().filter(|f| {
204 prefix_names.is_empty() || prefix_names.contains(&f.to_string().to_lowercase())
205 });
206
207 for table in filtered_prefixes {
208 match table {
209 DbKeyPrefix::FederationConfig => {
210 push_db_pair_items!(
211 self,
212 FederationConfigKeyPrefix,
213 FederationConfigKey,
214 FederationConfig,
215 gateway_items,
216 "Federation Config"
217 );
218 }
219 DbKeyPrefix::GatewayPublicKey => {
220 if let Some(public_key) = self.load_gateway_keypair().await {
221 gateway_items
222 .insert("Gateway Public Key".to_string(), Box::new(public_key));
223 }
224 }
225 _ => {}
226 }
227 }
228
229 gateway_items
230 }
231}
232
233#[repr(u8)]
234#[derive(Clone, EnumIter, Debug)]
235enum DbKeyPrefix {
236 FederationConfig = 0x04,
237 GatewayPublicKey = 0x06,
238 GatewayConfiguration = 0x07,
239 PreimageAuthentication = 0x08,
240 RegisteredIncomingContract = 0x09,
241 ClientDatabase = 0x10,
242}
243
244impl std::fmt::Display for DbKeyPrefix {
245 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
246 write!(f, "{self:?}")
247 }
248}
249
250#[derive(Debug, Encodable, Decodable)]
251struct FederationConfigKeyPrefixV0;
252
253#[derive(Debug, Encodable, Decodable)]
254struct FederationConfigKeyPrefixV1;
255
256#[derive(Debug, Encodable, Decodable)]
257struct FederationConfigKeyPrefix;
258
259#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
260struct FederationConfigKeyV0 {
261 id: FederationId,
262}
263
264#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
265pub struct FederationConfigV0 {
266 pub invite_code: InviteCode,
267 pub federation_index: u64,
268 pub timelock_delta: u64,
269 #[serde(with = "serde_routing_fees")]
270 pub fees: RoutingFees,
271}
272
273#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
274struct FederationConfigKeyV1 {
275 id: FederationId,
276}
277
278#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
279pub struct FederationConfigV1 {
280 pub invite_code: InviteCode,
281 #[serde(alias = "mint_channel_id")]
284 pub federation_index: u64,
285 pub timelock_delta: u64,
286 #[serde(with = "serde_routing_fees")]
287 pub fees: RoutingFees,
288 pub connector: Connector,
289}
290
291#[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
292struct FederationConfigKey {
293 id: FederationId,
294}
295
296impl_db_record!(
297 key = FederationConfigKeyV0,
298 value = FederationConfigV0,
299 db_prefix = DbKeyPrefix::FederationConfig,
300);
301
302impl_db_record!(
303 key = FederationConfigKeyV1,
304 value = FederationConfigV1,
305 db_prefix = DbKeyPrefix::FederationConfig,
306);
307
308impl_db_record!(
309 key = FederationConfigKey,
310 value = FederationConfig,
311 db_prefix = DbKeyPrefix::FederationConfig,
312);
313
314impl_db_lookup!(
315 key = FederationConfigKeyV0,
316 query_prefix = FederationConfigKeyPrefixV0
317);
318impl_db_lookup!(
319 key = FederationConfigKeyV1,
320 query_prefix = FederationConfigKeyPrefixV1
321);
322impl_db_lookup!(
323 key = FederationConfigKey,
324 query_prefix = FederationConfigKeyPrefix
325);
326
327#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
328struct GatewayPublicKey;
329
330impl_db_record!(
331 key = GatewayPublicKey,
332 value = Keypair,
333 db_prefix = DbKeyPrefix::GatewayPublicKey,
334);
335
336#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
337struct GatewayConfigurationKeyV0;
338
339#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
340struct GatewayConfigurationV0 {
341 password: String,
342 num_route_hints: u32,
343 #[serde(with = "serde_routing_fees")]
344 routing_fees: RoutingFees,
345 network: NetworkLegacyEncodingWrapper,
346}
347
348#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
349pub struct GatewayConfigurationKeyV1;
350
351#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
352pub struct GatewayConfigurationV1 {
353 pub hashed_password: sha256::Hash,
354 pub num_route_hints: u32,
355 #[serde(with = "serde_routing_fees")]
356 pub routing_fees: RoutingFees,
357 pub network: NetworkLegacyEncodingWrapper,
358 pub password_salt: [u8; 16],
359}
360
361#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
362pub struct GatewayConfigurationKeyV2;
363
364#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
365pub struct GatewayConfigurationV2 {
366 pub num_route_hints: u32,
367 #[serde(with = "serde_routing_fees")]
368 pub routing_fees: RoutingFees,
369 pub network: NetworkLegacyEncodingWrapper,
370}
371
372impl_db_record!(
373 key = GatewayConfigurationKeyV0,
374 value = GatewayConfigurationV0,
375 db_prefix = DbKeyPrefix::GatewayConfiguration,
376);
377
378impl_db_record!(
379 key = GatewayConfigurationKeyV1,
380 value = GatewayConfigurationV1,
381 db_prefix = DbKeyPrefix::GatewayConfiguration,
382);
383
384impl_db_record!(
385 key = GatewayConfigurationKeyV2,
386 value = GatewayConfigurationV2,
387 db_prefix = DbKeyPrefix::GatewayConfiguration,
388);
389
390#[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
391struct PreimageAuthentication {
392 payment_hash: sha256::Hash,
393}
394
395impl_db_record!(
396 key = PreimageAuthentication,
397 value = sha256::Hash,
398 db_prefix = DbKeyPrefix::PreimageAuthentication
399);
400
401#[derive(Debug, Encodable, Decodable)]
402struct PreimageAuthenticationPrefix;
403
404impl_db_lookup!(
405 key = PreimageAuthentication,
406 query_prefix = PreimageAuthenticationPrefix
407);
408
409pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, GeneralDbMigrationFn> {
410 let mut migrations: BTreeMap<DatabaseVersion, GeneralDbMigrationFn> = BTreeMap::new();
411 migrations.insert(
412 DatabaseVersion(0),
413 Box::new(|ctx| migrate_to_v1(ctx).boxed()),
414 );
415 migrations.insert(
416 DatabaseVersion(1),
417 Box::new(|ctx| migrate_to_v2(ctx).boxed()),
418 );
419 migrations.insert(
420 DatabaseVersion(2),
421 Box::new(|ctx| migrate_to_v3(ctx).boxed()),
422 );
423 migrations.insert(
424 DatabaseVersion(3),
425 Box::new(|ctx| migrate_to_v4(ctx).boxed()),
426 );
427 migrations.insert(
428 DatabaseVersion(4),
429 Box::new(|ctx| migrate_to_v5(ctx).boxed()),
430 );
431 migrations
432}
433
434async fn migrate_to_v1(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
435 fn hash_password(plaintext_password: &str, salt: [u8; 16]) -> sha256::Hash {
438 let mut bytes = Vec::new();
439 bytes.append(&mut plaintext_password.consensus_encode_to_vec());
440 bytes.append(&mut salt.consensus_encode_to_vec());
441 sha256::Hash::hash(&bytes)
442 }
443
444 let mut dbtx = ctx.dbtx();
445
446 if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
448 let password_salt: [u8; 16] = rand::thread_rng().r#gen();
449 let hashed_password = hash_password(&old_gateway_config.password, password_salt);
450 let new_gateway_config = GatewayConfigurationV1 {
451 hashed_password,
452 num_route_hints: old_gateway_config.num_route_hints,
453 routing_fees: old_gateway_config.routing_fees,
454 network: old_gateway_config.network,
455 password_salt,
456 };
457 dbtx.insert_entry(&GatewayConfigurationKeyV1, &new_gateway_config)
458 .await;
459 }
460
461 Ok(())
462}
463
464async fn migrate_to_v2(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
465 let mut dbtx = ctx.dbtx();
466
467 for (old_federation_id, _old_federation_config) in dbtx.load_federation_configs_v0().await {
469 if let Some(old_federation_config) = dbtx
470 .remove_entry(&FederationConfigKeyV0 {
471 id: old_federation_id,
472 })
473 .await
474 {
475 let new_federation_config = FederationConfigV1 {
476 invite_code: old_federation_config.invite_code,
477 federation_index: old_federation_config.federation_index,
478 timelock_delta: old_federation_config.timelock_delta,
479 fees: old_federation_config.fees,
480 connector: Connector::default(),
481 };
482 let new_federation_key = FederationConfigKeyV1 {
483 id: old_federation_id,
484 };
485 dbtx.insert_entry(&new_federation_key, &new_federation_config)
486 .await;
487 }
488 }
489 Ok(())
490}
491
492async fn migrate_to_v3(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
493 let mut dbtx = ctx.dbtx();
494
495 if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV1).await {
497 let new_gateway_config = GatewayConfigurationV2 {
498 num_route_hints: old_gateway_config.num_route_hints,
499 routing_fees: old_gateway_config.routing_fees,
500 network: old_gateway_config.network,
501 };
502 dbtx.insert_entry(&GatewayConfigurationKeyV2, &new_gateway_config)
503 .await;
504 }
505
506 Ok(())
507}
508
509async fn migrate_to_v4(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
510 let mut dbtx = ctx.dbtx();
511
512 dbtx.remove_entry(&GatewayConfigurationKeyV2).await;
513
514 let configs = dbtx
515 .find_by_prefix(&FederationConfigKeyPrefixV1)
516 .await
517 .collect::<Vec<_>>()
518 .await;
519 for (fed_id, _old_config) in configs {
520 if let Some(old_federation_config) = dbtx.remove_entry(&fed_id).await {
521 let new_fed_config = FederationConfig {
522 invite_code: old_federation_config.invite_code,
523 federation_index: old_federation_config.federation_index,
524 lightning_fee: old_federation_config.fees.into(),
525 transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
526 connector: Connector::default(),
527 };
528 let new_key = FederationConfigKey { id: fed_id.id };
529 dbtx.insert_new_entry(&new_key, &new_fed_config).await;
530 }
531 }
532 Ok(())
533}
534
535async fn migrate_to_v5(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
540 let mut dbtx = ctx.dbtx();
541 migrate_federation_configs(&mut dbtx).await
542}
543
544async fn migrate_federation_configs(
545 dbtx: &mut DatabaseTransaction<'_>,
546) -> Result<(), anyhow::Error> {
547 let problem_entries = dbtx
556 .raw_find_by_prefix(&[0x04])
557 .await?
558 .collect::<BTreeMap<_, _>>()
559 .await;
560 for (mut problem_key, value) in problem_entries {
561 if problem_key.len() == 33 {
565 if let Ok(federation_id) = FederationId::consensus_decode_whole(
566 &problem_key[1..33],
567 &ModuleDecoderRegistry::default(),
568 ) {
569 if let Ok(federation_config) = FederationConfig::consensus_decode_whole(
570 &value,
571 &ModuleDecoderRegistry::default(),
572 ) {
573 if federation_id == federation_config.invite_code.federation_id() {
574 continue;
575 }
576 }
577 }
578 }
579
580 dbtx.raw_remove_entry(&problem_key).await?;
581 let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
582 new_key.append(&mut problem_key);
583 dbtx.raw_insert_bytes(&new_key, &value).await?;
584 }
585
586 let fed_ids = dbtx
589 .find_by_prefix(&FederationConfigKeyPrefix)
590 .await
591 .collect::<BTreeMap<_, _>>()
592 .await;
593 for fed_id in fed_ids.keys() {
594 let federation_id_bytes = fed_id.id.consensus_encode_to_vec();
595 let isolated_entries = dbtx
596 .raw_find_by_prefix(&federation_id_bytes)
597 .await?
598 .collect::<BTreeMap<_, _>>()
599 .await;
600 for (mut key, value) in isolated_entries {
601 dbtx.raw_remove_entry(&key).await?;
602 let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
603 new_key.append(&mut key);
604 dbtx.raw_insert_bytes(&new_key, &value).await?;
605 }
606 }
607
608 Ok(())
609}
610
611#[derive(Debug, Encodable, Decodable)]
612struct RegisteredIncomingContractKey(pub PaymentImage);
613
614#[derive(Debug, Encodable, Decodable)]
615pub struct RegisteredIncomingContract {
616 pub federation_id: FederationId,
617 pub incoming_amount_msats: u64,
619 pub contract: IncomingContract,
620}
621
622impl_db_record!(
623 key = RegisteredIncomingContractKey,
624 value = RegisteredIncomingContract,
625 db_prefix = DbKeyPrefix::RegisteredIncomingContract,
626);
627
628#[cfg(test)]
629mod migration_tests;