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#[allow(dead_code)] #[derive(Debug, Encodable, Decodable)]
403struct PreimageAuthenticationPrefix;
404
405impl_db_lookup!(
406 key = PreimageAuthentication,
407 query_prefix = PreimageAuthenticationPrefix
408);
409
410pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, GeneralDbMigrationFn> {
411 let mut migrations: BTreeMap<DatabaseVersion, GeneralDbMigrationFn> = BTreeMap::new();
412 migrations.insert(
413 DatabaseVersion(0),
414 Box::new(|ctx| migrate_to_v1(ctx).boxed()),
415 );
416 migrations.insert(
417 DatabaseVersion(1),
418 Box::new(|ctx| migrate_to_v2(ctx).boxed()),
419 );
420 migrations.insert(
421 DatabaseVersion(2),
422 Box::new(|ctx| migrate_to_v3(ctx).boxed()),
423 );
424 migrations.insert(
425 DatabaseVersion(3),
426 Box::new(|ctx| migrate_to_v4(ctx).boxed()),
427 );
428 migrations.insert(
429 DatabaseVersion(4),
430 Box::new(|ctx| migrate_to_v5(ctx).boxed()),
431 );
432 migrations
433}
434
435async fn migrate_to_v1(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
436 fn hash_password(plaintext_password: &str, salt: [u8; 16]) -> sha256::Hash {
439 let mut bytes = Vec::new();
440 bytes.append(&mut plaintext_password.consensus_encode_to_vec());
441 bytes.append(&mut salt.consensus_encode_to_vec());
442 sha256::Hash::hash(&bytes)
443 }
444
445 let mut dbtx = ctx.dbtx();
446
447 if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
449 let password_salt: [u8; 16] = rand::thread_rng().r#gen();
450 let hashed_password = hash_password(&old_gateway_config.password, password_salt);
451 let new_gateway_config = GatewayConfigurationV1 {
452 hashed_password,
453 num_route_hints: old_gateway_config.num_route_hints,
454 routing_fees: old_gateway_config.routing_fees,
455 network: old_gateway_config.network,
456 password_salt,
457 };
458 dbtx.insert_entry(&GatewayConfigurationKeyV1, &new_gateway_config)
459 .await;
460 }
461
462 Ok(())
463}
464
465async fn migrate_to_v2(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
466 let mut dbtx = ctx.dbtx();
467
468 for (old_federation_id, _old_federation_config) in dbtx.load_federation_configs_v0().await {
470 if let Some(old_federation_config) = dbtx
471 .remove_entry(&FederationConfigKeyV0 {
472 id: old_federation_id,
473 })
474 .await
475 {
476 let new_federation_config = FederationConfigV1 {
477 invite_code: old_federation_config.invite_code,
478 federation_index: old_federation_config.federation_index,
479 timelock_delta: old_federation_config.timelock_delta,
480 fees: old_federation_config.fees,
481 connector: Connector::default(),
482 };
483 let new_federation_key = FederationConfigKeyV1 {
484 id: old_federation_id,
485 };
486 dbtx.insert_entry(&new_federation_key, &new_federation_config)
487 .await;
488 }
489 }
490 Ok(())
491}
492
493async fn migrate_to_v3(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
494 let mut dbtx = ctx.dbtx();
495
496 if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV1).await {
498 let new_gateway_config = GatewayConfigurationV2 {
499 num_route_hints: old_gateway_config.num_route_hints,
500 routing_fees: old_gateway_config.routing_fees,
501 network: old_gateway_config.network,
502 };
503 dbtx.insert_entry(&GatewayConfigurationKeyV2, &new_gateway_config)
504 .await;
505 }
506
507 Ok(())
508}
509
510async fn migrate_to_v4(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
511 let mut dbtx = ctx.dbtx();
512
513 dbtx.remove_entry(&GatewayConfigurationKeyV2).await;
514
515 let configs = dbtx
516 .find_by_prefix(&FederationConfigKeyPrefixV1)
517 .await
518 .collect::<Vec<_>>()
519 .await;
520 for (fed_id, _old_config) in configs {
521 if let Some(old_federation_config) = dbtx.remove_entry(&fed_id).await {
522 let new_fed_config = FederationConfig {
523 invite_code: old_federation_config.invite_code,
524 federation_index: old_federation_config.federation_index,
525 lightning_fee: old_federation_config.fees.into(),
526 transaction_fee: PaymentFee::TRANSACTION_FEE_DEFAULT,
527 connector: Connector::default(),
528 };
529 let new_key = FederationConfigKey { id: fed_id.id };
530 dbtx.insert_new_entry(&new_key, &new_fed_config).await;
531 }
532 }
533 Ok(())
534}
535
536async fn migrate_to_v5(mut ctx: GeneralDbMigrationFnContext<'_>) -> Result<(), anyhow::Error> {
541 let mut dbtx = ctx.dbtx();
542 migrate_federation_configs(&mut dbtx).await
543}
544
545async fn migrate_federation_configs(
546 dbtx: &mut DatabaseTransaction<'_>,
547) -> Result<(), anyhow::Error> {
548 let problem_entries = dbtx
557 .raw_find_by_prefix(&[0x04])
558 .await?
559 .collect::<BTreeMap<_, _>>()
560 .await;
561 for (mut problem_key, value) in problem_entries {
562 if problem_key.len() == 33
566 && let Ok(federation_id) = FederationId::consensus_decode_whole(
567 &problem_key[1..33],
568 &ModuleDecoderRegistry::default(),
569 )
570 && let Ok(federation_config) =
571 FederationConfig::consensus_decode_whole(&value, &ModuleDecoderRegistry::default())
572 && federation_id == federation_config.invite_code.federation_id()
573 {
574 continue;
575 }
576
577 dbtx.raw_remove_entry(&problem_key).await?;
578 let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
579 new_key.append(&mut problem_key);
580 dbtx.raw_insert_bytes(&new_key, &value).await?;
581 }
582
583 let fed_ids = dbtx
586 .find_by_prefix(&FederationConfigKeyPrefix)
587 .await
588 .collect::<BTreeMap<_, _>>()
589 .await;
590 for fed_id in fed_ids.keys() {
591 let federation_id_bytes = fed_id.id.consensus_encode_to_vec();
592 let isolated_entries = dbtx
593 .raw_find_by_prefix(&federation_id_bytes)
594 .await?
595 .collect::<BTreeMap<_, _>>()
596 .await;
597 for (mut key, value) in isolated_entries {
598 dbtx.raw_remove_entry(&key).await?;
599 let mut new_key = vec![DbKeyPrefix::ClientDatabase as u8];
600 new_key.append(&mut key);
601 dbtx.raw_insert_bytes(&new_key, &value).await?;
602 }
603 }
604
605 Ok(())
606}
607
608#[derive(Debug, Encodable, Decodable)]
609struct RegisteredIncomingContractKey(pub PaymentImage);
610
611#[derive(Debug, Encodable, Decodable)]
612pub struct RegisteredIncomingContract {
613 pub federation_id: FederationId,
614 pub incoming_amount_msats: u64,
616 pub contract: IncomingContract,
617}
618
619impl_db_record!(
620 key = RegisteredIncomingContractKey,
621 value = RegisteredIncomingContract,
622 db_prefix = DbKeyPrefix::RegisteredIncomingContract,
623);
624
625#[cfg(test)]
626mod migration_tests;