Переглянути джерело

refactor: use sproof state update

thesimplekid 7 місяців тому
батько
коміт
fb014573c1

+ 2 - 0
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]
 

+ 78 - 3
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<Database>) -> Result<u32, Error> {
     migrate_mint_quotes_01_to_02(db)?;
-
     Ok(2)
 }
+
+pub fn migrate_02_to_03(db: Arc<Database>) -> Result<u32, Error> {
+    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<Database>) -> Result<(), Error> {
 
     Ok(())
 }
+
+fn migrate_mint_proofs_02_to_03(db: Arc<Database>) -> Result<(), Error> {
+    let pending_proofs: Vec<([u8; 33], Option<Proof>)>;
+    let spent_proofs: Vec<([u8; 33], Option<Proof>)>;
+
+    {
+        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(())
+}

+ 45 - 63
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<Proof>) -> 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<Vec<Option<Proof>>, Self::Err> {
+    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, 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<Proof>) -> 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<Vec<Option<Proof>>, Self::Err> {
+    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, 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<Vec<Option<State>>, 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(

+ 3 - 0
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),

+ 21 - 0
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;

+ 57 - 53
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,28 +497,29 @@ 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)?;
+            .map_err(Error::from)
+            {
+                tracing::debug!("Attempting to add known proof. Skipping.... {:?}", err);
+            }
         }
         transaction.commit().await.map_err(Error::from)?;
+
         Ok(())
     }
-    async fn get_spent_proofs_by_ys(
-        &self,
-        ys: &[PublicKey],
-    ) -> Result<Vec<Option<Proof>>, Self::Err> {
+    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, 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";
+WHERE y=?;
         "#,
             )
             .bind(y.to_bytes().to_vec())
@@ -536,90 +537,93 @@ AND state="SPENT";
             };
         }
 
-        transaction.commit().await.map_err(Error::from)?;
-
         Ok(proofs)
     }
 
-    async fn add_pending_proofs(&self, proofs: Proofs) -> Result<(), Self::Err> {
+    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
-        for proof in proofs {
-            sqlx::query(
+
+        let mut states = Vec::with_capacity(ys.len());
+
+        for y in ys {
+            let rec = sqlx::query(
                 r#"
-INSERT OR REPLACE INTO proof
-(y, amount, keyset_id, secret, c, witness, state)
-VALUES (?, ?, ?, ?, ?, ?, ?);
+SELECT state
+FROM proof
+WHERE y=?;
         "#,
             )
-            .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)?;
+            .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()),
+                },
+            };
         }
-        transaction.commit().await.map_err(Error::from)?;
 
-        Ok(())
+        Ok(states)
     }
-    async fn get_pending_proofs_by_ys(
+
+    async fn update_proofs_states(
         &self,
         ys: &[PublicKey],
-    ) -> Result<Vec<Option<Proof>>, Self::Err> {
+        proofs_state: State,
+    ) -> Result<Vec<Option<State>>, Self::Err> {
         let mut transaction = self.pool.begin().await.map_err(Error::from)?;
 
-        let mut proofs = Vec::with_capacity(ys.len());
+        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 *
+SELECT state
 FROM proof
-WHERE y=?
-AND state="PENDING";
+WHERE y=?;
         "#,
             )
-            .bind(y.to_bytes().to_vec())
+            .bind(&y)
             .fetch_one(&mut transaction)
             .await;
 
             match rec {
                 Ok(rec) => {
-                    proofs.push(Some(sqlite_row_to_proof(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 => proofs.push(None),
+                    sqlx::Error::RowNotFound => states.push(None),
                     _ => return Err(Error::SQLX(err).into()),
                 },
             };
-        }
 
-        Ok(proofs)
-    }
-    async fn remove_pending_proofs(&self, secrets: Vec<&Secret>) -> Result<(), Self::Err> {
-        let mut transaction = self.pool.begin().await.map_err(Error::from)?;
-        for secret in secrets {
             sqlx::query(
                 r#"
-DELETE FROM proof
-WHERE secret=?
-AND state="PENDING";
+        UPDATE proof SET state = ? WHERE y = ?
         "#,
             )
-            .bind(secret.to_string())
+            .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],

+ 45 - 49
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<RwLock<HashMap<Id, MintKeySetInfo>>>,
     mint_quotes: Arc<RwLock<HashMap<String, MintQuote>>>,
     melt_quotes: Arc<RwLock<HashMap<String, mint::MeltQuote>>>,
-    pending_proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>,
-    spent_proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>,
+    proofs: Arc<RwLock<HashMap<[u8; 33], Proof>>>,
+    proof_state: Arc<Mutex<HashMap<[u8; 33], nut07::State>>>,
     blinded_signatures: Arc<RwLock<HashMap<[u8; 33], BlindSignature>>>,
 }
 
@@ -38,6 +39,21 @@ impl MintMemoryDatabase {
         spent_proofs: Proofs,
         blinded_signatures: HashMap<[u8; 33], BlindSignature>,
     ) -> Result<Self, Error> {
+        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<Vec<Option<Proof>>, Self::Err> {
-        let spent_proofs = self.spent_proofs.read().await;
+    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, 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<Vec<Option<Proof>>, Self::Err> {
-        let spent_proofs = self.pending_proofs.read().await;
+        proof_state: State,
+    ) -> Result<Vec<Option<State>>, 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<Vec<Option<State>>, 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(

+ 10 - 19
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<Vec<MintKeySetInfo>, 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<Vec<Option<Proof>>, 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<Vec<Option<Proof>>, Self::Err>;
+    /// Get [`Proofs`] state
+    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
+    /// Get [`Proofs`] state
+    async fn update_proofs_states(
         &self,
         ys: &[PublicKey],
-    ) -> Result<Vec<Option<Proof>>, Self::Err>;
-    /// Remove pending [`Proofs`]
-    async fn remove_pending_proofs(&self, secret: Vec<&Secret>) -> Result<(), Self::Err>;
+        proofs_state: State,
+    ) -> Result<Vec<Option<State>>, Self::Err>;
 
     /// Add [`BlindSignature`]
     async fn add_blind_signatures(

+ 1 - 0
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;

+ 47 - 57
crates/cdk/src/mint/mod.rs

@@ -614,16 +614,19 @@ impl Mint {
 
         let proof_count = swap_request.inputs.len();
 
-        let ys: Vec<PublicKey> = swap_request
+        let input_ys: Vec<PublicKey> = 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::<HashSet<&PublicKey>>()
             .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() {
@@ -696,6 +695,10 @@ impl Mint {
         }
 
         self.localstore
+            .update_proofs_states(&input_ys, State::Spent)
+            .await?;
+
+        self.localstore
             .add_blind_signatures(
                 &swap_request
                     .outputs
@@ -749,31 +752,17 @@ impl Mint {
         &self,
         check_state: &CheckStateRequest,
     ) -> Result<CheckStateResponse, Error> {
-        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::<HashSet<&State>>();
+
+        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<PublicKey> = 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<PublicKey> = 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