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 }