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