diff --git a/CHANGELOG.md b/CHANGELOG.md index 64742c7e..0ea7dfae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - cdk(wallet): Add `fn melt_proofs` that uses specific proofs for `melt` instead of selecting ([thesimplekid]). - cdk-cli(receive): Add support for signing keys to be nostr nsec encoded ([thesimplekid]). - cdk-fake-wallet: Add Fake wallet for testing ([thesimplekid]). +- cdk(cdk-database/mint): Add `add_proofs`, `get_proofs_by_ys`, `get_proofs_states`, and `update_proofs_states` ([thesimplekid]). ### Fixed - cdk(mint): `SIG_ALL` is not allowed in `melt` ([thesimplekid]). @@ -47,6 +48,7 @@ ### Removed - cdk(wallet): Remove unused argument `SplitTarget` on `melt` ([thesimplekid]). +- cdk(cdk-database/mint): Remove `get_spent_proofs`, `get_spent_proofs_by_ys`,`get_pending_proofs`, `get_pending_proofs_by_ys`, and `remove_pending_proofs` ([thesimplekid]). ## [v0.2.0] diff --git a/crates/cdk-redb/src/mint/migrations.rs b/crates/cdk-redb/src/mint/migrations.rs index b9244e85..7eae5e0e 100644 --- a/crates/cdk-redb/src/mint/migrations.rs +++ b/crates/cdk-redb/src/mint/migrations.rs @@ -1,23 +1,31 @@ +use core::str; use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; use cdk::mint::MintQuote; -use cdk::nuts::{CurrencyUnit, MintQuoteState}; +use cdk::nuts::{CurrencyUnit, MintQuoteState, Proof, State}; use cdk::{Amount, UncheckedUrl}; use lightning_invoice::Bolt11Invoice; use redb::{Database, ReadableTable, TableDefinition}; use serde::{Deserialize, Serialize}; -use super::Error; +use super::{Error, PROOFS_STATE_TABLE, PROOFS_TABLE}; const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes"); +const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = + TableDefinition::new("pending_proofs"); +const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs"); pub fn migrate_01_to_02(db: Arc) -> Result { migrate_mint_quotes_01_to_02(db)?; - Ok(2) } + +pub fn migrate_02_to_03(db: Arc) -> Result { + migrate_mint_proofs_02_to_03(db)?; + Ok(3) +} /// Mint Quote Info #[derive(Debug, Clone, Hash, PartialEq, Eq, Serialize, Deserialize)] struct V1MintQuote { @@ -97,3 +105,70 @@ fn migrate_mint_quotes_01_to_02(db: Arc) -> Result<(), Error> { Ok(()) } + +fn migrate_mint_proofs_02_to_03(db: Arc) -> Result<(), Error> { + let pending_proofs: Vec<([u8; 33], Option)>; + let spent_proofs: Vec<([u8; 33], Option)>; + + { + let read_txn = db.begin_read().map_err(Error::from)?; + let table = read_txn + .open_table(PENDING_PROOFS_TABLE) + .map_err(Error::from)?; + + pending_proofs = table + .iter() + .map_err(Error::from)? + .flatten() + .map(|(quote_id, mint_quote)| { + ( + quote_id.value(), + serde_json::from_str(mint_quote.value()).ok(), + ) + }) + .collect(); + } + { + let read_txn = db.begin_read().map_err(Error::from)?; + let table = read_txn + .open_table(SPENT_PROOFS_TABLE) + .map_err(Error::from)?; + + spent_proofs = table + .iter() + .map_err(Error::from)? + .flatten() + .map(|(quote_id, mint_quote)| { + ( + quote_id.value(), + serde_json::from_str(mint_quote.value()).ok(), + ) + }) + .collect(); + } + + let write_txn = db.begin_write().map_err(Error::from)?; + { + let mut proofs_table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?; + let mut state_table = write_txn + .open_table(PROOFS_STATE_TABLE) + .map_err(Error::from)?; + + for (y, proof) in pending_proofs { + if let Some(proof) = proof { + proofs_table.insert(y, serde_json::to_string(&proof)?.as_str())?; + state_table.insert(y, State::Pending.to_string().as_str())?; + } + } + + for (y, proof) in spent_proofs { + if let Some(proof) = proof { + proofs_table.insert(y, serde_json::to_string(&proof)?.as_str())?; + state_table.insert(y, State::Spent.to_string().as_str())?; + } + } + } + + write_txn.commit()?; + Ok(()) +} diff --git a/crates/cdk-redb/src/mint/mod.rs b/crates/cdk-redb/src/mint/mod.rs index 38958ca6..d726123d 100644 --- a/crates/cdk-redb/src/mint/mod.rs +++ b/crates/cdk-redb/src/mint/mod.rs @@ -11,9 +11,9 @@ 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, PublicKey, + BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, + State, }; -use cdk::secret::Secret; use cdk::{cdk_database, mint}; use migrations::migrate_01_to_02; use redb::{Database, ReadableTable, TableDefinition}; @@ -21,6 +21,7 @@ use tokio::sync::Mutex; use super::error::Error; use crate::migrations::migrate_00_to_01; +use crate::mint::migrations::migrate_02_to_03; mod migrations; @@ -28,15 +29,14 @@ const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new(" const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets"); const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes"); const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes"); -const PENDING_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = - TableDefinition::new("pending_proofs"); -const SPENT_PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("spent_proofs"); +const PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs"); +const PROOFS_STATE_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs_state"); const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config"); // Key is hex blinded_message B_ value is blinded_signature const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> = TableDefinition::new("blinded_signatures"); -const DATABASE_VERSION: u32 = 2; +const DATABASE_VERSION: u32 = 3; /// Mint Redbdatabase #[derive(Debug, Clone)] @@ -78,6 +78,10 @@ impl MintRedbDatabase { current_file_version = migrate_01_to_02(Arc::clone(&db))?; } + if current_file_version == 2 { + current_file_version = migrate_02_to_03(Arc::clone(&db))?; + } + if current_file_version != DATABASE_VERSION { tracing::warn!( "Database upgrade did not complete at {} current is {}", @@ -109,8 +113,8 @@ impl MintRedbDatabase { let _ = write_txn.open_table(KEYSETS_TABLE)?; let _ = write_txn.open_table(MINT_QUOTES_TABLE)?; let _ = write_txn.open_table(MELT_QUOTES_TABLE)?; - let _ = write_txn.open_table(PENDING_PROOFS_TABLE)?; - let _ = write_txn.open_table(SPENT_PROOFS_TABLE)?; + let _ = write_txn.open_table(PROOFS_TABLE)?; + let _ = write_txn.open_table(PROOFS_STATE_TABLE)?; let _ = write_txn.open_table(BLINDED_SIGNATURES)?; table.insert("db_version", DATABASE_VERSION.to_string().as_str())?; @@ -494,15 +498,13 @@ impl MintDatabase for MintRedbDatabase { Ok(()) } - async fn add_spent_proofs(&self, proofs: Vec) -> Result<(), Self::Err> { + async fn add_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> { let db = self.db.lock().await; let write_txn = db.begin_write().map_err(Error::from)?; { - let mut table = write_txn - .open_table(SPENT_PROOFS_TABLE) - .map_err(Error::from)?; + let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?; for proof in proofs { let y: PublicKey = hash_to_curve(&proof.secret.to_bytes()).map_err(Error::from)?; table @@ -518,15 +520,10 @@ impl MintDatabase for MintRedbDatabase { Ok(()) } - async fn get_spent_proofs_by_ys( - &self, - ys: &[PublicKey], - ) -> Result>, Self::Err> { + async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result>, Self::Err> { let db = self.db.lock().await; let read_txn = db.begin_read().map_err(Error::from)?; - let table = read_txn - .open_table(SPENT_PROOFS_TABLE) - .map_err(Error::from)?; + let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?; let mut proofs = Vec::with_capacity(ys.len()); @@ -542,70 +539,55 @@ impl MintDatabase for MintRedbDatabase { Ok(proofs) } - async fn add_pending_proofs(&self, proofs: Vec) -> Result<(), Self::Err> { - let db = self.db.lock().await; - - let write_txn = db.begin_write().map_err(Error::from)?; - - { - let mut table = write_txn - .open_table(PENDING_PROOFS_TABLE) - .map_err(Error::from)?; - for proof in proofs { - table - .insert( - hash_to_curve(&proof.secret.to_bytes())?.to_bytes(), - serde_json::to_string(&proof).map_err(Error::from)?.as_str(), - ) - .map_err(Error::from)?; - } - } - write_txn.commit().map_err(Error::from)?; - - Ok(()) - } - - async fn get_pending_proofs_by_ys( - &self, - ys: &[PublicKey], - ) -> Result>, Self::Err> { + async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result>, Self::Err> { let db = self.db.lock().await; let read_txn = db.begin_read().map_err(Error::from)?; let table = read_txn - .open_table(PENDING_PROOFS_TABLE) + .open_table(PROOFS_STATE_TABLE) .map_err(Error::from)?; - let mut proofs = Vec::with_capacity(ys.len()); + let mut states = Vec::with_capacity(ys.len()); for y in ys { match table.get(y.to_bytes()).map_err(Error::from)? { - Some(proof) => proofs.push(Some( - serde_json::from_str(proof.value()).map_err(Error::from)?, + Some(state) => states.push(Some( + serde_json::from_str(state.value()).map_err(Error::from)?, )), - None => proofs.push(None), + None => states.push(None), } } - Ok(proofs) + Ok(states) } - async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> { + async fn update_proofs_states( + &self, + ys: &[PublicKey], + proofs_state: State, + ) -> Result>, Self::Err> { let db = self.db.lock().await; - let write_txn = db.begin_write().map_err(Error::from)?; + let mut table = write_txn + .open_table(PROOFS_STATE_TABLE) + .map_err(Error::from)?; - { - let mut table = write_txn - .open_table(PENDING_PROOFS_TABLE) - .map_err(Error::from)?; - for secret in secrets { - let secret_hash = hash_to_curve(&secret.to_bytes()).map_err(Error::from)?; - table.remove(secret_hash.to_bytes()).map_err(Error::from)?; + let mut states = Vec::with_capacity(ys.len()); + + let state_str = serde_json::to_string(&proofs_state).map_err(Error::from)?; + + for y in ys { + match table + .insert(y.to_bytes(), state_str.as_str()) + .map_err(Error::from)? + { + Some(state) => states.push(Some( + serde_json::from_str(state.value()).map_err(Error::from)?, + )), + None => states.push(None), } } - write_txn.commit().map_err(Error::from)?; - Ok(()) + Ok(states) } async fn add_blind_signatures( diff --git a/crates/cdk-sqlite/src/mint/error.rs b/crates/cdk-sqlite/src/mint/error.rs index 9a3ed8b4..407dc91a 100644 --- a/crates/cdk-sqlite/src/mint/error.rs +++ b/crates/cdk-sqlite/src/mint/error.rs @@ -23,6 +23,9 @@ pub enum Error { /// NUT05 Error #[error(transparent)] CDKNUT05(#[from] cdk::nuts::nut05::Error), + /// NUT07 Error + #[error(transparent)] + CDKNUT07(#[from] cdk::nuts::nut07::Error), /// Secret Error #[error(transparent)] CDKSECRET(#[from] cdk::secret::Error), diff --git a/crates/cdk-sqlite/src/mint/migrations/20240718203721_allow_unspent.sql b/crates/cdk-sqlite/src/mint/migrations/20240718203721_allow_unspent.sql new file mode 100644 index 00000000..81a27629 --- /dev/null +++ b/crates/cdk-sqlite/src/mint/migrations/20240718203721_allow_unspent.sql @@ -0,0 +1,21 @@ +-- Create a new table with the updated CHECK constraint +CREATE TABLE proof_new ( + y BLOB PRIMARY KEY, + amount INTEGER NOT NULL, + keyset_id TEXT NOT NULL, + secret TEXT NOT NULL, + c BLOB NOT NULL, + witness TEXT, + state TEXT CHECK (state IN ('SPENT', 'PENDING', 'UNSPENT')) NOT NULL +); + +-- Copy the data from the old table to the new table +INSERT INTO proof_new (y, amount, keyset_id, secret, c, witness, state) +SELECT y, amount, keyset_id, secret, c, witness, state +FROM proof; + +-- Drop the old table +DROP TABLE proof; + +-- Rename the new table to the original table name +ALTER TABLE proof_new RENAME TO proof; diff --git a/crates/cdk-sqlite/src/mint/mod.rs b/crates/cdk-sqlite/src/mint/mod.rs index a89a042d..1425b68b 100644 --- a/crates/cdk-sqlite/src/mint/mod.rs +++ b/crates/cdk-sqlite/src/mint/mod.rs @@ -11,6 +11,7 @@ use cdk::mint::{MintKeySetInfo, MintQuote}; use cdk::nuts::nut05::QuoteState; use cdk::nuts::{ BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, + State, }; use cdk::secret::Secret; use cdk::{mint, Amount}; @@ -480,13 +481,12 @@ FROM keyset; .collect()) } - async fn add_spent_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> { + async fn add_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> { let mut transaction = self.pool.begin().await.map_err(Error::from)?; - for proof in proofs { - sqlx::query( + if let Err(err) = sqlx::query( r#" -INSERT OR REPLACE INTO proof +INSERT INTO proof (y, amount, keyset_id, secret, c, witness, state) VALUES (?, ?, ?, ?, ?, ?, ?); "#, @@ -497,79 +497,19 @@ VALUES (?, ?, ?, ?, ?, ?, ?); .bind(proof.secret.to_string()) .bind(proof.c.to_bytes().to_vec()) .bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap())) - .bind("SPENT") + .bind("UNSPENT") .execute(&mut transaction) .await - .map_err(Error::from)?; - } - transaction.commit().await.map_err(Error::from)?; - Ok(()) - } - async fn get_spent_proofs_by_ys( - &self, - ys: &[PublicKey], - ) -> Result>, Self::Err> { - let mut transaction = self.pool.begin().await.map_err(Error::from)?; - - let mut proofs = Vec::with_capacity(ys.len()); - for y in ys { - let rec = sqlx::query( - r#" -SELECT * -FROM proof -WHERE y=? -AND state="SPENT"; - "#, - ) - .bind(y.to_bytes().to_vec()) - .fetch_one(&mut transaction) - .await; - - match rec { - Ok(rec) => { - proofs.push(Some(sqlite_row_to_proof(rec)?)); - } - Err(err) => match err { - sqlx::Error::RowNotFound => proofs.push(None), - _ => return Err(Error::SQLX(err).into()), - }, - }; - } - - transaction.commit().await.map_err(Error::from)?; - - Ok(proofs) - } - - async fn add_pending_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> { - let mut transaction = self.pool.begin().await.map_err(Error::from)?; - for proof in proofs { - sqlx::query( - r#" -INSERT OR REPLACE INTO proof -(y, amount, keyset_id, secret, c, witness, state) -VALUES (?, ?, ?, ?, ?, ?, ?); - "#, - ) - .bind(proof.y()?.to_bytes().to_vec()) - .bind(u64::from(proof.amount) as i64) - .bind(proof.keyset_id.to_string()) - .bind(proof.secret.to_string()) - .bind(proof.c.to_bytes().to_vec()) - .bind(proof.witness.map(|w| serde_json::to_string(&w).unwrap())) - .bind("PENDING") - .execute(&mut transaction) - .await - .map_err(Error::from)?; + .map_err(Error::from) + { + tracing::debug!("Attempting to add known proof. Skipping.... {:?}", err); + } } transaction.commit().await.map_err(Error::from)?; Ok(()) } - async fn get_pending_proofs_by_ys( - &self, - ys: &[PublicKey], - ) -> Result>, Self::Err> { + async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result>, Self::Err> { let mut transaction = self.pool.begin().await.map_err(Error::from)?; let mut proofs = Vec::with_capacity(ys.len()); @@ -579,8 +519,7 @@ VALUES (?, ?, ?, ?, ?, ?, ?); r#" SELECT * FROM proof -WHERE y=? -AND state="PENDING"; +WHERE y=?; "#, ) .bind(y.to_bytes().to_vec()) @@ -600,26 +539,91 @@ AND state="PENDING"; Ok(proofs) } - async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> { + + async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result>, Self::Err> { let mut transaction = self.pool.begin().await.map_err(Error::from)?; - for secret in secrets { - sqlx::query( + + let mut states = Vec::with_capacity(ys.len()); + + for y in ys { + let rec = sqlx::query( r#" -DELETE FROM proof -WHERE secret=? -AND state="PENDING"; +SELECT state +FROM proof +WHERE y=?; "#, ) - .bind(secret.to_string()) + .bind(y.to_bytes().to_vec()) + .fetch_one(&mut transaction) + .await; + + match rec { + Ok(rec) => { + let state: String = rec.get("state"); + let state = State::from_str(&state).map_err(Error::from)?; + states.push(Some(state)); + } + Err(err) => match err { + sqlx::Error::RowNotFound => states.push(None), + _ => return Err(Error::SQLX(err).into()), + }, + }; + } + + Ok(states) + } + + async fn update_proofs_states( + &self, + ys: &[PublicKey], + proofs_state: State, + ) -> Result>, Self::Err> { + let mut transaction = self.pool.begin().await.map_err(Error::from)?; + + let mut states = Vec::with_capacity(ys.len()); + + let proofs_state = proofs_state.to_string(); + for y in ys { + let y = y.to_bytes().to_vec(); + let rec = sqlx::query( + r#" +SELECT state +FROM proof +WHERE y=?; + "#, + ) + .bind(&y) + .fetch_one(&mut transaction) + .await; + + match rec { + Ok(rec) => { + let state: String = rec.get("state"); + let state = State::from_str(&state).map_err(Error::from)?; + states.push(Some(state)); + } + Err(err) => match err { + sqlx::Error::RowNotFound => states.push(None), + _ => return Err(Error::SQLX(err).into()), + }, + }; + + sqlx::query( + r#" + UPDATE proof SET state = ? WHERE y = ? + "#, + ) + .bind(&proofs_state) + .bind(y) .execute(&mut transaction) .await .map_err(Error::from)?; } + transaction.commit().await.map_err(Error::from)?; - Ok(()) + Ok(states) } - async fn add_blind_signatures( &self, blinded_messages: &[PublicKey], diff --git a/crates/cdk/src/cdk_database/mint_memory.rs b/crates/cdk/src/cdk_database/mint_memory.rs index bd20ec1b..228aced5 100644 --- a/crates/cdk/src/cdk_database/mint_memory.rs +++ b/crates/cdk/src/cdk_database/mint_memory.rs @@ -4,15 +4,16 @@ use std::collections::HashMap; use std::sync::Arc; use async_trait::async_trait; -use tokio::sync::RwLock; +use tokio::sync::{Mutex, RwLock}; use super::{Error, MintDatabase}; use crate::dhke::hash_to_curve; use crate::mint::{self, MintKeySetInfo, MintQuote}; +use crate::nuts::nut07::State; use crate::nuts::{ - BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey, + nut07, BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, + PublicKey, }; -use crate::secret::Secret; /// Mint Memory Database #[derive(Debug, Clone)] @@ -21,8 +22,8 @@ pub struct MintMemoryDatabase { keysets: Arc>>, mint_quotes: Arc>>, melt_quotes: Arc>>, - pending_proofs: Arc>>, - spent_proofs: Arc>>, + proofs: Arc>>, + proof_state: Arc>>, blinded_signatures: Arc>>, } @@ -38,6 +39,21 @@ impl MintMemoryDatabase { spent_proofs: Proofs, blinded_signatures: HashMap<[u8; 33], BlindSignature>, ) -> Result { + let mut proofs = HashMap::new(); + let mut proof_states = HashMap::new(); + + for proof in pending_proofs { + let y = hash_to_curve(&proof.secret.to_bytes())?.to_bytes(); + proofs.insert(y, proof); + proof_states.insert(y, State::Pending); + } + + for proof in spent_proofs { + let y = hash_to_curve(&proof.secret.to_bytes())?.to_bytes(); + proofs.insert(y, proof); + proof_states.insert(y, State::Spent); + } + Ok(Self { active_keysets: Arc::new(RwLock::new(active_keysets)), keysets: Arc::new(RwLock::new( @@ -49,18 +65,8 @@ impl MintMemoryDatabase { melt_quotes: Arc::new(RwLock::new( melt_quotes.into_iter().map(|q| (q.id.clone(), q)).collect(), )), - pending_proofs: Arc::new(RwLock::new( - pending_proofs - .into_iter() - .map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p)) - .collect(), - )), - spent_proofs: Arc::new(RwLock::new( - spent_proofs - .into_iter() - .map(|p| (hash_to_curve(&p.secret.to_bytes()).unwrap().to_bytes(), p)) - .collect(), - )), + proofs: Arc::new(RwLock::new(proofs)), + proof_state: Arc::new(Mutex::new(proof_states)), blinded_signatures: Arc::new(RwLock::new(blinded_signatures)), }) } @@ -213,21 +219,18 @@ impl MintDatabase for MintMemoryDatabase { Ok(()) } - async fn add_spent_proofs(&self, spent_proofs: Proofs) -> Result<(), Self::Err> { - let mut proofs = self.spent_proofs.write().await; + async fn add_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> { + let mut db_proofs = self.proofs.write().await; - for proof in spent_proofs { + for proof in proofs { let secret_point = hash_to_curve(&proof.secret.to_bytes())?; - proofs.insert(secret_point.to_bytes(), proof); + db_proofs.insert(secret_point.to_bytes(), proof); } Ok(()) } - async fn get_spent_proofs_by_ys( - &self, - ys: &[PublicKey], - ) -> Result>, Self::Err> { - let spent_proofs = self.spent_proofs.read().await; + async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result>, Self::Err> { + let spent_proofs = self.proofs.read().await; let mut proofs = Vec::with_capacity(ys.len()); @@ -240,41 +243,34 @@ impl MintDatabase for MintMemoryDatabase { Ok(proofs) } - async fn add_pending_proofs(&self, pending_proofs: Proofs) -> Result<(), Self::Err> { - let mut proofs = self.pending_proofs.write().await; - - for proof in pending_proofs { - proofs.insert(hash_to_curve(&proof.secret.to_bytes())?.to_bytes(), proof); - } - Ok(()) - } - - async fn get_pending_proofs_by_ys( + async fn update_proofs_states( &self, ys: &[PublicKey], - ) -> Result>, Self::Err> { - let spent_proofs = self.pending_proofs.read().await; + proof_state: State, + ) -> Result>, Self::Err> { + let mut proofs_states = self.proof_state.lock().await; - let mut proofs = Vec::with_capacity(ys.len()); + let mut states = Vec::new(); for y in ys { - let proof = spent_proofs.get(&y.to_bytes()).cloned(); - - proofs.push(proof); + let state = proofs_states.insert(y.to_bytes(), proof_state); + states.push(state); } - Ok(proofs) + Ok(states) } - async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> { - let mut proofs = self.pending_proofs.write().await; + async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result>, Self::Err> { + let proofs_states = self.proof_state.lock().await; - for secret in secrets { - let secret_point = hash_to_curve(&secret.to_bytes())?; - proofs.remove(&secret_point.to_bytes()); + let mut states = Vec::new(); + + for y in ys { + let state = proofs_states.get(&y.to_bytes()).cloned(); + states.push(state); } - Ok(()) + Ok(states) } async fn add_blind_signatures( diff --git a/crates/cdk/src/cdk_database/mod.rs b/crates/cdk/src/cdk_database/mod.rs index d4356e1a..56af50eb 100644 --- a/crates/cdk/src/cdk_database/mod.rs +++ b/crates/cdk/src/cdk_database/mod.rs @@ -14,16 +14,12 @@ use crate::mint; use crate::mint::MintKeySetInfo; #[cfg(feature = "mint")] use crate::mint::MintQuote as MintMintQuote; -#[cfg(feature = "wallet")] -use crate::nuts::State; #[cfg(feature = "mint")] use crate::nuts::{BlindSignature, MeltQuoteState, MintQuoteState, Proof}; #[cfg(any(feature = "wallet", feature = "mint"))] -use crate::nuts::{CurrencyUnit, Id, Proofs, PublicKey}; +use crate::nuts::{CurrencyUnit, Id, Proofs, PublicKey, State}; #[cfg(feature = "wallet")] use crate::nuts::{KeySetInfo, Keys, MintInfo, SpendingConditions}; -#[cfg(feature = "mint")] -use crate::secret::Secret; #[cfg(feature = "wallet")] use crate::types::ProofInfo; #[cfg(feature = "wallet")] @@ -218,22 +214,17 @@ pub trait MintDatabase { async fn get_keyset_infos(&self) -> Result, Self::Err>; /// Add spent [`Proofs`] - async fn add_spent_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; - /// Get spent [`Proofs`] by ys - async fn get_spent_proofs_by_ys( - &self, - y: &[PublicKey], - ) -> Result>, Self::Err>; - - /// Add pending [`Proofs`] - async fn add_pending_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; - /// Get pending [`Proofs`] by ys - async fn get_pending_proofs_by_ys( + async fn add_proofs(&self, proof: Proofs) -> Result<(), Self::Err>; + /// Get [`Proofs`] by ys + async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result>, Self::Err>; + /// Get [`Proofs`] state + async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result>, Self::Err>; + /// Get [`Proofs`] state + async fn update_proofs_states( &self, ys: &[PublicKey], - ) -> Result>, Self::Err>; - /// Remove pending [`Proofs`] - async fn remove_pending_proofs(&self, secret: Vec<&Secret>) -> Result<(), Self::Err>; + proofs_state: State, + ) -> Result>, Self::Err>; /// Add [`BlindSignature`] async fn add_blind_signatures( diff --git a/crates/cdk/src/lib.rs b/crates/cdk/src/lib.rs index 8dfcabe9..574f4184 100644 --- a/crates/cdk/src/lib.rs +++ b/crates/cdk/src/lib.rs @@ -4,6 +4,7 @@ #![warn(rustdoc::bare_urls)] pub mod amount; +#[cfg(any(feature = "wallet", feature = "mint"))] pub mod cdk_database; #[cfg(feature = "mint")] pub mod cdk_lightning; diff --git a/crates/cdk/src/mint/mod.rs b/crates/cdk/src/mint/mod.rs index 3614ec92..1e943e7f 100644 --- a/crates/cdk/src/mint/mod.rs +++ b/crates/cdk/src/mint/mod.rs @@ -614,16 +614,19 @@ impl Mint { let proof_count = swap_request.inputs.len(); - let ys: Vec = swap_request + let input_ys: Vec = swap_request .inputs .iter() .flat_map(|p| hash_to_curve(&p.secret.to_bytes())) .collect(); - self.check_ys_unspent(&ys).await?; + self.localstore + .add_proofs(swap_request.inputs.clone()) + .await?; + self.check_ys_spendable(&input_ys, State::Pending).await?; // Check that there are no duplicate proofs in request - if ys + if input_ys .iter() .collect::>() .len() @@ -684,10 +687,6 @@ impl Mint { } } - self.localstore - .add_spent_proofs(swap_request.inputs) - .await?; - let mut promises = Vec::with_capacity(swap_request.outputs.len()); for blinded_message in swap_request.outputs.iter() { @@ -695,6 +694,10 @@ impl Mint { promises.push(blinded_signature); } + self.localstore + .update_proofs_states(&input_ys, State::Spent) + .await?; + self.localstore .add_blind_signatures( &swap_request @@ -749,31 +752,17 @@ impl Mint { &self, check_state: &CheckStateRequest, ) -> Result { - let spent_proofs = self - .localstore - .get_spent_proofs_by_ys(&check_state.ys) - .await?; - let pending_proofs = self - .localstore - .get_pending_proofs_by_ys(&check_state.ys) - .await?; + let states = self.localstore.get_proofs_states(&check_state.ys).await?; - let states = spent_proofs + let states = states .iter() - .zip(&pending_proofs) .zip(&check_state.ys) - .map(|((spent, pending), y)| { - let state = match (spent, pending) { - (None, None) => State::Unspent, - (Some(_), None) => State::Spent, - (None, Some(_)) => State::Pending, - (Some(_), Some(_)) => { - tracing::error!( - "Proof should not be both pending and spent. Assuming Spent" - ); - State::Spent - } + .map(|(state, y)| { + let state = match state { + Some(state) => *state, + None => State::Unspent, }; + ProofState { y: *y, state, @@ -787,28 +776,23 @@ impl Mint { /// Check Tokens are not spent or pending #[instrument(skip_all)] - pub async fn check_ys_unspent(&self, ys: &[PublicKey]) -> Result<(), Error> { - let pending_proofs: Proofs = self + pub async fn check_ys_spendable( + &self, + ys: &[PublicKey], + proof_state: State, + ) -> Result<(), Error> { + let proofs_state = self .localstore - .get_pending_proofs_by_ys(ys) - .await? - .into_iter() - .flatten() - .collect(); + .update_proofs_states(ys, proof_state) + .await?; - if !pending_proofs.is_empty() { + let proofs_state = proofs_state.iter().flatten().collect::>(); + + if proofs_state.contains(&State::Pending) { return Err(Error::TokenPending); } - let spent_proofs: Proofs = self - .localstore - .get_spent_proofs_by_ys(ys) - .await? - .into_iter() - .flatten() - .collect(); - - if !spent_proofs.is_empty() { + if proofs_state.contains(&State::Spent) { return Err(Error::TokenAlreadySpent); } @@ -832,7 +816,10 @@ impl Mint { return Err(Error::DuplicateProofs); } - self.check_ys_unspent(&ys).await?; + self.localstore + .add_proofs(melt_request.inputs.clone()) + .await?; + self.check_ys_spendable(&ys, State::Pending).await?; for proof in &melt_request.inputs { self.verify_proof(proof).await?; @@ -929,11 +916,6 @@ impl Mint { return Err(Error::MultipleUnits); } - // Add proofs to pending - self.localstore - .add_pending_proofs(melt_request.inputs.clone()) - .await?; - tracing::debug!("Verified melt quote: {}", melt_request.quote); Ok(quote) } @@ -943,8 +925,14 @@ impl Mint { /// The [`Proofs`] should be returned to an unspent state and the quote should be unpaid #[instrument(skip_all)] pub async fn process_unpaid_melt(&self, melt_request: &MeltBolt11Request) -> Result<(), Error> { + let input_ys: Vec = melt_request + .inputs + .iter() + .flat_map(|p| hash_to_curve(&p.secret.to_bytes())) + .collect(); + self.localstore - .remove_pending_proofs(melt_request.inputs.iter().map(|p| &p.secret).collect()) + .update_proofs_states(&input_ys, State::Unspent) .await?; self.localstore @@ -987,10 +975,6 @@ impl Mint { } } - self.localstore - .add_spent_proofs(melt_request.inputs.clone()) - .await?; - let mut change = None; if let Some(outputs) = melt_request.outputs.clone() { @@ -1038,8 +1022,14 @@ impl Mint { ); } + let input_ys: Vec = melt_request + .inputs + .iter() + .flat_map(|p| hash_to_curve(&p.secret.to_bytes())) + .collect(); + self.localstore - .remove_pending_proofs(melt_request.inputs.iter().map(|p| &p.secret).collect()) + .update_proofs_states(&input_ys, State::Spent) .await?; self.localstore