diff --git a/crates/cashu/src/mint.rs b/crates/cashu/src/mint.rs index a784a2e94..13f90731e 100644 --- a/crates/cashu/src/mint.rs +++ b/crates/cashu/src/mint.rs @@ -4,7 +4,6 @@ use bitcoin::bip32::DerivationPath; use serde::{Deserialize, Serialize}; use uuid::Uuid; -use crate::mint_url::MintUrl; use crate::nuts::{MeltQuoteState, MintQuoteState}; use crate::{Amount, CurrencyUnit, Id, KeySetInfo, PublicKey}; @@ -13,8 +12,6 @@ use crate::{Amount, CurrencyUnit, Id, KeySetInfo, PublicKey}; pub struct MintQuote { /// Quote id pub id: Uuid, - /// Mint Url - pub mint_url: MintUrl, /// Amount of quote pub amount: Amount, /// Unit of quote @@ -34,7 +31,6 @@ pub struct MintQuote { impl MintQuote { /// Create new [`MintQuote`] pub fn new( - mint_url: MintUrl, request: String, unit: CurrencyUnit, amount: Amount, @@ -45,7 +41,6 @@ impl MintQuote { let id = Uuid::new_v4(); Self { - mint_url, id, amount, unit, diff --git a/crates/cdk-axum/src/router_handlers.rs b/crates/cdk-axum/src/router_handlers.rs index ec2a5c372..e91719fba 100644 --- a/crates/cdk-axum/src/router_handlers.rs +++ b/crates/cdk-axum/src/router_handlers.rs @@ -342,7 +342,18 @@ pub async fn post_check( ))] /// Mint information, operator contact information, and other info pub async fn get_mint_info(State(state): State) -> Result, Response> { - Ok(Json(state.mint.mint_info().clone().time(unix_time()))) + Ok(Json( + state + .mint + .mint_info() + .await + .map_err(|err| { + tracing::error!("Could not get mint info: {}", err); + into_response(err) + })? + .clone() + .time(unix_time()), + )) } #[cfg_attr(feature = "swagger", utoipa::path( diff --git a/crates/cdk-common/src/database/mint.rs b/crates/cdk-common/src/database/mint.rs index 4755b626d..3ce81f2ee 100644 --- a/crates/cdk-common/src/database/mint.rs +++ b/crates/cdk-common/src/database/mint.rs @@ -3,10 +3,11 @@ use std::collections::HashMap; use async_trait::async_trait; +use cashu::MintInfo; use uuid::Uuid; use super::Error; -use crate::common::LnKey; +use crate::common::{LnKey, QuoteTTL}; use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote}; use crate::nuts::{ BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, Proof, @@ -127,4 +128,14 @@ pub trait Database { &self, quote_id: &Uuid, ) -> Result, Self::Err>; + + /// Set [`MintInfo`] + async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Self::Err>; + /// Get [`MintInfo`] + async fn get_mint_info(&self) -> Result; + + /// Set [`QuoteTTL`] + async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Self::Err>; + /// Get [`QuoteTTL`] + async fn get_quote_ttl(&self) -> Result; } diff --git a/crates/cdk-integration-tests/src/init_fake_wallet.rs b/crates/cdk-integration-tests/src/init_fake_wallet.rs index 011610010..831ff0796 100644 --- a/crates/cdk-integration-tests/src/init_fake_wallet.rs +++ b/crates/cdk-integration-tests/src/init_fake_wallet.rs @@ -50,7 +50,6 @@ where mint_builder = mint_builder .with_name("fake test mint".to_string()) - .with_mint_url(format!("http://{addr}:{port}")) .with_description("fake test mint".to_string()) .with_quote_ttl(10000, 10000) .with_seed(mnemonic.to_seed_normalized("").to_vec()); diff --git a/crates/cdk-integration-tests/src/init_pure_tests.rs b/crates/cdk-integration-tests/src/init_pure_tests.rs index 7ab29a3fe..afccd18d8 100644 --- a/crates/cdk-integration-tests/src/init_pure_tests.rs +++ b/crates/cdk-integration-tests/src/init_pure_tests.rs @@ -38,11 +38,7 @@ impl DirectMintConnection { impl Debug for DirectMintConnection { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!( - f, - "DirectMintConnection {{ mint_info: {:?} }}", - self.mint.config.mint_info() - ) + write!(f, "DirectMintConnection",) } } @@ -130,7 +126,7 @@ impl MintConnector for DirectMintConnection { } async fn get_mint_info(&self) -> Result { - Ok(self.mint.mint_info().clone().time(unix_time())) + Ok(self.mint.mint_info().await?.clone().time(unix_time())) } async fn post_check_state( @@ -175,7 +171,6 @@ pub async fn create_and_start_test_mint() -> anyhow::Result> { mint_builder = mint_builder .with_name("pure test mint".to_string()) - .with_mint_url("http://aa".to_string()) .with_description("pure test mint".to_string()) .with_quote_ttl(10000, 10000) .with_seed(mnemonic.to_seed_normalized("").to_vec()); @@ -198,7 +193,7 @@ pub fn create_test_wallet_for_mint(mint: Arc) -> anyhow::Result Mint { let mint_info = MintInfo::new().nuts(nuts); - let mnemonic = Mnemonic::generate(12).unwrap(); + let localstore = MintMemoryDatabase::default(); - let quote_ttl = QuoteTTL::new(10000, 10000); + localstore + .set_mint_info(mint_info) + .await + .expect("Could not set mint info"); + let mnemonic = Mnemonic::generate(12).unwrap(); Mint::new( - MINT_URL, &mnemonic.to_seed_normalized(""), - mint_info, - quote_ttl, - Arc::new(MintMemoryDatabase::default()), + Arc::new(localstore), HashMap::new(), supported_units, HashMap::new(), @@ -72,7 +73,6 @@ async fn mint_proofs( let request_lookup = uuid::Uuid::new_v4().to_string(); let quote = MintQuote::new( - mint.config.mint_url(), "".to_string(), CurrencyUnit::Sat, amount, diff --git a/crates/cdk-mintd/src/main.rs b/crates/cdk-mintd/src/main.rs index ca2d3f7df..f6d728e67 100644 --- a/crates/cdk-mintd/src/main.rs +++ b/crates/cdk-mintd/src/main.rs @@ -301,7 +301,6 @@ async fn main() -> anyhow::Result<()> { mint_builder = mint_builder .with_name(settings.mint_info.name) - .with_mint_url(settings.info.url) .with_version(mint_version) .with_description(settings.mint_info.description) .with_quote_ttl(10000, 10000) diff --git a/crates/cdk-redb/src/error.rs b/crates/cdk-redb/src/error.rs index a4a51a001..3b12ef280 100644 --- a/crates/cdk-redb/src/error.rs +++ b/crates/cdk-redb/src/error.rs @@ -52,6 +52,9 @@ pub enum Error { /// Unknown Mint Info #[error("Unknown mint info")] UnknownMintInfo, + /// Unknown quote ttl + #[error("Unknown quote ttl")] + UnknownQuoteTTL, /// Unknown Proof Y #[error("Unknown proof Y")] UnknownY, diff --git a/crates/cdk-redb/src/mint/migrations.rs b/crates/cdk-redb/src/mint/migrations.rs index 3dafb99a5..b7025588d 100644 --- a/crates/cdk-redb/src/mint/migrations.rs +++ b/crates/cdk-redb/src/mint/migrations.rs @@ -202,7 +202,6 @@ impl From for MintQuote { fn from(quote: V1MintQuote) -> MintQuote { MintQuote { id: quote.id, - mint_url: quote.mint_url, amount: quote.amount, unit: quote.unit, request: quote.request.clone(), diff --git a/crates/cdk-redb/src/mint/mod.rs b/crates/cdk-redb/src/mint/mod.rs index 89385f45b..648caa4da 100644 --- a/crates/cdk-redb/src/mint/mod.rs +++ b/crates/cdk-redb/src/mint/mod.rs @@ -7,14 +7,14 @@ use std::str::FromStr; use std::sync::Arc; use async_trait::async_trait; -use cdk_common::common::LnKey; +use cdk_common::common::{LnKey, QuoteTTL}; use cdk_common::database::{self, MintDatabase}; use cdk_common::dhke::hash_to_curve; use cdk_common::mint::{self, MintKeySetInfo, MintQuote}; use cdk_common::nut00::ProofsMethods; use cdk_common::{ - BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, Proof, - Proofs, PublicKey, State, + BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintInfo, MintQuoteState, + Proof, Proofs, PublicKey, State, }; use migrations::{migrate_01_to_02, migrate_04_to_05}; use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition}; @@ -812,4 +812,56 @@ impl MintDatabase for MintRedbDatabase { Ok(signatures) } + + async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Self::Err> { + let write_txn = self.db.begin_write().map_err(Error::from)?; + + { + let mut table = write_txn.open_table(CONFIG_TABLE).map_err(Error::from)?; + table + .insert("mint_info", serde_json::to_string(&mint_info)?.as_str()) + .map_err(Error::from)?; + } + write_txn.commit().map_err(Error::from)?; + + Ok(()) + } + async fn get_mint_info(&self) -> Result { + let read_txn = self.db.begin_read().map_err(Error::from)?; + let table = read_txn.open_table(CONFIG_TABLE).map_err(Error::from)?; + + if let Some(mint_info) = table.get("mint_info").map_err(Error::from)? { + let mint_info = serde_json::from_str(mint_info.value())?; + + return Ok(mint_info); + } + + Err(Error::UnknownMintInfo.into()) + } + + async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Self::Err> { + let write_txn = self.db.begin_write().map_err(Error::from)?; + + { + let mut table = write_txn.open_table(CONFIG_TABLE).map_err(Error::from)?; + table + .insert("quote_ttl", serde_json::to_string("e_ttl)?.as_str()) + .map_err(Error::from)?; + } + write_txn.commit().map_err(Error::from)?; + + Ok(()) + } + async fn get_quote_ttl(&self) -> Result { + let read_txn = self.db.begin_read().map_err(Error::from)?; + let table = read_txn.open_table(CONFIG_TABLE).map_err(Error::from)?; + + if let Some(quote_ttl) = table.get("quote_ttl").map_err(Error::from)? { + let quote_ttl = serde_json::from_str(quote_ttl.value())?; + + return Ok(quote_ttl); + } + + Err(Error::UnknownQuoteTTL.into()) + } } diff --git a/crates/cdk-sqlite/src/mint/error.rs b/crates/cdk-sqlite/src/mint/error.rs index e4c185fda..d52922cfe 100644 --- a/crates/cdk-sqlite/src/mint/error.rs +++ b/crates/cdk-sqlite/src/mint/error.rs @@ -44,6 +44,12 @@ pub enum Error { /// Serde Error #[error(transparent)] Serde(#[from] serde_json::Error), + /// Unknown Mint Info + #[error("Unknown mint info")] + UnknownMintInfo, + /// Unknown quote TTL + #[error("Unknown quote TTL")] + UnknownQuoteTTL, } impl From for cdk_common::database::Error { diff --git a/crates/cdk-sqlite/src/mint/migrations/20250129200912_remove_mint_url.sql b/crates/cdk-sqlite/src/mint/migrations/20250129200912_remove_mint_url.sql new file mode 100644 index 000000000..a48fbb27a --- /dev/null +++ b/crates/cdk-sqlite/src/mint/migrations/20250129200912_remove_mint_url.sql @@ -0,0 +1 @@ +ALTER TABLE mint_quote DROP COLUMN mint_url; diff --git a/crates/cdk-sqlite/src/mint/migrations/20250129230326_add_config_table.sql b/crates/cdk-sqlite/src/mint/migrations/20250129230326_add_config_table.sql new file mode 100644 index 000000000..41d059002 --- /dev/null +++ b/crates/cdk-sqlite/src/mint/migrations/20250129230326_add_config_table.sql @@ -0,0 +1,4 @@ +CREATE TABLE IF NOT EXISTS config ( + id TEXT PRIMARY KEY, + value TEXT NOT NULL +); diff --git a/crates/cdk-sqlite/src/mint/mod.rs b/crates/cdk-sqlite/src/mint/mod.rs index d0c6dbe0e..65891d75f 100644 --- a/crates/cdk-sqlite/src/mint/mod.rs +++ b/crates/cdk-sqlite/src/mint/mod.rs @@ -7,16 +7,16 @@ use std::time::Duration; use async_trait::async_trait; use bitcoin::bip32::DerivationPath; -use cdk_common::common::LnKey; +use cdk_common::common::{LnKey, QuoteTTL}; use cdk_common::database::{self, MintDatabase}; use cdk_common::mint::{self, MintKeySetInfo, MintQuote}; -use cdk_common::mint_url::MintUrl; use cdk_common::nut00::ProofsMethods; use cdk_common::nut05::QuoteState; use cdk_common::secret::Secret; use cdk_common::{ Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltBolt11Request, - MeltQuoteState, MintQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State, + MeltQuoteState, MintInfo, MintQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SecretKey, + State, }; use error::Error; use lightning_invoice::Bolt11Invoice; @@ -206,12 +206,11 @@ WHERE active = 1 let res = sqlx::query( r#" INSERT OR REPLACE INTO mint_quote -(id, mint_url, amount, unit, request, state, expiry, request_lookup_id, pubkey) -VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); +(id, amount, unit, request, state, expiry, request_lookup_id, pubkey) +VALUES (?, ?, ?, ?, ?, ?, ?, ?); "#, ) .bind(quote.id.to_string()) - .bind(quote.mint_url.to_string()) .bind(u64::from(quote.amount) as i64) .bind(quote.unit.to_string()) .bind(quote.request) @@ -1221,6 +1220,148 @@ WHERE quote_id=?; } } } + + async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Self::Err> { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let res = sqlx::query( + r#" +INSERT OR REPLACE INTO config +(id, value) +VALUES (?, ?); + "#, + ) + .bind("mint_info") + .bind(serde_json::to_string(&mint_info)?) + .execute(&mut transaction) + .await; + + match res { + Ok(_) => { + transaction.commit().await.map_err(Error::from)?; + Ok(()) + } + Err(err) => { + tracing::error!("SQLite Could not update mint info"); + if let Err(err) = transaction.rollback().await { + tracing::error!("Could not rollback sql transaction: {}", err); + } + + Err(Error::from(err).into()) + } + } + } + async fn get_mint_info(&self) -> Result { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let rec = sqlx::query( + r#" +SELECT * +FROM config +WHERE id=?; + "#, + ) + .bind("mint_info") + .fetch_one(&mut transaction) + .await; + + match rec { + Ok(rec) => { + transaction.commit().await.map_err(Error::from)?; + + let value: String = rec.try_get("value").map_err(Error::from)?; + + let mint_info = serde_json::from_str(&value)?; + + Ok(mint_info) + } + Err(err) => match err { + sqlx::Error::RowNotFound => { + transaction.commit().await.map_err(Error::from)?; + return Err(Error::UnknownMintInfo.into()); + } + _ => { + return { + if let Err(err) = transaction.rollback().await { + tracing::error!("Could not rollback sql transaction: {}", err); + } + Err(Error::SQLX(err).into()) + } + } + }, + } + } + + async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Self::Err> { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let res = sqlx::query( + r#" +INSERT OR REPLACE INTO config +(id, value) +VALUES (?, ?); + "#, + ) + .bind("quote_ttl") + .bind(serde_json::to_string("e_ttl)?) + .execute(&mut transaction) + .await; + + match res { + Ok(_) => { + transaction.commit().await.map_err(Error::from)?; + Ok(()) + } + Err(err) => { + tracing::error!("SQLite Could not update mint info"); + if let Err(err) = transaction.rollback().await { + tracing::error!("Could not rollback sql transaction: {}", err); + } + + Err(Error::from(err).into()) + } + } + } + async fn get_quote_ttl(&self) -> Result { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let rec = sqlx::query( + r#" +SELECT * +FROM config +WHERE id=?; + "#, + ) + .bind("quote_ttl") + .fetch_one(&mut transaction) + .await; + + match rec { + Ok(rec) => { + transaction.commit().await.map_err(Error::from)?; + + let value: String = rec.try_get("value").map_err(Error::from)?; + + let quote_ttl = serde_json::from_str(&value)?; + + Ok(quote_ttl) + } + Err(err) => match err { + sqlx::Error::RowNotFound => { + transaction.commit().await.map_err(Error::from)?; + return Err(Error::UnknownQuoteTTL.into()); + } + _ => { + return { + if let Err(err) = transaction.rollback().await { + tracing::error!("Could not rollback sql transaction: {}", err); + } + Err(Error::SQLX(err).into()) + } + } + }, + } + } } fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result { @@ -1250,7 +1391,6 @@ fn sqlite_row_to_keyset_info(row: SqliteRow) -> Result { fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result { let row_id: Hyphenated = row.try_get("id").map_err(Error::from)?; - let row_mint_url: String = row.try_get("mint_url").map_err(Error::from)?; let row_amount: i64 = row.try_get("amount").map_err(Error::from)?; let row_unit: String = row.try_get("unit").map_err(Error::from)?; let row_request: String = row.try_get("request").map_err(Error::from)?; @@ -1274,7 +1414,6 @@ fn sqlite_row_to_mint_quote(row: SqliteRow) -> Result { Ok(MintQuote { id: row_id.into_uuid(), - mint_url: MintUrl::from_str(&row_mint_url)?, amount: Amount::from(row_amount as u64), unit: CurrencyUnit::from_str(&row_unit).map_err(Error::from)?, request: row_request, diff --git a/crates/cdk/Cargo.toml b/crates/cdk/Cargo.toml index 1dfad52ad..5c28749cf 100644 --- a/crates/cdk/Cargo.toml +++ b/crates/cdk/Cargo.toml @@ -23,7 +23,6 @@ http_subscription = [] [dependencies] cdk-common = { path = "../cdk-common", version = "0.6.0" } cbor-diag = "0.1.12" -arc-swap = "1.7.1" async-trait = "0.1" anyhow = { version = "1.0.43", features = ["backtrace"] } bitcoin = { version = "0.32.2", features = [ diff --git a/crates/cdk/src/cdk_database/mint_memory.rs b/crates/cdk/src/cdk_database/mint_memory.rs index 86ec3577e..52bf4c703 100644 --- a/crates/cdk/src/cdk_database/mint_memory.rs +++ b/crates/cdk/src/cdk_database/mint_memory.rs @@ -4,9 +4,11 @@ use std::collections::HashMap; use std::sync::Arc; use async_trait::async_trait; +use cdk_common::common::QuoteTTL; use cdk_common::database::{Error, MintDatabase}; use cdk_common::mint::MintKeySetInfo; use cdk_common::nut00::ProofsMethods; +use cdk_common::MintInfo; use tokio::sync::{Mutex, RwLock}; use uuid::Uuid; @@ -33,6 +35,8 @@ pub struct MintMemoryDatabase { blinded_signatures: Arc>>, quote_signatures: Arc>>>, melt_requests: Arc, LnKey)>>>, + mint_info: Arc>, + quote_ttl: Arc>, } impl MintMemoryDatabase { @@ -49,6 +53,8 @@ impl MintMemoryDatabase { blinded_signatures: HashMap<[u8; 33], BlindSignature>, quote_signatures: HashMap>, melt_request: Vec<(MeltBolt11Request, LnKey)>, + mint_info: MintInfo, + quote_ttl: QuoteTTL, ) -> Result { let mut proofs = HashMap::new(); let mut proof_states = HashMap::new(); @@ -87,6 +93,8 @@ impl MintMemoryDatabase { quote_proofs: Arc::new(Mutex::new(quote_proofs)), quote_signatures: Arc::new(RwLock::new(quote_signatures)), melt_requests: Arc::new(RwLock::new(melt_requests)), + mint_info: Arc::new(RwLock::new(mint_info)), + quote_ttl: Arc::new(RwLock::new(quote_ttl)), }) } } @@ -412,4 +420,30 @@ impl MintDatabase for MintMemoryDatabase { Ok(ys.get(quote_id).cloned().unwrap_or_default()) } + + async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Self::Err> { + let mut current_mint_info = self.mint_info.write().await; + + *current_mint_info = mint_info; + + Ok(()) + } + async fn get_mint_info(&self) -> Result { + let mint_info = self.mint_info.read().await; + + Ok(mint_info.clone()) + } + + async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), Self::Err> { + let mut current_quote_ttl = self.quote_ttl.write().await; + + *current_quote_ttl = quote_ttl; + + Ok(()) + } + async fn get_quote_ttl(&self) -> Result { + let quote_ttl = self.quote_ttl.read().await; + + Ok(*quote_ttl) + } } diff --git a/crates/cdk/src/mint/builder.rs b/crates/cdk/src/mint/builder.rs index cb68bccf1..5ab52befa 100644 --- a/crates/cdk/src/mint/builder.rs +++ b/crates/cdk/src/mint/builder.rs @@ -21,8 +21,6 @@ use crate::types::{LnKey, QuoteTTL}; /// Cashu Mint #[derive(Default)] pub struct MintBuilder { - /// Mint Url - mint_url: Option, /// Mint Info mint_info: MintInfo, /// Mint Storage backend @@ -63,12 +61,6 @@ impl MintBuilder { self } - /// Set mint url - pub fn with_mint_url(mut self, mint_url: String) -> Self { - self.mint_url = Some(mint_url); - self - } - /// Set seed pub fn with_seed(mut self, seed: Vec) -> Self { self.seed = Some(seed); @@ -226,14 +218,19 @@ impl MintBuilder { /// Build mint pub async fn build(&self) -> anyhow::Result { + let localstore = self + .localstore + .clone() + .ok_or(anyhow!("Localstore not set"))?; + localstore.set_mint_info(self.mint_info.clone()).await?; + + localstore + .set_quote_ttl(self.quote_ttl.ok_or(anyhow!("Quote ttl not set"))?) + .await?; + Ok(Mint::new( - self.mint_url.as_ref().ok_or(anyhow!("Mint url not set"))?, self.seed.as_ref().ok_or(anyhow!("Mint seed not set"))?, - self.mint_info.clone(), - self.quote_ttl.ok_or(anyhow!("Quote ttl not set"))?, - self.localstore - .clone() - .ok_or(anyhow!("Localstore not set"))?, + localstore, self.ln.clone().ok_or(anyhow!("Ln backends not set"))?, self.supported_units.clone(), HashMap::new(), diff --git a/crates/cdk/src/mint/config.rs b/crates/cdk/src/mint/config.rs deleted file mode 100644 index a669cea09..000000000 --- a/crates/cdk/src/mint/config.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Active mint configuration -//! -//! This is the active configuration that can be updated at runtime. -use std::collections::HashMap; -use std::sync::Arc; - -use arc_swap::ArcSwap; - -use super::{Id, MintInfo, MintKeySet}; -use crate::mint_url::MintUrl; -use crate::types::QuoteTTL; - -/// Mint Inner configuration -pub struct Config { - /// Active Mint Keysets - pub keysets: HashMap, - /// Mint url - pub mint_info: MintInfo, - /// Mint config - pub mint_url: MintUrl, - /// Quotes ttl - pub quote_ttl: QuoteTTL, -} - -/// Mint configuration -/// -/// This struct is used to configure the mint, and it is wrapped inside a ArcSwap, so it can be -/// updated at runtime without locking the shared config nor without requiriming a mutable reference -/// to the config -/// -/// ArcSwap is used instead of a RwLock since the updates should be less frequent than the reads -#[derive(Clone)] -pub struct SwappableConfig { - config: Arc>, -} - -impl SwappableConfig { - /// Creates a new configuration instance - pub fn new( - mint_url: MintUrl, - quote_ttl: QuoteTTL, - mint_info: MintInfo, - keysets: HashMap, - ) -> Self { - let inner = Config { - keysets, - quote_ttl, - mint_info, - mint_url, - }; - - Self { - config: Arc::new(ArcSwap::from_pointee(inner)), - } - } - - /// Gets an Arc of the current configuration - pub fn load(&self) -> Arc { - self.config.load().clone() - } - - /// Gets a copy of the mint url - pub fn mint_url(&self) -> MintUrl { - self.load().mint_url.clone() - } - - /// Replace the current mint url with a new one - pub fn set_mint_url(&self, mint_url: MintUrl) { - let current_inner = self.load(); - let new_inner = Config { - mint_url, - quote_ttl: current_inner.quote_ttl, - mint_info: current_inner.mint_info.clone(), - keysets: current_inner.keysets.clone(), - }; - - self.config.store(Arc::new(new_inner)); - } - - /// Gets a copy of the quote ttl - pub fn quote_ttl(&self) -> QuoteTTL { - self.load().quote_ttl - } - - /// Replaces the current quote ttl with a new one - pub fn set_quote_ttl(&self, quote_ttl: QuoteTTL) { - let current_inner = self.load(); - let new_inner = Config { - mint_info: current_inner.mint_info.clone(), - mint_url: current_inner.mint_url.clone(), - quote_ttl, - keysets: current_inner.keysets.clone(), - }; - - self.config.store(Arc::new(new_inner)); - } - - /// Gets a copy of the mint info - pub fn mint_info(&self) -> MintInfo { - self.load().mint_info.clone() - } - - /// Replaces the current mint info with a new one - pub fn set_mint_info(&self, mint_info: MintInfo) { - let current_inner = self.load(); - let new_inner = Config { - mint_info, - mint_url: current_inner.mint_url.clone(), - quote_ttl: current_inner.quote_ttl, - keysets: current_inner.keysets.clone(), - }; - - self.config.store(Arc::new(new_inner)); - } - - /// Replaces the current keysets with a new one - pub fn set_keysets(&self, keysets: HashMap) { - let current_inner = self.load(); - let new_inner = Config { - mint_info: current_inner.mint_info.clone(), - quote_ttl: current_inner.quote_ttl, - mint_url: current_inner.mint_url.clone(), - keysets, - }; - - self.config.store(Arc::new(new_inner)); - } -} diff --git a/crates/cdk/src/mint/info.rs b/crates/cdk/src/mint/info.rs deleted file mode 100644 index 4cbb174c5..000000000 --- a/crates/cdk/src/mint/info.rs +++ /dev/null @@ -1,30 +0,0 @@ -use tracing::instrument; - -use super::{Mint, MintInfo}; -use crate::mint_url::MintUrl; - -impl Mint { - /// Set Mint Url - #[instrument(skip_all)] - pub fn set_mint_url(&self, mint_url: MintUrl) { - self.config.set_mint_url(mint_url); - } - - /// Get Mint Url - #[instrument(skip_all)] - pub fn get_mint_url(&self) -> MintUrl { - self.config.mint_url() - } - - /// Set Mint Info - #[instrument(skip_all)] - pub fn set_mint_info(&self, mint_info: MintInfo) { - self.config.set_mint_info(mint_info); - } - - /// Get Mint Info - #[instrument(skip_all)] - pub fn mint_info(&self) -> MintInfo { - self.config.mint_info() - } -} diff --git a/crates/cdk/src/mint/keysets.rs b/crates/cdk/src/mint/keysets.rs index abad59839..c099381cf 100644 --- a/crates/cdk/src/mint/keysets.rs +++ b/crates/cdk/src/mint/keysets.rs @@ -16,9 +16,9 @@ impl Mint { pub async fn keyset_pubkeys(&self, keyset_id: &Id) -> Result { self.ensure_keyset_loaded(keyset_id).await?; let keyset = self - .config - .load() .keysets + .read() + .await .get(keyset_id) .ok_or(Error::UnknownKeySet)? .clone(); @@ -41,9 +41,9 @@ impl Mint { Ok(KeysResponse { keysets: self - .config - .load() .keysets + .read() + .await .values() .filter_map(|k| match active_keysets.contains(&k.id) { true => Some(k.clone().into()), @@ -82,8 +82,7 @@ impl Mint { #[instrument(skip(self))] pub async fn keyset(&self, id: &Id) -> Result, Error> { self.ensure_keyset_loaded(id).await?; - let config = self.config.load(); - let keysets = &config.keysets; + let keysets = self.keysets.read().await; let keyset = keysets.get(id).map(|k| k.clone().into()); Ok(keyset) } @@ -118,9 +117,8 @@ impl Mint { self.localstore.add_keyset_info(keyset_info).await?; self.localstore.set_active_keyset(unit, id).await?; - let mut keysets = self.config.load().keysets.clone(); + let mut keysets = self.keysets.write().await; keysets.insert(id, keyset); - self.config.set_keysets(keysets); Ok(()) } @@ -128,11 +126,14 @@ impl Mint { /// Ensure Keyset is loaded in mint #[instrument(skip(self))] pub async fn ensure_keyset_loaded(&self, id: &Id) -> Result<(), Error> { - if self.config.load().keysets.contains_key(id) { - return Ok(()); + { + let keysets = self.keysets.read().await; + if keysets.contains_key(id) { + return Ok(()); + } } - let mut keysets = self.config.load().keysets.clone(); + let mut keysets = self.keysets.write().await; let keyset_info = self .localstore .get_keyset_info(id) @@ -140,7 +141,6 @@ impl Mint { .ok_or(Error::UnknownKeySet)?; let id = keyset_info.id; keysets.insert(id, self.generate_keyset(keyset_info)); - self.config.set_keysets(keysets); Ok(()) } diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index ac55266ac..89ba6d4af 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -31,7 +31,7 @@ impl Mint { request: String, options: Option, ) -> Result<(), Error> { - let mint_info = self.mint_info(); + let mint_info = self.localstore.get_mint_info().await?; let nut05 = mint_info.nuts.nut05; let nut15 = mint_info.nuts.nut15; @@ -120,12 +120,14 @@ impl Mint { // or we want to ignore the amount and do an mpp payment let msats_to_pay = options.map(|opt| opt.amount_msat()); + let melt_ttl = self.localstore.get_quote_ttl().await?.melt_ttl; + let quote = MeltQuote::new( request.to_string(), unit.clone(), payment_quote.amount, payment_quote.fee, - unix_time() + self.config.quote_ttl().melt_ttl, + unix_time() + melt_ttl, payment_quote.request_lookup_id.clone(), msats_to_pay, ); diff --git a/crates/cdk/src/mint/mint_nut04.rs b/crates/cdk/src/mint/mint_nut04.rs index f253486b7..ab2ef58aa 100644 --- a/crates/cdk/src/mint/mint_nut04.rs +++ b/crates/cdk/src/mint/mint_nut04.rs @@ -12,12 +12,12 @@ use crate::{Amount, Error}; impl Mint { /// Checks that minting is enabled, request is supported unit and within range - fn check_mint_request_acceptable( + async fn check_mint_request_acceptable( &self, amount: Amount, unit: &CurrencyUnit, ) -> Result<(), Error> { - let mint_info = self.mint_info(); + let mint_info = self.localstore.get_mint_info().await?; let nut04 = &mint_info.nuts.nut04; if nut04.disabled { @@ -69,7 +69,7 @@ impl Mint { pubkey, } = mint_quote_request; - self.check_mint_request_acceptable(amount, &unit)?; + self.check_mint_request_acceptable(amount, &unit).await?; let ln = self .ln @@ -80,7 +80,9 @@ impl Mint { Error::UnitUnsupported })?; - let quote_expiry = unix_time() + self.config.quote_ttl().mint_ttl; + let mint_ttl = self.localstore.get_quote_ttl().await?.mint_ttl; + + let quote_expiry = unix_time() + mint_ttl; if description.is_some() && !ln.get_settings().invoice_description { tracing::error!("Backend does not support invoice description"); @@ -101,7 +103,6 @@ impl Mint { })?; let quote = MintQuote::new( - self.config.mint_url(), create_invoice_response.request.to_string(), unit.clone(), amount, diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 23fd96e12..910c4da2c 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -1,19 +1,17 @@ //! Cashu Mint use std::collections::HashMap; -use std::str::FromStr; use std::sync::Arc; use bitcoin::bip32::{ChildNumber, DerivationPath, Xpriv}; use bitcoin::secp256k1::{self, Secp256k1}; -use cdk_common::common::{LnKey, QuoteTTL}; +use cdk_common::common::LnKey; use cdk_common::database::{self, MintDatabase}; use cdk_common::mint::MintKeySetInfo; -use config::SwappableConfig; use futures::StreamExt; use serde::{Deserialize, Serialize}; use subscription::PubSubManager; -use tokio::sync::Notify; +use tokio::sync::{Notify, RwLock}; use tokio::task::JoinSet; use tracing::instrument; use uuid::Uuid; @@ -22,15 +20,12 @@ use crate::cdk_lightning::{self, MintLightning}; use crate::dhke::{sign_message, verify_message}; use crate::error::Error; use crate::fees::calculate_fee; -use crate::mint_url::MintUrl; use crate::nuts::*; use crate::util::unix_time; use crate::Amount; mod builder; mod check_spendable; -pub mod config; -mod info; mod keysets; mod melt; mod mint_nut04; @@ -44,8 +39,6 @@ pub use cdk_common::mint::{MeltQuote, MintQuote}; /// Cashu Mint #[derive(Clone)] pub struct Mint { - /// Mint Config - pub config: SwappableConfig, /// Mint Storage backend pub localstore: Arc + Send + Sync>, /// Ln backends for mint @@ -54,16 +47,14 @@ pub struct Mint { pub pubsub_manager: Arc, secp_ctx: Secp256k1, xpriv: Xpriv, + keysets: Arc>>, } impl Mint { /// Create new [`Mint`] #[allow(clippy::too_many_arguments)] pub async fn new( - mint_url: &str, seed: &[u8], - mint_info: MintInfo, - quote_ttl: QuoteTTL, localstore: Arc + Send + Sync>, ln: HashMap + Send + Sync>>, // Hashmap where the key is the unit and value is (input fee ppk, max_order) @@ -182,21 +173,23 @@ impl Mint { } } + let keysets = Arc::new(RwLock::new(active_keysets)); + Ok(Self { - config: SwappableConfig::new( - MintUrl::from_str(mint_url)?, - quote_ttl, - mint_info, - active_keysets, - ), pubsub_manager: Arc::new(localstore.clone().into()), secp_ctx, xpriv, localstore, ln, + keysets, }) } + /// Get mint info + pub async fn mint_info(&self) -> Result { + Ok(self.localstore.get_mint_info().await?) + } + /// Wait for any invoice to be paid /// For each backend starts a task that waits for any invoice to be paid /// Once invoice is paid mint quote status is updated @@ -315,8 +308,7 @@ impl Mint { return Err(Error::InactiveKeyset); } - let config = self.config.load(); - let keysets = &config.keysets; + let keysets = self.keysets.read().await; let keyset = keysets.get(keyset_id).ok_or(Error::UnknownKeySet)?; let key_pair = match keyset.keys.get(amount) { @@ -361,8 +353,7 @@ impl Mint { } self.ensure_keyset_loaded(&proof.keyset_id).await?; - let config = self.config.load(); - let keysets = &config.keysets; + let keysets = self.keysets.read().await; let keyset = keysets.get(&proof.keyset_id).ok_or(Error::UnknownKeySet)?; let keypair = match keyset.keys.get(&proof.amount) { @@ -564,6 +555,7 @@ fn derivation_path_from_unit(unit: CurrencyUnit, index: u32) -> Option { active_keysets: HashMap, @@ -671,7 +662,6 @@ mod tests { blinded_signatures: HashMap<[u8; 33], BlindSignature>, quote_proofs: HashMap>, quote_signatures: HashMap>, - mint_url: &'a str, seed: &'a [u8], mint_info: MintInfo, supported_units: HashMap, @@ -692,15 +682,14 @@ mod tests { config.blinded_signatures, config.quote_signatures, config.melt_requests, + config.mint_info, + config.quote_ttl, ) .unwrap(), ); Mint::new( - config.mint_url, config.seed, - config.mint_info, - config.quote_ttl, localstore, HashMap::new(), config.supported_units, @@ -712,15 +701,10 @@ mod tests { #[tokio::test] async fn mint_mod_new_mint() -> Result<(), Error> { let config = MintConfig::<'_> { - mint_url: "http://example.com", ..Default::default() }; let mint = create_mint(config).await?; - assert_eq!(mint.get_mint_url().to_string(), "http://example.com"); - let info = mint.mint_info(); - assert!(info.name.is_none()); - assert!(info.pubkey.is_none()); assert_eq!( mint.pubkeys().await.unwrap(), KeysResponse { @@ -751,7 +735,6 @@ mod tests { #[tokio::test] async fn mint_mod_rotate_keyset() -> Result<(), Error> { let config = MintConfig::<'_> { - mint_url: "http://example.com", ..Default::default() }; let mint = create_mint(config).await?; @@ -796,7 +779,6 @@ mod tests { println!("{}", seed); let config = MintConfig::<'_> { - mint_url: "http://example.com", seed: &seed.to_seed_normalized(""), ..Default::default() }; @@ -805,7 +787,7 @@ mod tests { mint.rotate_keyset(CurrencyUnit::default(), 0, 32, 1, HashMap::new()) .await?; - let keys = mint.config.load().keysets.clone(); + let keys = mint.keysets.read().await.clone(); let expected_keys = r#"{"005f6e8c540c9e61":{"id":"005f6e8c540c9e61","unit":"sat","keys":{"1":{"public_key":"03e8aded7525acee36e3394e28f2dcbc012533ef2a2b085a55fc291d311afee3ef","secret_key":"32ee9fc0723772aed4c7b8ac0a02ffe390e54a4e0b037ec6035c2afa10ebd873"},"2":{"public_key":"02628c0919e5cb8ce9aed1f81ce313f40e1ab0b33439d5be2abc69d9bb574902e0","secret_key":"48384bf901bbe8f937d601001d067e73b28b435819c009589350c664f9ba872c"},"4":{"public_key":"039e7c7f274e1e8a90c61669e961c944944e6154c0794fccf8084af90252d2848f","secret_key":"1f039c1e54e9e65faae8ecf69492f810b4bb2292beb3734059f2bb4d564786d0"},"8":{"public_key":"02ca0e563ae941700aefcb16a7fb820afbb3258ae924ab520210cb730227a76ca3","secret_key":"ea3c2641d847c9b15c5f32c150b5c9c04d0666af0549e54f51f941cf584442be"},"16":{"public_key":"031dbab0e4f7fb4fb0030f0e1a1dc80668eadd0b1046df3337bb13a7b9c982d392","secret_key":"5b244f8552077e68b30b534e85bd0e8e29ae0108ff47f5cd92522aa524d3288f"},"32":{"public_key":"037241f7ad421374eb764a48e7769b5e2473582316844fda000d6eef28eea8ffb8","secret_key":"95608f61dd690aef34e6a2d4cbef3ad8fddb4537a14480a17512778058e4f5bd"},"64":{"public_key":"02bc9767b4abf88becdac47a59e67ee9a9a80b9864ef57d16084575273ac63c0e7","secret_key":"2e9cd067fafa342f3118bc1e62fbb8e53acdb0f96d51ce8a1e1037e43fad0dce"},"128":{"public_key":"0351e33a076f415c2cadc945bc9bcb75bf4a774b28df8a0605dea1557e5897fed8","secret_key":"7014f27be5e2b77e4951a81c18ae3585d0b037899d8a37b774970427b13d8f65"},"256":{"public_key":"0314b9f4300367c7e64fa85770da90839d2fc2f57d63660f08bb3ebbf90ed76840","secret_key":"1a545bd9c40fc6cf2ab281710e279967e9f4b86cd07761c741da94bc8042c8fb"},"512":{"public_key":"030d95abc7e881d173f4207a3349f4ee442b9e51cc461602d3eb9665b9237e8db3","secret_key":"622984ef16d1cb28e9adc7a7cfea1808d85b4bdabd015977f0320c9f573858b4"},"1024":{"public_key":"0351a68a667c5fc21d66c187baecefa1d65529d06b7ae13112d432b6bca16b0e8c","secret_key":"6a8badfa26129499b60edb96cda4cbcf08f8007589eb558a9d0307bdc56e0ff6"},"2048":{"public_key":"0376166d8dcf97d8b0e9f11867ff0dafd439c90255b36a25be01e37e14741b9c6a","secret_key":"48fe41181636716ce202b3a3303c2475e6d511991930868d907441e1bcbf8566"},"4096":{"public_key":"03d40f47b4e5c4d72f2a977fab5c66b54d945b2836eb888049b1dd9334d1d70304","secret_key":"66a25bf144a3b40c015dd1f630aa4ba81d2242f5aee845e4f378246777b21676"},"8192":{"public_key":"03be18afaf35a29d7bcd5dfd1936d82c1c14691a63f8aa6ece258e16b0c043049b","secret_key":"4ddac662e82f6028888c11bdefd07229d7c1b56987395f106cc9ea5b301695f6"},"16384":{"public_key":"028e9c6ce70f34cd29aad48656bf8345bb5ba2cb4f31fdd978686c37c93f0ab411","secret_key":"83676bd7d047655476baecad2864519f0ffd8e60f779956d2faebcc727caa7bd"},"32768":{"public_key":"0253e34bab4eec93e235c33994e01bf851d5caca4559f07d37b5a5c266de7cf840","secret_key":"d5be522906223f5d92975e2a77f7e166aa121bf93d5fe442d6d132bf67166b04"},"65536":{"public_key":"02684ede207f9ace309b796b5259fc81ef0d4492b4fb5d66cf866b0b4a6f27bec9","secret_key":"20d859b7052d768e007bf285ee11dc0b98a4abfe272a551852b0cce9fb6d5ad4"},"131072":{"public_key":"027cdf7be8b20a49ac7f2f065f7c53764c8926799877858c6b00b888a8aa6741a5","secret_key":"f6eef28183344b32fc0a1fba00cd6cf967614e51d1c990f0bfce8f67c6d9746a"},"262144":{"public_key":"026939b8f766c3ebaf26408e7e54fc833805563e2ef14c8ee4d0435808b005ec4c","secret_key":"690f23e4eaa250c652afeac24d4efb583095a66abf6b87a7f3d17b1f42c5f896"},"524288":{"public_key":"03772542057493a46eed6513b40386e766eedada16560ffde2f776b65794e9f004","secret_key":"fe36e61bea74665f8796b4b62f9501ae6e0d5b16733d2c05c146cd39f89475a0"},"1048576":{"public_key":"02b016346e5a322d371c6e6164b28b31b4d93a51572351ca2f26cdc12e916d9ac3","secret_key":"b9269779e057ce715964caa6d6b5b65672f255e86746e994b6b8c4780cb9d728"},"2097152":{"public_key":"028f25283e36a11df7713934a5287267381f8304aca3c1eb1b89fddce973ef1436","secret_key":"41aec998b9624ddcff97eb7341daa6385b2a8714ed3f12969ef39649f4d641ab"},"4194304":{"public_key":"03e5841d310819a49ec42dfb24839c61f68bbfc93ac68f6dad37fd5b2d204cc535","secret_key":"e5aef2509c56236f004e2df4343beab6406816fb187c3532d4340a9674857c64"},"8388608":{"public_key":"0307ebfeb87b7bca9baa03fad00499e5cc999fa5179ef0b7ad4f555568bcb946f5","secret_key":"369e8dcabcc69a2eabb7363beb66178cafc29e53b02c46cd15374028c3110541"},"16777216":{"public_key":"02f2508e7df981c32f7b0008a273e2a1f19c23bb60a1561dba6b2a95ed1251eb90","secret_key":"f93965b96ed5428bcacd684eff2f43a9777d03adfde867fa0c6efb39c46a7550"},"33554432":{"public_key":"0381883a1517f8c9979a84fcd5f18437b1a2b0020376ecdd2e515dc8d5a157a318","secret_key":"7f5e77c7ed04dff952a7c15564ab551c769243eb65423adfebf46bf54360cd64"},"67108864":{"public_key":"02aa648d39c9a725ef5927db15af6895f0d43c17f0a31faff4406314fc80180086","secret_key":"d34eda86679bf872dfb6faa6449285741bba6c6d582cd9fe5a9152d5752596cc"},"134217728":{"public_key":"0380658e5163fcf274e1ace6c696d1feef4c6068e0d03083d676dc5ef21804f22d","secret_key":"3ad22e92d497309c5b08b2dc01cb5180de3e00d3d703229914906bc847183987"},"268435456":{"public_key":"031526f03de945c638acccb879de837ac3fabff8590057cfb8552ebcf51215f3aa","secret_key":"3a740771e29119b171ab8e79e97499771439e0ab6a082ec96e43baf06a546372"},"536870912":{"public_key":"035eb3e7262e126c5503e1b402db05f87de6556773ae709cb7aa1c3b0986b87566","secret_key":"9b77ee8cd879128c0ea6952dd188e63617fbaa9e66a3bca0244bcceb9b1f7f48"},"1073741824":{"public_key":"03f12e6a0903ed0db87485a296b1dca9d953a8a6919ff88732238fbc672d6bd125","secret_key":"f3947bca4df0f024eade569c81c5c53e167476e074eb81fa6b289e5e10dd4e42"},"2147483648":{"public_key":"02cece3fb38a54581e0646db4b29242b6d78e49313dda46764094f9d128c1059c1","secret_key":"582d54a894cd41441157849e0d16750e5349bd9310776306e7313b255866950b"}}}}"#;