/ gateway / ln-gateway / src / db.rs
db.rs
  1  use std::collections::BTreeMap;
  2  
  3  use bitcoin::Network;
  4  use bitcoin_hashes::sha256;
  5  use fedimint_core::config::FederationId;
  6  use fedimint_core::db::{
  7      DatabaseTransaction, DatabaseVersion, IDatabaseTransactionOpsCoreTyped, ServerMigrationFn,
  8  };
  9  use fedimint_core::encoding::{Decodable, Encodable};
 10  use fedimint_core::invite_code::InviteCode;
 11  use fedimint_core::{impl_db_lookup, impl_db_record, secp256k1};
 12  use fedimint_ln_common::serde_routing_fees;
 13  use fedimint_lnv2_client::CreateInvoicePayload;
 14  use futures::FutureExt;
 15  use lightning_invoice::RoutingFees;
 16  use rand::Rng;
 17  use serde::{Deserialize, Serialize};
 18  use strum_macros::EnumIter;
 19  
 20  use crate::rpc::rpc_server::hash_password;
 21  
 22  pub const GATEWAYD_DATABASE_VERSION: DatabaseVersion = DatabaseVersion(1);
 23  
 24  #[repr(u8)]
 25  #[derive(Clone, EnumIter, Debug)]
 26  pub enum DbKeyPrefix {
 27      FederationConfig = 0x04,
 28      GatewayPublicKey = 0x06,
 29      GatewayConfiguration = 0x07,
 30      PreimageAuthentication = 0x08,
 31      CreateInvoicePayload = 0x09,
 32  }
 33  
 34  impl std::fmt::Display for DbKeyPrefix {
 35      fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
 36          write!(f, "{self:?}")
 37      }
 38  }
 39  
 40  #[derive(Debug, Clone, Encodable, Decodable, Eq, PartialEq, Hash, Ord, PartialOrd)]
 41  pub struct FederationIdKey {
 42      pub id: FederationId,
 43  }
 44  
 45  #[derive(Debug, Encodable, Decodable)]
 46  pub struct FederationIdKeyPrefix;
 47  
 48  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
 49  pub struct FederationConfig {
 50      pub invite_code: InviteCode,
 51      pub mint_channel_id: u64,
 52      pub timelock_delta: u64,
 53      #[serde(with = "serde_routing_fees")]
 54      pub fees: RoutingFees,
 55  }
 56  
 57  impl_db_record!(
 58      key = FederationIdKey,
 59      value = FederationConfig,
 60      db_prefix = DbKeyPrefix::FederationConfig,
 61  );
 62  
 63  impl_db_lookup!(key = FederationIdKey, query_prefix = FederationIdKeyPrefix);
 64  
 65  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
 66  pub struct GatewayPublicKey;
 67  
 68  impl_db_record!(
 69      key = GatewayPublicKey,
 70      value = secp256k1::KeyPair,
 71      db_prefix = DbKeyPrefix::GatewayPublicKey,
 72  );
 73  
 74  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
 75  pub struct GatewayConfigurationKeyV0;
 76  
 77  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
 78  pub struct GatewayConfigurationV0 {
 79      pub password: String,
 80      pub num_route_hints: u32,
 81      #[serde(with = "serde_routing_fees")]
 82      pub routing_fees: RoutingFees,
 83      pub network: Network,
 84  }
 85  
 86  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
 87  pub struct GatewayConfigurationKey;
 88  
 89  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable, Serialize, Deserialize)]
 90  pub struct GatewayConfiguration {
 91      pub hashed_password: sha256::Hash,
 92      pub num_route_hints: u32,
 93      #[serde(with = "serde_routing_fees")]
 94      pub routing_fees: RoutingFees,
 95      pub network: Network,
 96      pub password_salt: [u8; 16],
 97  }
 98  
 99  impl_db_record!(
100      key = GatewayConfigurationKeyV0,
101      value = GatewayConfigurationV0,
102      db_prefix = DbKeyPrefix::GatewayConfiguration,
103  );
104  
105  impl_db_record!(
106      key = GatewayConfigurationKey,
107      value = GatewayConfiguration,
108      db_prefix = DbKeyPrefix::GatewayConfiguration,
109      notify_on_modify = true,
110  );
111  
112  #[derive(Debug, Clone, Eq, PartialEq, Encodable, Decodable)]
113  pub struct PreimageAuthentication {
114      pub payment_hash: sha256::Hash,
115  }
116  
117  impl_db_record!(
118      key = PreimageAuthentication,
119      value = sha256::Hash,
120      db_prefix = DbKeyPrefix::PreimageAuthentication
121  );
122  
123  #[derive(Debug, Encodable, Decodable)]
124  pub struct PreimageAuthenticationPrefix;
125  
126  impl_db_lookup!(
127      key = PreimageAuthentication,
128      query_prefix = PreimageAuthenticationPrefix
129  );
130  
131  pub fn get_gatewayd_database_migrations() -> BTreeMap<DatabaseVersion, ServerMigrationFn> {
132      let mut migrations: BTreeMap<DatabaseVersion, ServerMigrationFn> = BTreeMap::new();
133      migrations.insert(DatabaseVersion(0), move |dbtx| migrate_to_v1(dbtx).boxed());
134      migrations
135  }
136  
137  async fn migrate_to_v1(dbtx: &mut DatabaseTransaction<'_>) -> Result<(), anyhow::Error> {
138      // If there is no old gateway configuration, there is nothing to do.
139      if let Some(old_gateway_config) = dbtx.remove_entry(&GatewayConfigurationKeyV0).await {
140          let password_salt: [u8; 16] = rand::thread_rng().gen();
141          let hashed_password = hash_password(old_gateway_config.password, password_salt);
142          let new_gateway_config = GatewayConfiguration {
143              hashed_password,
144              num_route_hints: old_gateway_config.num_route_hints,
145              routing_fees: old_gateway_config.routing_fees,
146              network: old_gateway_config.network,
147              password_salt,
148          };
149          dbtx.insert_entry(&GatewayConfigurationKey, &new_gateway_config)
150              .await;
151      }
152  
153      Ok(())
154  }
155  
156  #[derive(Debug, Encodable, Decodable)]
157  pub struct CreateInvoicePayloadKey(pub [u8; 32]);
158  
159  impl_db_record!(
160      key = CreateInvoicePayloadKey,
161      value = CreateInvoicePayload,
162      db_prefix = DbKeyPrefix::CreateInvoicePayload,
163  );
164  
165  #[cfg(test)]
166  mod fedimint_migration_tests {
167      use std::str::FromStr;
168  
169      use anyhow::ensure;
170      use bitcoin::Network;
171      use bitcoin_hashes::{sha256, Hash};
172      use fedimint_core::config::FederationId;
173      use fedimint_core::db::{Database, IDatabaseTransactionOpsCoreTyped};
174      use fedimint_core::invite_code::InviteCode;
175      use fedimint_core::module::registry::ModuleDecoderRegistry;
176      use fedimint_core::secp256k1;
177      use fedimint_core::util::SafeUrl;
178      use fedimint_logging::TracingSetup;
179      use fedimint_testing::db::{
180          snapshot_db_migrations_with_decoders, validate_migrations_global, BYTE_32,
181      };
182      use futures::StreamExt;
183      use rand::rngs::OsRng;
184      use strum::IntoEnumIterator;
185      use tracing::info;
186  
187      use super::{
188          FederationConfig, FederationIdKey, GatewayConfigurationKey, GatewayConfigurationKeyV0,
189          GatewayConfigurationV0, GatewayPublicKey, PreimageAuthentication,
190      };
191      use crate::db::{
192          get_gatewayd_database_migrations, DbKeyPrefix, FederationIdKeyPrefix,
193          PreimageAuthenticationPrefix, GATEWAYD_DATABASE_VERSION,
194      };
195      use crate::DEFAULT_FEES;
196  
197      async fn create_gatewayd_db_data(db: Database) {
198          let mut dbtx = db.begin_transaction().await;
199          let federation_id = FederationId::dummy();
200          let invite_code = InviteCode::new(
201              SafeUrl::from_str("http://myexamplefed.com").expect("SafeUrl parsing can't fail"),
202              0.into(),
203              federation_id,
204          );
205          let federation_config = FederationConfig {
206              invite_code,
207              mint_channel_id: 2,
208              timelock_delta: 10,
209              fees: DEFAULT_FEES,
210          };
211  
212          dbtx.insert_new_entry(&FederationIdKey { id: federation_id }, &federation_config)
213              .await;
214  
215          let context = secp256k1::Secp256k1::new();
216          let (secret, _) = context.generate_keypair(&mut OsRng);
217          let key_pair = secp256k1::KeyPair::from_secret_key(&context, &secret);
218          dbtx.insert_new_entry(&GatewayPublicKey, &key_pair).await;
219  
220          let gateway_configuration = GatewayConfigurationV0 {
221              password: "EXAMPLE".to_string(),
222              num_route_hints: 2,
223              routing_fees: DEFAULT_FEES,
224              network: Network::Regtest,
225          };
226  
227          dbtx.insert_new_entry(&GatewayConfigurationKeyV0, &gateway_configuration)
228              .await;
229  
230          let preimage_auth = PreimageAuthentication {
231              payment_hash: sha256::Hash::from_slice(&BYTE_32).expect("Hash should not fail"),
232          };
233          let verification_hash = sha256::Hash::from_slice(&BYTE_32).expect("Hash should not fail");
234          dbtx.insert_new_entry(&preimage_auth, &verification_hash)
235              .await;
236  
237          dbtx.commit_tx().await;
238      }
239  
240      #[tokio::test(flavor = "multi_thread")]
241      async fn snapshot_server_db_migrations() -> anyhow::Result<()> {
242          snapshot_db_migrations_with_decoders(
243              "gatewayd",
244              |db| {
245                  Box::pin(async move {
246                      create_gatewayd_db_data(db).await;
247                  })
248              },
249              ModuleDecoderRegistry::from_iter([]),
250          )
251          .await
252      }
253  
254      #[tokio::test(flavor = "multi_thread")]
255      async fn test_server_db_migrations() -> anyhow::Result<()> {
256          let _ = TracingSetup::default().init();
257          validate_migrations_global(
258              |db| async move {
259                  let mut dbtx = db.begin_transaction().await;
260  
261                  for prefix in DbKeyPrefix::iter() {
262                      match prefix {
263                          DbKeyPrefix::FederationConfig => {
264                              let configs = dbtx
265                                  .find_by_prefix(&FederationIdKeyPrefix)
266                                  .await
267                                  .collect::<Vec<_>>()
268                                  .await;
269                              let num_configs = configs.len();
270                              ensure!(
271                                  num_configs > 0,
272                                  "validate_migrations was not able to read any FederationConfigs"
273                              );
274                              info!("Validated FederationConfig");
275                          }
276                          DbKeyPrefix::GatewayPublicKey => {
277                              let gateway_id = dbtx.get_value(&GatewayPublicKey).await;
278                              ensure!(gateway_id.is_some(), "validate_migrations was not able to read GatewayPublicKey");
279                              info!("Validated GatewayPublicKey");
280                          }
281                          DbKeyPrefix::PreimageAuthentication => {
282                              let preimage_authentications = dbtx.find_by_prefix(&PreimageAuthenticationPrefix).await.collect::<Vec<_>>().await;
283                              let num_auths = preimage_authentications.len();
284                              ensure!(num_auths > 0, "validate_migrations was not able to read any PreimageAuthentication");
285                              info!("Validated PreimageAuthentication");
286                          }
287                          DbKeyPrefix::GatewayConfiguration => {
288                              let gateway_configuration = dbtx.get_value(&GatewayConfigurationKey).await;
289                              ensure!(gateway_configuration.is_some(), "validate_migrations was not able to read GatewayConfiguration");
290                              info!("Validated GatewayConfiguration");
291                          }
292                          DbKeyPrefix::CreateInvoicePayload => {}
293                      }
294                  }
295                  Ok(())
296              },
297              "gatewayd",
298              GATEWAYD_DATABASE_VERSION,
299              get_gatewayd_database_migrations(),
300              ModuleDecoderRegistry::from_iter([]),
301          )
302          .await
303      }
304  }