From 2e80677631ef5bfcaf9190ef42516c1d3247917d Mon Sep 17 00:00:00 2001 From: thesimplekid Date: Mon, 23 Sep 2024 18:14:31 +0200 Subject: [PATCH] feat: store and get melt requests --- crates/cdk-axum/src/lib.rs | 19 +--- .../cdk-integration-tests/src/init_regtest.rs | 2 +- crates/cdk-integration-tests/src/lib.rs | 2 +- crates/cdk-mintd/src/main.rs | 40 ++++++- crates/cdk-redb/src/mint/mod.rs | 48 +++++++- crates/cdk-sqlite/src/mint/error.rs | 3 + .../20240923153640_melt_requests.sql | 8 ++ crates/cdk-sqlite/src/mint/mod.rs | 106 +++++++++++++++++- crates/cdk/src/cdk_database/mint_memory.rs | 34 +++++- crates/cdk/src/cdk_database/mod.rs | 14 +++ crates/cdk/src/mint/mod.rs | 4 + crates/cdk/src/types.rs | 20 +++- 12 files changed, 270 insertions(+), 30 deletions(-) create mode 100644 crates/cdk-sqlite/src/mint/migrations/20240923153640_melt_requests.sql diff --git a/crates/cdk-axum/src/lib.rs b/crates/cdk-axum/src/lib.rs index fea7cf32c..51cefca2f 100644 --- a/crates/cdk-axum/src/lib.rs +++ b/crates/cdk-axum/src/lib.rs @@ -13,7 +13,7 @@ use axum::Router; use cdk::cdk_lightning::{self, MintLightning}; use cdk::mint::Mint; use cdk::mint_url::MintUrl; -use cdk::nuts::{CurrencyUnit, PaymentMethod}; +use cdk::types::LnKey; use router_handlers::*; mod router_handlers; @@ -66,20 +66,3 @@ pub struct MintState { mint_url: MintUrl, quote_ttl: u64, } - -/// Key used in hashmap of ln backends to identify what unit and payment method -/// it is for -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub struct LnKey { - /// Unit of Payment backend - pub unit: CurrencyUnit, - /// Method of payment backend - pub method: PaymentMethod, -} - -impl LnKey { - /// Create new [`LnKey`] - pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self { - Self { unit, method } - } -} diff --git a/crates/cdk-integration-tests/src/init_regtest.rs b/crates/cdk-integration-tests/src/init_regtest.rs index 8d2f20b38..3c8dac65b 100644 --- a/crates/cdk-integration-tests/src/init_regtest.rs +++ b/crates/cdk-integration-tests/src/init_regtest.rs @@ -8,8 +8,8 @@ use cdk::{ cdk_lightning::MintLightning, mint::{FeeReserve, Mint}, nuts::{CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings}, + types::LnKey, }; -use cdk_axum::LnKey; use cdk_cln::Cln as CdkCln; use futures::StreamExt; use ln_regtest_rs::{ diff --git a/crates/cdk-integration-tests/src/lib.rs b/crates/cdk-integration-tests/src/lib.rs index 41de06473..c8bfa951c 100644 --- a/crates/cdk-integration-tests/src/lib.rs +++ b/crates/cdk-integration-tests/src/lib.rs @@ -14,9 +14,9 @@ use cdk::nuts::{ CurrencyUnit, Id, KeySet, MeltMethodSettings, MintInfo, MintMethodSettings, MintQuoteState, Nuts, PaymentMethod, PreMintSecrets, Proofs, }; +use cdk::types::LnKey; use cdk::wallet::client::HttpClient; use cdk::{Mint, Wallet}; -use cdk_axum::LnKey; use cdk_fake_wallet::FakeWallet; use futures::StreamExt; use init_regtest::{get_mint_addr, get_mint_port, get_mint_url}; diff --git a/crates/cdk-mintd/src/main.rs b/crates/cdk-mintd/src/main.rs index bd447e28e..4d972fb84 100644 --- a/crates/cdk-mintd/src/main.rs +++ b/crates/cdk-mintd/src/main.rs @@ -20,7 +20,7 @@ use cdk::nuts::{ nut04, nut05, ContactInfo, CurrencyUnit, MeltMethodSettings, MintInfo, MintMethodSettings, MintVersion, MppMethodSettings, Nuts, PaymentMethod, }; -use cdk_axum::LnKey; +use cdk::types::LnKey; use cdk_cln::Cln; use cdk_fake_wallet::FakeWallet; use cdk_lnbits::LNbits; @@ -438,9 +438,17 @@ async fn main() -> anyhow::Result<()> { // it is possible that a mint quote was paid but the mint has not been updated // this will check and update the mint state of those quotes for ln in ln_backends.values() { - check_pending_quotes(Arc::clone(&mint), Arc::clone(ln)).await?; + check_pending_mint_quotes(Arc::clone(&mint), Arc::clone(ln)).await?; } + /* + check_pending_melt_quotes( + Arc::clone(&mint), + ln_backends.values().into_iter().collect(), + ) + .await; + */ + let mint_url = settings.info.url; let listen_addr = settings.info.listen_host; let listen_port = settings.info.listen_port; @@ -505,7 +513,7 @@ async fn handle_paid_invoice(mint: Arc, request_lookup_id: &str) -> Result } /// Used on mint start up to check status of all pending mint quotes -async fn check_pending_quotes( +async fn check_pending_mint_quotes( mint: Arc, ln: Arc + Send + Sync>, ) -> Result<()> { @@ -539,6 +547,32 @@ async fn check_pending_quotes( Ok(()) } +/* +async fn check_pending_melt_quotes( + mint: Arc, + ln_backends: Vec + Send + Sync>>, +) -> Result<()> { + let melt_quotes = mint.localstore.get_melt_quotes().await?; + let pending_quotes: Vec = melt_quotes + .into_iter() + .filter(|q| q.state == MeltQuoteState::Pending || q.state == MeltQuoteState::Unknown) + .collect(); + + for pending_quote in pending_quotes { + let mut payment_response = vec![]; + for ln_backend in ln_backends.iter() { + let pay_invoice_response = ln_backend + .check_outgoing_payment(&pending_quote.request_lookup_id) + .await?; + + payment_response.push(pay_invoice_response); + } + } + + todo!() +} +*/ + fn expand_path(path: &str) -> Option { if path.starts_with('~') { if let Some(home_dir) = home::home_dir().as_mut() { diff --git a/crates/cdk-redb/src/mint/mod.rs b/crates/cdk-redb/src/mint/mod.rs index f8646b625..55d62221f 100644 --- a/crates/cdk-redb/src/mint/mod.rs +++ b/crates/cdk-redb/src/mint/mod.rs @@ -11,9 +11,10 @@ use cdk::cdk_database::MintDatabase; use cdk::dhke::hash_to_curve; use cdk::mint::{MintKeySetInfo, MintQuote}; use cdk::nuts::{ - BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, - State, + BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, Proof, + Proofs, PublicKey, State, }; +use cdk::types::LnKey; use cdk::{cdk_database, mint}; use migrations::migrate_01_to_02; use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition}; @@ -39,6 +40,8 @@ const QUOTE_PROOFS_TABLE: MultimapTableDefinition<&str, [u8; 33]> = const QUOTE_SIGNATURES_TABLE: MultimapTableDefinition<&str, &str> = MultimapTableDefinition::new("quote_signatures"); +const MELT_REQUESTS: TableDefinition<&str, (&str, &str)> = TableDefinition::new("melt_requests"); + const DATABASE_VERSION: u32 = 4; /// Mint Redbdatabase @@ -735,4 +738,45 @@ impl MintDatabase for MintRedbDatabase { }) .collect()) } + + /// Add melt request + async fn add_melt_request( + &self, + melt_request: MeltBolt11Request, + ln_key: LnKey, + ) -> Result<(), Self::Err> { + let write_txn = self.db.begin_write().map_err(Error::from)?; + let mut table = write_txn.open_table(MELT_REQUESTS).map_err(Error::from)?; + + table + .insert( + melt_request.quote.as_str(), + ( + serde_json::to_string(&melt_request)?.as_str(), + serde_json::to_string(&ln_key)?.as_str(), + ), + ) + .map_err(Error::from)?; + + Ok(()) + } + /// Get melt request + async fn get_melt_request( + &self, + quote_id: &str, + ) -> Result, Self::Err> { + let read_txn = self.db.begin_read().map_err(Error::from)?; + let table = read_txn.open_table(MELT_REQUESTS).map_err(Error::from)?; + + match table.get(quote_id).map_err(Error::from)? { + Some(melt_request) => { + let (melt_request_str, ln_key_str) = melt_request.value(); + let melt_request = serde_json::from_str(melt_request_str)?; + let ln_key = serde_json::from_str(ln_key_str)?; + + Ok(Some((melt_request, ln_key))) + } + None => Ok(None), + } + } } diff --git a/crates/cdk-sqlite/src/mint/error.rs b/crates/cdk-sqlite/src/mint/error.rs index 8683a489d..5f2be0907 100644 --- a/crates/cdk-sqlite/src/mint/error.rs +++ b/crates/cdk-sqlite/src/mint/error.rs @@ -41,6 +41,9 @@ pub enum Error { /// Invalid Database Path #[error("Invalid database path")] InvalidDbPath, + /// Serde Error + #[error(transparent)] + Serde(#[from] serde_json::Error), } impl From for cdk::cdk_database::Error { diff --git a/crates/cdk-sqlite/src/mint/migrations/20240923153640_melt_requests.sql b/crates/cdk-sqlite/src/mint/migrations/20240923153640_melt_requests.sql new file mode 100644 index 000000000..d04bd0406 --- /dev/null +++ b/crates/cdk-sqlite/src/mint/migrations/20240923153640_melt_requests.sql @@ -0,0 +1,8 @@ +-- Melt Requesr Table +CREATE TABLE IF NOT EXISTS melt_request ( +id TEXT PRIMARY KEY, +inputs TEXT NOT NULL, +outputs TEXT, +method TEXT NOT NULL, +unit TEXT NOT NULL, +); diff --git a/crates/cdk-sqlite/src/mint/mod.rs b/crates/cdk-sqlite/src/mint/mod.rs index c2fdf48c7..7f5920e0a 100644 --- a/crates/cdk-sqlite/src/mint/mod.rs +++ b/crates/cdk-sqlite/src/mint/mod.rs @@ -12,10 +12,11 @@ use cdk::mint::{MintKeySetInfo, MintQuote}; use cdk::mint_url::MintUrl; use cdk::nuts::nut05::QuoteState; use cdk::nuts::{ - BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, - State, + BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, + PaymentMethod, Proof, Proofs, PublicKey, State, }; use cdk::secret::Secret; +use cdk::types::LnKey; use cdk::{mint, Amount}; use error::Error; use lightning_invoice::Bolt11Invoice; @@ -1121,6 +1122,86 @@ WHERE keyset_id=?; } } } + + async fn add_melt_request( + &self, + melt_request: MeltBolt11Request, + ln_key: LnKey, + ) -> Result<(), Self::Err> { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let res = sqlx::query( + r#" +INSERT OR REPLACE INTO melt_request +(id, inputs, outputs, method, unit) +VALUES (?, ?, ?, ?, ?); + "#, + ) + .bind(melt_request.quote) + .bind(serde_json::to_string(&melt_request.inputs)?) + .bind(serde_json::to_string(&melt_request.outputs)?) + .bind(ln_key.method.to_string()) + .bind(ln_key.unit.to_string()) + .execute(&mut transaction) + .await; + + match res { + Ok(_) => { + transaction.commit().await.map_err(Error::from)?; + Ok(()) + } + Err(err) => { + tracing::error!("SQLite Could not update keyset"); + if let Err(err) = transaction.rollback().await { + tracing::error!("Could not rollback sql transaction: {}", err); + } + + Err(Error::from(err).into()) + } + } + } + + async fn get_melt_request( + &self, + quote_id: &str, + ) -> Result, Self::Err> { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let rec = sqlx::query( + r#" +SELECT * +FROM melt_request +WHERE id=?; + "#, + ) + .bind(quote_id) + .fetch_one(&mut transaction) + .await; + + match rec { + Ok(rec) => { + transaction.commit().await.map_err(Error::from)?; + + let (request, key) = sqlite_row_to_melt_request(rec)?; + + Ok(Some((request, key))) + } + Err(err) => match err { + sqlx::Error::RowNotFound => { + transaction.commit().await.map_err(Error::from)?; + return Ok(None); + } + _ => { + 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 { @@ -1259,3 +1340,24 @@ fn sqlite_row_to_blind_signature(row: SqliteRow) -> Result Result<(MeltBolt11Request, LnKey), Error> { + let quote_id: String = row.try_get("id").map_err(Error::from)?; + let row_inputs: String = row.try_get("inputs").map_err(Error::from)?; + let row_outputs: Option = row.try_get("outputs").map_err(Error::from)?; + let row_method: String = row.try_get("method").map_err(Error::from)?; + let row_unit: String = row.try_get("unit").map_err(Error::from)?; + + let melt_request = MeltBolt11Request { + quote: quote_id, + inputs: serde_json::from_str(&row_inputs)?, + outputs: row_outputs.and_then(|o| serde_json::from_str(&o).ok()), + }; + + let ln_key = LnKey { + unit: CurrencyUnit::from_str(&row_unit)?, + method: PaymentMethod::from_str(&row_method)?, + }; + + Ok((melt_request, ln_key)) +} diff --git a/crates/cdk/src/cdk_database/mint_memory.rs b/crates/cdk/src/cdk_database/mint_memory.rs index 81f158867..ac9f18728 100644 --- a/crates/cdk/src/cdk_database/mint_memory.rs +++ b/crates/cdk/src/cdk_database/mint_memory.rs @@ -11,9 +11,10 @@ use crate::dhke::hash_to_curve; use crate::mint::{self, MintKeySetInfo, MintQuote}; use crate::nuts::nut07::State; use crate::nuts::{ - nut07, BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, - PublicKey, + nut07, BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintQuoteState, + Proof, Proofs, PublicKey, }; +use crate::types::LnKey; /// Mint Memory Database #[derive(Debug, Clone, Default)] @@ -27,6 +28,7 @@ pub struct MintMemoryDatabase { quote_proofs: Arc>>>, blinded_signatures: Arc>>, quote_signatures: Arc>>>, + melt_requests: Arc>>, } impl MintMemoryDatabase { @@ -42,6 +44,7 @@ impl MintMemoryDatabase { quote_proofs: HashMap>, blinded_signatures: HashMap<[u8; 33], BlindSignature>, quote_signatures: HashMap>, + melt_request: Vec<(MeltBolt11Request, LnKey)>, ) -> Result { let mut proofs = HashMap::new(); let mut proof_states = HashMap::new(); @@ -58,6 +61,11 @@ impl MintMemoryDatabase { proof_states.insert(y, State::Spent); } + let melt_requests = melt_request + .into_iter() + .map(|(request, ln_key)| (request.quote.clone(), (request, ln_key))) + .collect(); + Ok(Self { active_keysets: Arc::new(RwLock::new(active_keysets)), keysets: Arc::new(RwLock::new( @@ -74,6 +82,7 @@ impl MintMemoryDatabase { blinded_signatures: Arc::new(RwLock::new(blinded_signatures)), quote_proofs: Arc::new(Mutex::new(quote_proofs)), quote_signatures: Arc::new(RwLock::new(quote_signatures)), + melt_requests: Arc::new(RwLock::new(melt_requests)), }) } } @@ -225,6 +234,27 @@ impl MintDatabase for MintMemoryDatabase { Ok(()) } + async fn add_melt_request( + &self, + melt_request: MeltBolt11Request, + ln_key: LnKey, + ) -> Result<(), Self::Err> { + let mut melt_requests = self.melt_requests.write().await; + melt_requests.insert(melt_request.quote.clone(), (melt_request, ln_key)); + Ok(()) + } + + async fn get_melt_request( + &self, + quote_id: &str, + ) -> Result, Self::Err> { + let melt_requests = self.melt_requests.read().await; + + let melt_request = melt_requests.get(quote_id); + + Ok(melt_request.cloned()) + } + async fn add_proofs(&self, proofs: Proofs, quote_id: Option) -> Result<(), Self::Err> { let mut db_proofs = self.proofs.write().await; diff --git a/crates/cdk/src/cdk_database/mod.rs b/crates/cdk/src/cdk_database/mod.rs index c10aac51c..338f8ddd8 100644 --- a/crates/cdk/src/cdk_database/mod.rs +++ b/crates/cdk/src/cdk_database/mod.rs @@ -16,12 +16,14 @@ use crate::mint::MintKeySetInfo; use crate::mint::MintQuote as MintMintQuote; #[cfg(feature = "wallet")] use crate::mint_url::MintUrl; +use crate::nuts::MeltBolt11Request; #[cfg(feature = "mint")] use crate::nuts::{BlindSignature, MeltQuoteState, MintQuoteState, Proof, Proofs}; #[cfg(any(feature = "wallet", feature = "mint"))] use crate::nuts::{CurrencyUnit, Id, PublicKey, State}; #[cfg(feature = "wallet")] use crate::nuts::{KeySetInfo, Keys, MintInfo, SpendingConditions}; +use crate::types::LnKey; #[cfg(feature = "wallet")] use crate::types::ProofInfo; #[cfg(feature = "wallet")] @@ -220,6 +222,18 @@ pub trait MintDatabase { /// Remove [`mint::MeltQuote`] async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err>; + /// Add melt request + async fn add_melt_request( + &self, + melt_request: MeltBolt11Request, + ln_key: LnKey, + ) -> Result<(), Self::Err>; + /// Get melt request + async fn get_melt_request( + &self, + quote_id: &str, + ) -> Result, Self::Err>; + /// Add [`MintKeySetInfo`] async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>; /// Get [`MintKeySetInfo`] diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index bd854f26d..16b337272 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -1572,6 +1572,8 @@ mod tests { use bitcoin::Network; use secp256k1::Secp256k1; + use crate::types::LnKey; + use super::*; #[test] @@ -1677,6 +1679,7 @@ mod tests { seed: &'a [u8], mint_info: MintInfo, supported_units: HashMap, + melt_requests: Vec<(MeltBolt11Request, LnKey)>, } async fn create_mint(config: MintConfig<'_>) -> Result { @@ -1691,6 +1694,7 @@ mod tests { config.quote_proofs, config.blinded_signatures, config.quote_signatures, + config.melt_requests, ) .unwrap(), ); diff --git a/crates/cdk/src/types.rs b/crates/cdk/src/types.rs index e393ce5ac..ad75a7529 100644 --- a/crates/cdk/src/types.rs +++ b/crates/cdk/src/types.rs @@ -5,7 +5,8 @@ use serde::{Deserialize, Serialize}; use crate::error::Error; use crate::mint_url::MintUrl; use crate::nuts::{ - CurrencyUnit, MeltQuoteState, Proof, Proofs, PublicKey, SpendingConditions, State, + CurrencyUnit, MeltQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SpendingConditions, + State, }; use crate::Amount; @@ -137,6 +138,23 @@ impl ProofInfo { } } +/// Key used in hashmap of ln backends to identify what unit and payment method +/// it is for +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct LnKey { + /// Unit of Payment backend + pub unit: CurrencyUnit, + /// Method of payment backend + pub method: PaymentMethod, +} + +impl LnKey { + /// Create new [`LnKey`] + pub fn new(unit: CurrencyUnit, method: PaymentMethod) -> Self { + Self { unit, method } + } +} + #[cfg(test)] mod tests { use std::str::FromStr;