소스 검색

Split the database trait into read and transactions.

The transaction traits will encapsulate all database changes and also expect
READ-and-lock operations to read and lock records from the database for
exclusive access, thereby avoiding race conditions.

The Transaction trait expects a `rollback` operation on Drop unless the
transaction has been committed.

TODO:
 - [ ] Add a set of tests for the database operations
 - [ ] Move all INSERT, UPDATE and DELETE to their transaction traits
Cesar Rodas 1 주 전
부모
커밋
09c2ff44d6

+ 123 - 37
crates/cdk-common/src/database/mint/mod.rs

@@ -23,42 +23,92 @@ pub mod test;
 #[cfg(feature = "auth")]
 #[cfg(feature = "auth")]
 pub use auth::MintAuthDatabase;
 pub use auth::MintAuthDatabase;
 
 
+/// KeysDatabaseWriter
+#[async_trait]
+pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
+    /// Add Active Keyset
+    async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
+
+    /// Add [`MintKeySetInfo`]
+    async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
+}
+
 /// Mint Keys Database trait
 /// Mint Keys Database trait
 #[async_trait]
 #[async_trait]
 pub trait KeysDatabase {
 pub trait KeysDatabase {
     /// Mint Keys Database Error
     /// Mint Keys Database Error
     type Err: Into<Error> + From<Error>;
     type Err: Into<Error> + From<Error>;
 
 
-    /// Add Active Keyset
-    async fn set_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err>;
+    /// Beings a transaction
+    async fn begin_transaction<'a>(
+        &'a self,
+    ) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
+
     /// Get Active Keyset
     /// Get Active Keyset
     async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
     async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
+
     /// Get all Active Keyset
     /// Get all Active Keyset
     async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
     async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
-    /// Add [`MintKeySetInfo`]
-    async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err>;
+
     /// Get [`MintKeySetInfo`]
     /// Get [`MintKeySetInfo`]
     async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
     async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
+
     /// Get [`MintKeySetInfo`]s
     /// Get [`MintKeySetInfo`]s
     async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
     async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
 }
 }
 
 
-/// Mint Quote Database trait
+/// Mint Quote Database writer trait
 #[async_trait]
 #[async_trait]
-pub trait QuotesDatabase {
+pub trait QuotesTransaction<'a> {
     /// Mint Quotes Database Error
     /// Mint Quotes Database Error
     type Err: Into<Error> + From<Error>;
     type Err: Into<Error> + From<Error>;
 
 
+    /// Get [`MintMintQuote`] and lock it for update in this transaction
+    async fn get_mint_quote(&mut self, quote_id: &Uuid)
+        -> Result<Option<MintMintQuote>, Self::Err>;
     /// Add [`MintMintQuote`]
     /// Add [`MintMintQuote`]
-    async fn add_mint_quote(&self, quote: MintMintQuote) -> Result<(), Self::Err>;
-    /// Get [`MintMintQuote`]
-    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintMintQuote>, Self::Err>;
+    async fn add_mint_quote(&mut self, quote: MintMintQuote) -> Result<(), Self::Err>;
     /// Update state of [`MintMintQuote`]
     /// Update state of [`MintMintQuote`]
     async fn update_mint_quote_state(
     async fn update_mint_quote_state(
-        &self,
+        &mut self,
         quote_id: &Uuid,
         quote_id: &Uuid,
         state: MintQuoteState,
         state: MintQuoteState,
     ) -> Result<MintQuoteState, Self::Err>;
     ) -> Result<MintQuoteState, Self::Err>;
+    /// Remove [`MintMintQuote`]
+    async fn remove_mint_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err>;
+    /// Get [`mint::MeltQuote`] and lock it for update in this transaction
+    async fn get_melt_quote(
+        &mut self,
+        quote_id: &Uuid,
+    ) -> Result<Option<mint::MeltQuote>, Self::Err>;
+    /// Add [`mint::MeltQuote`]
+    async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
+    /// Update [`mint::MeltQuote`] state
+    ///
+    /// It is expected for this function to fail if the state is already set to the new state
+    async fn update_melt_quote_state(
+        &mut self,
+        quote_id: &Uuid,
+        new_state: MeltQuoteState,
+    ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
+    /// Remove [`mint::MeltQuote`]
+    async fn remove_melt_quote(&mut self, quote_id: &Uuid) -> Result<(), Self::Err>;
+    /// Get all [`MintMintQuote`]s and lock it for update in this transaction
+    async fn get_mint_quote_by_request(
+        &mut self,
+        request: &str,
+    ) -> Result<Option<MintMintQuote>, Self::Err>;
+}
+
+/// Mint Quote Database trait
+#[async_trait]
+pub trait QuotesDatabase {
+    /// Mint Quotes Database Error
+    type Err: Into<Error> + From<Error>;
+
+    /// Get [`MintMintQuote`]
+    async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintMintQuote>, Self::Err>;
+
     /// Get all [`MintMintQuote`]s
     /// Get all [`MintMintQuote`]s
     async fn get_mint_quote_by_request(
     async fn get_mint_quote_by_request(
         &self,
         &self,
@@ -76,30 +126,15 @@ pub trait QuotesDatabase {
         &self,
         &self,
         state: MintQuoteState,
         state: MintQuoteState,
     ) -> Result<Vec<MintMintQuote>, Self::Err>;
     ) -> Result<Vec<MintMintQuote>, Self::Err>;
-    /// Remove [`MintMintQuote`]
-    async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err>;
-
-    /// Add [`mint::MeltQuote`]
-    async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
     /// Get [`mint::MeltQuote`]
     /// Get [`mint::MeltQuote`]
     async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err>;
     async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err>;
-    /// Update [`mint::MeltQuote`] state
-    ///
-    /// It is expected for this function to fail if the state is already set to the new state
-    async fn update_melt_quote_state(
-        &self,
-        quote_id: &Uuid,
-        new_state: MeltQuoteState,
-    ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
     /// Get all [`mint::MeltQuote`]s
     /// Get all [`mint::MeltQuote`]s
     async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
     async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
-    /// Remove [`mint::MeltQuote`]
-    async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err>;
 }
 }
 
 
-/// Mint Proof Database trait
+/// Mint Proof Transaction trait
 #[async_trait]
 #[async_trait]
-pub trait ProofsDatabase {
+pub trait ProofsTransaction<'a> {
     /// Mint Proof Database Error
     /// Mint Proof Database Error
     type Err: Into<Error> + From<Error>;
     type Err: Into<Error> + From<Error>;
 
 
@@ -107,7 +142,21 @@ pub trait ProofsDatabase {
     ///
     ///
     /// Adds proofs to the database. The database should error if the proof already exits, with a
     /// Adds proofs to the database. The database should error if the proof already exits, with a
     /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
     /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
-    async fn add_proofs(&self, proof: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err>;
+    async fn add_proofs(&mut self, proof: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err>;
+    /// Updates the proofs to a given states and return the previous states
+    async fn update_proofs_states(
+        &mut self,
+        ys: &[PublicKey],
+        proofs_state: State,
+    ) -> Result<Vec<Option<State>>, Self::Err>;
+}
+
+/// Mint Proof Database trait
+#[async_trait]
+pub trait ProofsDatabase {
+    /// Mint Proof Database Error
+    type Err: Into<Error> + From<Error>;
+
     /// Remove [`Proofs`]
     /// Remove [`Proofs`]
     async fn remove_proofs(
     async fn remove_proofs(
         &self,
         &self,
@@ -120,12 +169,6 @@ pub trait ProofsDatabase {
     async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err>;
     async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err>;
     /// Get [`Proofs`] state
     /// Get [`Proofs`] state
     async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
     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],
-        proofs_state: State,
-    ) -> Result<Vec<Option<State>>, Self::Err>;
     /// Get [`Proofs`] by state
     /// Get [`Proofs`] by state
     async fn get_proofs_by_keyset_id(
     async fn get_proofs_by_keyset_id(
         &self,
         &self,
@@ -134,18 +177,32 @@ pub trait ProofsDatabase {
 }
 }
 
 
 #[async_trait]
 #[async_trait]
-/// Mint Signatures Database trait
-pub trait SignaturesDatabase {
+/// Mint Signatures Transaction trait
+pub trait SignaturesTransaction<'a> {
     /// Mint Signature Database Error
     /// Mint Signature Database Error
     type Err: Into<Error> + From<Error>;
     type Err: Into<Error> + From<Error>;
 
 
     /// Add [`BlindSignature`]
     /// Add [`BlindSignature`]
     async fn add_blind_signatures(
     async fn add_blind_signatures(
-        &self,
+        &mut self,
         blinded_messages: &[PublicKey],
         blinded_messages: &[PublicKey],
         blind_signatures: &[BlindSignature],
         blind_signatures: &[BlindSignature],
         quote_id: Option<Uuid>,
         quote_id: Option<Uuid>,
     ) -> Result<(), Self::Err>;
     ) -> Result<(), Self::Err>;
+
+    /// Get [`BlindSignature`]s
+    async fn get_blind_signatures(
+        &mut self,
+        blinded_messages: &[PublicKey],
+    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
+}
+
+#[async_trait]
+/// Mint Signatures Database trait
+pub trait SignaturesDatabase {
+    /// Mint Signature Database Error
+    type Err: Into<Error> + From<Error>;
+
     /// Get [`BlindSignature`]s
     /// Get [`BlindSignature`]s
     async fn get_blind_signatures(
     async fn get_blind_signatures(
         &self,
         &self,
@@ -163,13 +220,42 @@ pub trait SignaturesDatabase {
     ) -> Result<Vec<BlindSignature>, Self::Err>;
     ) -> Result<Vec<BlindSignature>, Self::Err>;
 }
 }
 
 
+#[async_trait]
+/// Commit and Rollback
+pub trait DbTransactionFinalizer {
+    /// Mint Signature Database Error
+    type Err: Into<Error> + From<Error>;
+
+    /// Commits all the changes into the database
+    async fn commit(self: Box<Self>) -> Result<(), Self::Err>;
+
+    /// Rollbacks the write transaction
+    async fn rollback(self: Box<Self>) -> Result<(), Self::Err>;
+}
+
+/// Base database writer
+#[async_trait]
+pub trait Transaction<'a, Error>:
+    DbTransactionFinalizer<Err = Error>
+    + QuotesTransaction<'a, Err = Error>
+    + SignaturesTransaction<'a, Err = Error>
+    + ProofsTransaction<'a, Err = Error>
+{
+}
+
 /// Mint Database trait
 /// Mint Database trait
 #[async_trait]
 #[async_trait]
 pub trait Database<Error>:
 pub trait Database<Error>:
     QuotesDatabase<Err = Error> + ProofsDatabase<Err = Error> + SignaturesDatabase<Err = Error>
     QuotesDatabase<Err = Error> + ProofsDatabase<Err = Error> + SignaturesDatabase<Err = Error>
 {
 {
+    /// Beings a transaction
+    async fn begin_transaction<'a>(
+        &'a self,
+    ) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
+
     /// Set [`MintInfo`]
     /// Set [`MintInfo`]
     async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error>;
     async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), Error>;
+
     /// Get [`MintInfo`]
     /// Get [`MintInfo`]
     async fn get_mint_info(&self) -> Result<MintInfo, Error>;
     async fn get_mint_info(&self) -> Result<MintInfo, Error>;
 
 

+ 18 - 7
crates/cdk-common/src/database/mint/test.rs

@@ -2,17 +2,21 @@
 //!
 //!
 //! This set is generic and checks the default and expected behaviour for a mint database
 //! This set is generic and checks the default and expected behaviour for a mint database
 //! implementation
 //! implementation
-use std::fmt::Debug;
 use std::str::FromStr;
 use std::str::FromStr;
 
 
 use cashu::secret::Secret;
 use cashu::secret::Secret;
 use cashu::{Amount, CurrencyUnit, SecretKey};
 use cashu::{Amount, CurrencyUnit, SecretKey};
 
 
 use super::*;
 use super::*;
+use crate::database;
 use crate::mint::MintKeySetInfo;
 use crate::mint::MintKeySetInfo;
 
 
 #[inline]
 #[inline]
-async fn setup_keyset<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>(db: &DB) -> Id {
+
+async fn setup_keyset<DB>(db: &DB) -> Id
+where
+    DB: KeysDatabase<Err = database::Error>,
+{
     let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
     let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
     let keyset_info = MintKeySetInfo {
     let keyset_info = MintKeySetInfo {
         id: keyset_id,
         id: keyset_id,
@@ -25,12 +29,17 @@ async fn setup_keyset<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>(db: &DB
         max_order: 32,
         max_order: 32,
         input_fee_ppk: 0,
         input_fee_ppk: 0,
     };
     };
-    db.add_keyset_info(keyset_info).await.unwrap();
+    let mut writer = db.begin_transaction().await.expect("db.begin()");
+    writer.add_keyset_info(keyset_info).await.unwrap();
+    writer.commit().await.expect("commit()");
     keyset_id
     keyset_id
 }
 }
 
 
 /// State transition test
 /// State transition test
-pub async fn state_transition<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>(db: DB) {
+pub async fn state_transition<DB>(db: DB)
+where
+    DB: Database<database::Error> + KeysDatabase<Err = database::Error>,
+{
     let keyset_id = setup_keyset(&db).await;
     let keyset_id = setup_keyset(&db).await;
 
 
     let proofs = vec![
     let proofs = vec![
@@ -53,19 +62,21 @@ pub async fn state_transition<E: Debug, DB: Database<E> + KeysDatabase<Err = E>>
     ];
     ];
 
 
     // Add proofs to database
     // Add proofs to database
-    db.add_proofs(proofs.clone(), None).await.unwrap();
+    let mut tx = Database::begin_transaction(&db).await.unwrap();
+    tx.add_proofs(proofs.clone(), None).await.unwrap();
 
 
     // Mark one proof as `pending`
     // Mark one proof as `pending`
-    assert!(db
+    assert!(tx
         .update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
         .update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
         .await
         .await
         .is_ok());
         .is_ok());
 
 
     // Attempt to select the `pending` proof, as `pending` again (which should fail)
     // Attempt to select the `pending` proof, as `pending` again (which should fail)
-    assert!(db
+    assert!(tx
         .update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
         .update_proofs_states(&[proofs[0].y().unwrap()], State::Pending)
         .await
         .await
         .is_err());
         .is_err());
+    tx.commit().await.unwrap();
 }
 }
 
 
 /// Unit test that is expected to be passed for a correct database implementation
 /// Unit test that is expected to be passed for a correct database implementation

+ 5 - 2
crates/cdk-common/src/database/mod.rs

@@ -9,9 +9,12 @@ mod wallet;
 pub use mint::MintAuthDatabase;
 pub use mint::MintAuthDatabase;
 #[cfg(feature = "mint")]
 #[cfg(feature = "mint")]
 pub use mint::{
 pub use mint::{
-    Database as MintDatabase, KeysDatabase as MintKeysDatabase,
-    ProofsDatabase as MintProofsDatabase, QuotesDatabase as MintQuotesDatabase,
+    Database as MintDatabase, DbTransactionFinalizer as MintDbWriterFinalizer,
+    KeysDatabase as MintKeysDatabase, KeysDatabaseTransaction as MintKeyDatabaseTransaction,
+    ProofsDatabase as MintProofsDatabase, ProofsTransaction as MintProofsTransaction,
+    QuotesDatabase as MintQuotesDatabase, QuotesTransaction as MintQuotesTransaction,
     SignaturesDatabase as MintSignaturesDatabase,
     SignaturesDatabase as MintSignaturesDatabase,
+    SignaturesTransaction as MintSignatureTransaction, Transaction as MintTransaction,
 };
 };
 #[cfg(feature = "wallet")]
 #[cfg(feature = "wallet")]
 pub use wallet::Database as WalletDatabase;
 pub use wallet::Database as WalletDatabase;

+ 8 - 5
crates/cdk-signatory/src/common.rs

@@ -25,13 +25,14 @@ pub async fn init_keysets(
     // Get keysets info from DB
     // Get keysets info from DB
     let keysets_infos = localstore.get_keyset_infos().await?;
     let keysets_infos = localstore.get_keyset_infos().await?;
 
 
+    let mut tx = localstore.begin_transaction().await.expect("begin");
     if !keysets_infos.is_empty() {
     if !keysets_infos.is_empty() {
         tracing::debug!("Setting all saved keysets to inactive");
         tracing::debug!("Setting all saved keysets to inactive");
         for keyset in keysets_infos.clone() {
         for keyset in keysets_infos.clone() {
             // Set all to in active
             // Set all to in active
             let mut keyset = keyset;
             let mut keyset = keyset;
             keyset.active = false;
             keyset.active = false;
-            localstore.add_keyset_info(keyset).await?;
+            tx.add_keyset_info(keyset).await?;
         }
         }
 
 
         let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
         let keysets_by_unit: HashMap<CurrencyUnit, Vec<MintKeySetInfo>> =
@@ -74,9 +75,9 @@ pub async fn init_keysets(
                     active_keysets.insert(id, keyset);
                     active_keysets.insert(id, keyset);
                     let mut keyset_info = highest_index_keyset;
                     let mut keyset_info = highest_index_keyset;
                     keyset_info.active = true;
                     keyset_info.active = true;
-                    localstore.add_keyset_info(keyset_info).await?;
+                    tx.add_keyset_info(keyset_info).await?;
                     active_keyset_units.push(unit.clone());
                     active_keyset_units.push(unit.clone());
-                    localstore.set_active_keyset(unit, id).await?;
+                    tx.set_active_keyset(unit, id).await?;
                 } else {
                 } else {
                     // Check to see if there are not keysets by this unit
                     // Check to see if there are not keysets by this unit
                     let derivation_path_index = if keysets.is_empty() {
                     let derivation_path_index = if keysets.is_empty() {
@@ -104,8 +105,8 @@ pub async fn init_keysets(
                     );
                     );
 
 
                     let id = keyset_info.id;
                     let id = keyset_info.id;
-                    localstore.add_keyset_info(keyset_info).await?;
-                    localstore.set_active_keyset(unit.clone(), id).await?;
+                    tx.add_keyset_info(keyset_info).await?;
+                    tx.set_active_keyset(unit.clone(), id).await?;
                     active_keysets.insert(id, keyset);
                     active_keysets.insert(id, keyset);
                     active_keyset_units.push(unit.clone());
                     active_keyset_units.push(unit.clone());
                 };
                 };
@@ -113,6 +114,8 @@ pub async fn init_keysets(
         }
         }
     }
     }
 
 
+    tx.commit().await.expect("commit");
+
     Ok((active_keysets, active_keyset_units))
     Ok((active_keysets, active_keyset_units))
 }
 }
 
 

+ 10 - 4
crates/cdk-signatory/src/db_signatory.rs

@@ -52,6 +52,8 @@ impl DbSignatory {
         )
         )
         .await?;
         .await?;
 
 
+        let mut tx = localstore.begin_transaction().await?;
+
         // Create new keysets for supported units that aren't covered by the current keysets
         // Create new keysets for supported units that aren't covered by the current keysets
         for (unit, (fee, max_order)) in supported_units {
         for (unit, (fee, max_order)) in supported_units {
             if !active_keyset_units.contains(&unit) {
             if !active_keyset_units.contains(&unit) {
@@ -75,12 +77,14 @@ impl DbSignatory {
                 );
                 );
 
 
                 let id = keyset_info.id;
                 let id = keyset_info.id;
-                localstore.add_keyset_info(keyset_info).await?;
-                localstore.set_active_keyset(unit, id).await?;
+                tx.add_keyset_info(keyset_info).await?;
+                tx.set_active_keyset(unit, id).await?;
                 active_keysets.insert(id, keyset);
                 active_keysets.insert(id, keyset);
             }
             }
         }
         }
 
 
+        tx.commit().await?;
+
         let keys = Self {
         let keys = Self {
             keysets: Default::default(),
             keysets: Default::default(),
             active_keysets: Default::default(),
             active_keysets: Default::default(),
@@ -242,8 +246,10 @@ impl Signatory for DbSignatory {
             None,
             None,
         );
         );
         let id = info.id;
         let id = info.id;
-        self.localstore.add_keyset_info(info.clone()).await?;
-        self.localstore.set_active_keyset(args.unit, id).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.add_keyset_info(info.clone()).await?;
+        tx.set_active_keyset(args.unit, id).await?;
+        tx.commit().await?;
 
 
         self.reload_keys_from_db().await?;
         self.reload_keys_from_db().await?;
 
 

+ 12 - 9
crates/cdk-sqlite/src/mint/memory.rs

@@ -1,9 +1,7 @@
 //! In-memory database that is provided by the `cdk-sqlite` crate, mainly for testing purposes.
 //! In-memory database that is provided by the `cdk-sqlite` crate, mainly for testing purposes.
 use std::collections::HashMap;
 use std::collections::HashMap;
 
 
-use cdk_common::database::{
-    self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
-};
+use cdk_common::database::{self, MintDatabase, MintKeysDatabase};
 use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
 use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
 use cdk_common::nuts::{CurrencyUnit, Id, Proofs};
 use cdk_common::nuts::{CurrencyUnit, Id, Proofs};
 use cdk_common::MintInfo;
 use cdk_common::MintInfo;
@@ -31,26 +29,31 @@ pub async fn new_with_state(
     mint_info: MintInfo,
     mint_info: MintInfo,
 ) -> Result<MintSqliteDatabase, database::Error> {
 ) -> Result<MintSqliteDatabase, database::Error> {
     let db = empty().await?;
     let db = empty().await?;
+    let mut tx = MintKeysDatabase::begin_transaction(&db).await?;
 
 
     for active_keyset in active_keysets {
     for active_keyset in active_keysets {
-        db.set_active_keyset(active_keyset.0, active_keyset.1)
+        tx.set_active_keyset(active_keyset.0, active_keyset.1)
             .await?;
             .await?;
     }
     }
 
 
     for keyset in keysets {
     for keyset in keysets {
-        db.add_keyset_info(keyset).await?;
+        tx.add_keyset_info(keyset).await?;
     }
     }
+    tx.commit().await?;
+
+    let mut tx = MintDatabase::begin_transaction(&db).await?;
 
 
     for quote in mint_quotes {
     for quote in mint_quotes {
-        db.add_mint_quote(quote).await?;
+        tx.add_mint_quote(quote).await?;
     }
     }
 
 
     for quote in melt_quotes {
     for quote in melt_quotes {
-        db.add_melt_quote(quote).await?;
+        tx.add_melt_quote(quote).await?;
     }
     }
 
 
-    db.add_proofs(pending_proofs, None).await?;
-    db.add_proofs(spent_proofs, None).await?;
+    tx.add_proofs(pending_proofs, None).await?;
+    tx.add_proofs(spent_proofs, None).await?;
+    tx.commit().await?;
 
 
     db.set_mint_info(mint_info).await?;
     db.set_mint_info(mint_info).await?;
 
 

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 500 - 351
crates/cdk-sqlite/src/mint/mod.rs


+ 1 - 1
crates/cdk/src/lib.rs

@@ -13,7 +13,7 @@ pub mod cdk_database {
     #[cfg(feature = "mint")]
     #[cfg(feature = "mint")]
     pub use cdk_common::database::{
     pub use cdk_common::database::{
         MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
         MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
-        MintSignaturesDatabase,
+        MintSignaturesDatabase, MintTransaction,
     };
     };
 }
 }
 
 

+ 13 - 10
crates/cdk/src/mint/check_spendable.rs

@@ -26,10 +26,13 @@ impl Mint {
             }
             }
         }
         }
 
 
+        let mut tx = self.localstore.begin_transaction().await?;
         for (state, ys) in ys_by_state {
         for (state, ys) in ys_by_state {
-            self.localstore.update_proofs_states(&ys, state).await?;
+            tx.update_proofs_states(&ys, state).await?;
         }
         }
 
 
+        tx.commit().await?;
+
         self.localstore.remove_proofs(&unknown_proofs, None).await?;
         self.localstore.remove_proofs(&unknown_proofs, None).await?;
 
 
         Ok(())
         Ok(())
@@ -75,18 +78,18 @@ impl Mint {
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn check_ys_spendable(
     pub async fn check_ys_spendable(
         &self,
         &self,
+        tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
         ys: &[PublicKey],
         ys: &[PublicKey],
         proof_state: State,
         proof_state: State,
     ) -> Result<(), Error> {
     ) -> Result<(), Error> {
-        let original_proofs_state =
-            match self.localstore.update_proofs_states(ys, proof_state).await {
-                Ok(states) => states,
-                Err(cdk_database::Error::AttemptUpdateSpentProof)
-                | Err(cdk_database::Error::AttemptRemoveSpentProof) => {
-                    return Err(Error::TokenAlreadySpent)
-                }
-                Err(err) => return Err(err.into()),
-            };
+        let original_proofs_state = match tx.update_proofs_states(ys, proof_state).await {
+            Ok(states) => states,
+            Err(cdk_database::Error::AttemptUpdateSpentProof)
+            | Err(cdk_database::Error::AttemptRemoveSpentProof) => {
+                return Err(Error::TokenAlreadySpent)
+            }
+            Err(err) => return Err(err.into()),
+        };
 
 
         assert!(ys.len() == original_proofs_state.len());
         assert!(ys.len() == original_proofs_state.len());
 
 

+ 56 - 64
crates/cdk/src/mint/issue/issue_nut04.rs

@@ -105,7 +105,9 @@ impl Mint {
             create_invoice_response.request_lookup_id,
             create_invoice_response.request_lookup_id,
         );
         );
 
 
-        self.localstore.add_mint_quote(quote.clone()).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.add_mint_quote(quote.clone()).await?;
+        tx.commit().await?;
 
 
         let quote: MintQuoteBolt11Response<Uuid> = quote.into();
         let quote: MintQuoteBolt11Response<Uuid> = quote.into();
 
 
@@ -121,8 +123,8 @@ impl Mint {
         &self,
         &self,
         quote_id: &Uuid,
         quote_id: &Uuid,
     ) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
     ) -> Result<MintQuoteBolt11Response<Uuid>, Error> {
-        let quote = self
-            .localstore
+        let mut tx = self.localstore.begin_transaction().await?;
+        let mut mint_quote = tx
             .get_mint_quote(quote_id)
             .get_mint_quote(quote_id)
             .await?
             .await?
             .ok_or(Error::UnknownQuote)?;
             .ok_or(Error::UnknownQuote)?;
@@ -130,27 +132,28 @@ impl Mint {
         // Since the pending state is not part of the NUT it should not be part of the
         // Since the pending state is not part of the NUT it should not be part of the
         // response. In practice the wallet should not be checking the state of
         // response. In practice the wallet should not be checking the state of
         // a quote while waiting for the mint response.
         // a quote while waiting for the mint response.
-        let state = match quote.state {
-            MintQuoteState::Pending => MintQuoteState::Paid,
-            MintQuoteState::Unpaid => self.check_mint_quote_paid(quote_id).await?,
-            s => s,
-        };
+        if mint_quote.state == MintQuoteState::Unpaid {
+            self.check_mint_quote_paid(&mut tx, &mut mint_quote).await?;
+            tx.commit().await?;
+        }
 
 
         Ok(MintQuoteBolt11Response {
         Ok(MintQuoteBolt11Response {
-            quote: quote.id,
-            request: quote.request,
-            state,
-            expiry: Some(quote.expiry),
-            pubkey: quote.pubkey,
-            amount: Some(quote.amount),
-            unit: Some(quote.unit.clone()),
+            quote: mint_quote.id,
+            request: mint_quote.request,
+            state: mint_quote.state,
+            expiry: Some(mint_quote.expiry),
+            pubkey: mint_quote.pubkey,
+            amount: Some(mint_quote.amount),
+            unit: Some(mint_quote.unit.clone()),
         })
         })
     }
     }
 
 
     /// Update mint quote
     /// Update mint quote
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn update_mint_quote(&self, quote: MintQuote) -> Result<(), Error> {
     pub async fn update_mint_quote(&self, quote: MintQuote) -> Result<(), Error> {
-        self.localstore.add_mint_quote(quote).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.add_mint_quote(quote).await?;
+        tx.commit().await?;
         Ok(())
         Ok(())
     }
     }
 
 
@@ -186,7 +189,9 @@ impl Mint {
     /// Remove mint quote
     /// Remove mint quote
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
     pub async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
-        self.localstore.remove_mint_quote(quote_id).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.remove_mint_quote(quote_id).await?;
+        tx.commit().await?;
 
 
         Ok(())
         Ok(())
     }
     }
@@ -215,9 +220,10 @@ impl Mint {
             mint_quote.id
             mint_quote.id
         );
         );
         if mint_quote.state != MintQuoteState::Issued && mint_quote.state != MintQuoteState::Paid {
         if mint_quote.state != MintQuoteState::Issued && mint_quote.state != MintQuoteState::Paid {
-            self.localstore
-                .update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
+            let mut tx = self.localstore.begin_transaction().await?;
+            tx.update_mint_quote_state(&mint_quote.id, MintQuoteState::Paid)
                 .await?;
                 .await?;
+            tx.commit().await?;
         } else {
         } else {
             tracing::debug!(
             tracing::debug!(
                 "{} Quote already {} continuing",
                 "{} Quote already {} continuing",
@@ -238,39 +244,25 @@ impl Mint {
         &self,
         &self,
         mint_request: MintRequest<Uuid>,
         mint_request: MintRequest<Uuid>,
     ) -> Result<MintResponse, Error> {
     ) -> Result<MintResponse, Error> {
-        let mint_quote = self
-            .localstore
+        let mut tx = self.localstore.begin_transaction().await?;
+
+        let mut mint_quote = tx
             .get_mint_quote(&mint_request.quote)
             .get_mint_quote(&mint_request.quote)
             .await?
             .await?
             .ok_or(Error::UnknownQuote)?;
             .ok_or(Error::UnknownQuote)?;
 
 
-        let state = self
-            .localstore
-            .update_mint_quote_state(&mint_request.quote, MintQuoteState::Pending)
-            .await?;
-
-        let state = if state == MintQuoteState::Unpaid {
-            self.check_mint_quote_paid(&mint_quote.id).await?
-        } else {
-            state
-        };
+        if mint_quote.state == MintQuoteState::Unpaid {
+            self.check_mint_quote_paid(&mut tx, &mut mint_quote).await?
+        }
 
 
-        match state {
+        match mint_quote.state {
             MintQuoteState::Unpaid => {
             MintQuoteState::Unpaid => {
-                let _state = self
-                    .localstore
-                    .update_mint_quote_state(&mint_request.quote, MintQuoteState::Unpaid)
-                    .await?;
                 return Err(Error::UnpaidQuote);
                 return Err(Error::UnpaidQuote);
             }
             }
             MintQuoteState::Pending => {
             MintQuoteState::Pending => {
                 return Err(Error::PendingQuote);
                 return Err(Error::PendingQuote);
             }
             }
             MintQuoteState::Issued => {
             MintQuoteState::Issued => {
-                let _state = self
-                    .localstore
-                    .update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
-                    .await?;
                 return Err(Error::IssuedQuote);
                 return Err(Error::IssuedQuote);
             }
             }
             MintQuoteState::Paid => (),
             MintQuoteState::Paid => (),
@@ -282,17 +274,17 @@ impl Mint {
             mint_request.verify_signature(pubkey)?;
             mint_request.verify_signature(pubkey)?;
         }
         }
 
 
-        let Verification { amount, unit } = match self.verify_outputs(&mint_request.outputs).await {
-            Ok(verification) => verification,
-            Err(err) => {
-                tracing::debug!("Could not verify mint outputs");
-                self.localstore
-                    .update_mint_quote_state(&mint_request.quote, MintQuoteState::Paid)
-                    .await?;
-
-                return Err(err);
-            }
-        };
+        let Verification { amount, unit } =
+            match self.verify_outputs(&mut tx, &mint_request.outputs).await {
+                Ok(verification) => verification,
+                Err(err) => {
+                    tracing::debug!("Could not verify mint outputs");
+                    tx.update_mint_quote_state(&mint_request.quote, MintQuoteState::Paid)
+                        .await?;
+                    tx.commit().await?;
+                    return Err(err);
+                }
+            };
 
 
         // We check the total value of blinded messages == mint quote
         // We check the total value of blinded messages == mint quote
         if amount != mint_quote.amount {
         if amount != mint_quote.amount {
@@ -313,21 +305,21 @@ impl Mint {
             blind_signatures.push(blind_signature);
             blind_signatures.push(blind_signature);
         }
         }
 
 
-        self.localstore
-            .add_blind_signatures(
-                &mint_request
-                    .outputs
-                    .iter()
-                    .map(|p| p.blinded_secret)
-                    .collect::<Vec<PublicKey>>(),
-                &blind_signatures,
-                Some(mint_request.quote),
-            )
+        tx.add_blind_signatures(
+            &mint_request
+                .outputs
+                .iter()
+                .map(|p| p.blinded_secret)
+                .collect::<Vec<PublicKey>>(),
+            &blind_signatures,
+            Some(mint_request.quote),
+        )
+        .await?;
+
+        tx.update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
             .await?;
             .await?;
 
 
-        self.localstore
-            .update_mint_quote_state(&mint_request.quote, MintQuoteState::Issued)
-            .await?;
+        tx.commit().await?;
 
 
         self.pubsub_manager
         self.pubsub_manager
             .mint_quote_bolt11_status(mint_quote, MintQuoteState::Issued);
             .mint_quote_bolt11_status(mint_quote, MintQuoteState::Issued);

+ 9 - 12
crates/cdk/src/mint/ln.rs

@@ -1,19 +1,18 @@
 use cdk_common::common::PaymentProcessorKey;
 use cdk_common::common::PaymentProcessorKey;
+use cdk_common::database::{self, MintTransaction};
+use cdk_common::mint::MintQuote;
 use cdk_common::MintQuoteState;
 use cdk_common::MintQuoteState;
 
 
 use super::Mint;
 use super::Mint;
-use crate::mint::Uuid;
 use crate::Error;
 use crate::Error;
 
 
 impl Mint {
 impl Mint {
     /// Check the status of an ln payment for a quote
     /// Check the status of an ln payment for a quote
-    pub async fn check_mint_quote_paid(&self, quote_id: &Uuid) -> Result<MintQuoteState, Error> {
-        let mut quote = self
-            .localstore
-            .get_mint_quote(quote_id)
-            .await?
-            .ok_or(Error::UnknownQuote)?;
-
+    pub async fn check_mint_quote_paid(
+        &self,
+        tx: &mut Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
+        quote: &mut MintQuote,
+    ) -> Result<(), Error> {
         let ln = match self.ln.get(&PaymentProcessorKey::new(
         let ln = match self.ln.get(&PaymentProcessorKey::new(
             quote.unit.clone(),
             quote.unit.clone(),
             cdk_common::PaymentMethod::Bolt11,
             cdk_common::PaymentMethod::Bolt11,
@@ -31,9 +30,7 @@ impl Mint {
             .await?;
             .await?;
 
 
         if ln_status != quote.state && quote.state != MintQuoteState::Issued {
         if ln_status != quote.state && quote.state != MintQuoteState::Issued {
-            self.localstore
-                .update_mint_quote_state(quote_id, ln_status)
-                .await?;
+            tx.update_mint_quote_state(&quote.id, ln_status).await?;
 
 
             quote.state = ln_status;
             quote.state = ln_status;
 
 
@@ -41,6 +38,6 @@ impl Mint {
                 .mint_quote_bolt11_status(quote.clone(), ln_status);
                 .mint_quote_bolt11_status(quote.clone(), ln_status);
         }
         }
 
 
-        Ok(quote.state)
+        Ok(())
     }
     }
 }
 }

+ 57 - 108
crates/cdk/src/mint/melt.rs

@@ -1,6 +1,7 @@
 use std::str::FromStr;
 use std::str::FromStr;
 
 
 use anyhow::bail;
 use anyhow::bail;
+use cdk_common::database::{self, MintTransaction};
 use cdk_common::nut00::ProofsMethods;
 use cdk_common::nut00::ProofsMethods;
 use cdk_common::nut05::MeltMethodOptions;
 use cdk_common::nut05::MeltMethodOptions;
 use cdk_common::MeltOptions;
 use cdk_common::MeltOptions;
@@ -174,7 +175,9 @@ impl Mint {
             payment_quote.request_lookup_id
             payment_quote.request_lookup_id
         );
         );
 
 
-        self.localstore.add_melt_quote(quote.clone()).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.add_melt_quote(quote.clone()).await?;
+        tx.commit().await?;
 
 
         Ok(quote.into())
         Ok(quote.into())
     }
     }
@@ -215,7 +218,9 @@ impl Mint {
     /// Update melt quote
     /// Update melt quote
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn update_melt_quote(&self, quote: MeltQuote) -> Result<(), Error> {
     pub async fn update_melt_quote(&self, quote: MeltQuote) -> Result<(), Error> {
-        self.localstore.add_melt_quote(quote).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.add_melt_quote(quote).await?;
+        tx.commit().await?;
         Ok(())
         Ok(())
     }
     }
 
 
@@ -229,7 +234,9 @@ impl Mint {
     /// Remove melt quote
     /// Remove melt quote
     #[instrument(skip(self))]
     #[instrument(skip(self))]
     pub async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
     pub async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Error> {
-        self.localstore.remove_melt_quote(quote_id).await?;
+        let mut tx = self.localstore.begin_transaction().await?;
+        tx.remove_melt_quote(quote_id).await?;
+        tx.commit().await?;
 
 
         Ok(())
         Ok(())
     }
     }
@@ -295,10 +302,10 @@ impl Mint {
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn verify_melt_request(
     pub async fn verify_melt_request(
         &self,
         &self,
+        tx: &mut Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
         melt_request: &MeltRequest<Uuid>,
         melt_request: &MeltRequest<Uuid>,
     ) -> Result<MeltQuote, Error> {
     ) -> Result<MeltQuote, Error> {
-        let (state, quote) = self
-            .localstore
+        let (state, quote) = tx
             .update_melt_quote_state(melt_request.quote(), MeltQuoteState::Pending)
             .update_melt_quote_state(melt_request.quote(), MeltQuoteState::Pending)
             .await?;
             .await?;
 
 
@@ -341,8 +348,7 @@ impl Mint {
             ));
             ));
         }
         }
 
 
-        if let Some(err) = self
-            .localstore
+        if let Some(err) = tx
             .add_proofs(melt_request.inputs().clone(), None)
             .add_proofs(melt_request.inputs().clone(), None)
             .await
             .await
             .err()
             .err()
@@ -356,7 +362,8 @@ impl Mint {
             };
             };
         }
         }
 
 
-        self.check_ys_spendable(&input_ys, State::Pending).await?;
+        self.check_ys_spendable(tx, &input_ys, State::Pending)
+            .await?;
 
 
         for proof in melt_request.inputs() {
         for proof in melt_request.inputs() {
             self.pubsub_manager
             self.pubsub_manager
@@ -372,7 +379,7 @@ impl Mint {
                 let Verification {
                 let Verification {
                     amount: _,
                     amount: _,
                     unit: output_unit,
                     unit: output_unit,
-                } = self.verify_outputs(outputs).await?;
+                } = self.verify_outputs(tx, outputs).await?;
 
 
                 ensure_cdk!(input_unit == output_unit, Error::UnsupportedUnit);
                 ensure_cdk!(input_unit == output_unit, Error::UnsupportedUnit);
             }
             }
@@ -382,35 +389,6 @@ impl Mint {
         Ok(quote)
         Ok(quote)
     }
     }
 
 
-    /// Process unpaid melt request
-    /// In the event that a melt request fails and the lighthing payment is not
-    /// made 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: &MeltRequest<Uuid>) -> Result<(), Error> {
-        let input_ys = melt_request.inputs().ys()?;
-
-        self.localstore
-            .remove_proofs(&input_ys, Some(*melt_request.quote()))
-            .await?;
-
-        self.localstore
-            .update_melt_quote_state(melt_request.quote(), MeltQuoteState::Unpaid)
-            .await?;
-
-        if let Ok(Some(quote)) = self.localstore.get_melt_quote(melt_request.quote()).await {
-            self.pubsub_manager
-                .melt_quote_status(quote, None, None, MeltQuoteState::Unpaid);
-        }
-
-        for public_key in input_ys {
-            self.pubsub_manager
-                .proof_state((public_key, State::Unspent));
-        }
-
-        Ok(())
-    }
-
     /// Melt Bolt11
     /// Melt Bolt11
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn melt_bolt11(
     pub async fn melt_bolt11(
@@ -439,40 +417,30 @@ impl Mint {
             }
             }
         }
         }
 
 
-        let quote = match self.verify_melt_request(melt_request).await {
-            Ok(quote) => quote,
-            Err(err) => {
+        let mut tx = self.localstore.begin_transaction().await?;
+
+        let quote = self
+            .verify_melt_request(&mut tx, melt_request)
+            .await
+            .map_err(|err| {
                 tracing::debug!("Error attempting to verify melt quote: {}", err);
                 tracing::debug!("Error attempting to verify melt quote: {}", err);
+                err
+            })?;
 
 
-                if let Err(err) = self.process_unpaid_melt(melt_request).await {
-                    tracing::error!(
-                        "Could not reset melt quote {} state: {}",
-                        melt_request.quote(),
-                        err
-                    );
-                }
-                return Err(err);
-            }
-        };
+        let settled_internally_amount = self
+            .handle_internal_melt_mint(&mut tx, &quote, melt_request)
+            .await
+            .map_err(|err| {
+                tracing::error!("Attempting to settle internally failed: {}", err);
+                err
+            })?;
 
 
-        let settled_internally_amount =
-            match self.handle_internal_melt_mint(&quote, melt_request).await {
-                Ok(amount) => amount,
-                Err(err) => {
-                    tracing::error!("Attempting to settle internally failed");
-                    if let Err(err) = self.process_unpaid_melt(melt_request).await {
-                        tracing::error!(
-                            "Could not reset melt quote {} state: {}",
-                            melt_request.quote(),
-                            err
-                        );
-                    }
-                    return Err(err);
-                }
-            };
+        tx.commit().await?;
 
 
-        let (preimage, amount_spent_quote_unit) = match settled_internally_amount {
-            Some(amount_spent) => (None, amount_spent),
+        let mut tx = self.localstore.begin_transaction().await?;
+
+        let (preimage, amount_spent_quote_unit, quote) = match settled_internally_amount {
+            Some(amount_spent) => (None, amount_spent, quote),
             None => {
             None => {
                 // If the quote unit is SAT or MSAT we can check that the expected fees are
                 // If the quote unit is SAT or MSAT we can check that the expected fees are
                 // provided. We also check if the quote is less then the invoice
                 // provided. We also check if the quote is less then the invoice
@@ -488,9 +456,6 @@ impl Mint {
                             Ok(amount) => amount,
                             Ok(amount) => amount,
                             Err(err) => {
                             Err(err) => {
                                 tracing::error!("Fee is not expected: {}", err);
                                 tracing::error!("Fee is not expected: {}", err);
-                                if let Err(err) = self.process_unpaid_melt(melt_request).await {
-                                    tracing::error!("Could not reset melt quote state: {}", err);
-                                }
                                 return Err(Error::Internal);
                                 return Err(Error::Internal);
                             }
                             }
                         }
                         }
@@ -505,10 +470,6 @@ impl Mint {
                     Some(ln) => ln,
                     Some(ln) => ln,
                     None => {
                     None => {
                         tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
                         tracing::info!("Could not get ln backend for {}, bolt11 ", quote.unit);
-                        if let Err(err) = self.process_unpaid_melt(melt_request).await {
-                            tracing::error!("Could not reset melt quote state: {}", err);
-                        }
-
                         return Err(Error::UnsupportedUnit);
                         return Err(Error::UnsupportedUnit);
                     }
                     }
                 };
                 };
@@ -539,9 +500,6 @@ impl Mint {
                         // hold the proofs as pending to we reset them  and return an error.
                         // hold the proofs as pending to we reset them  and return an error.
                         if matches!(err, cdk_payment::Error::InvoiceAlreadyPaid) {
                         if matches!(err, cdk_payment::Error::InvoiceAlreadyPaid) {
                             tracing::debug!("Invoice already paid, resetting melt quote");
                             tracing::debug!("Invoice already paid, resetting melt quote");
-                            if let Err(err) = self.process_unpaid_melt(melt_request).await {
-                                tracing::error!("Could not reset melt quote state: {}", err);
-                            }
                             return Err(Error::RequestAlreadyPaid);
                             return Err(Error::RequestAlreadyPaid);
                         }
                         }
 
 
@@ -567,9 +525,6 @@ impl Mint {
                             "Lightning payment for quote {} failed.",
                             "Lightning payment for quote {} failed.",
                             melt_request.quote()
                             melt_request.quote()
                         );
                         );
-                        if let Err(err) = self.process_unpaid_melt(melt_request).await {
-                            tracing::error!("Could not reset melt quote state: {}", err);
-                        }
                         return Err(Error::PaymentFailed);
                         return Err(Error::PaymentFailed);
                     }
                     }
                     MeltQuoteState::Pending => {
                     MeltQuoteState::Pending => {
@@ -599,19 +554,20 @@ impl Mint {
                     let mut melt_quote = quote;
                     let mut melt_quote = quote;
                     melt_quote.request_lookup_id = payment_lookup_id;
                     melt_quote.request_lookup_id = payment_lookup_id;
 
 
-                    if let Err(err) = self.localstore.add_melt_quote(melt_quote).await {
+                    if let Err(err) = tx.add_melt_quote(melt_quote.clone()).await {
                         tracing::warn!("Could not update payment lookup id: {}", err);
                         tracing::warn!("Could not update payment lookup id: {}", err);
                     }
                     }
+                    (pre.payment_proof, amount_spent, melt_quote)
+                } else {
+                    (pre.payment_proof, amount_spent, quote)
                 }
                 }
-
-                (pre.payment_proof, amount_spent)
             }
             }
         };
         };
 
 
         // If we made it here the payment has been made.
         // If we made it here the payment has been made.
         // We process the melt burning the inputs and returning change
         // We process the melt burning the inputs and returning change
         let res = self
         let res = self
-            .process_melt_request(melt_request, preimage, amount_spent_quote_unit)
+            .process_melt_request(tx, quote, melt_request, preimage, amount_spent_quote_unit)
             .await
             .await
             .map_err(|err| {
             .map_err(|err| {
                 tracing::error!("Could not process melt request: {}", err);
                 tracing::error!("Could not process melt request: {}", err);
@@ -626,26 +582,19 @@ impl Mint {
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn process_melt_request(
     pub async fn process_melt_request(
         &self,
         &self,
+        mut tx: Box<dyn MintTransaction<'_, database::Error> + Send + Sync + '_>,
+        quote: MeltQuote,
         melt_request: &MeltRequest<Uuid>,
         melt_request: &MeltRequest<Uuid>,
         payment_preimage: Option<String>,
         payment_preimage: Option<String>,
         total_spent: Amount,
         total_spent: Amount,
     ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
     ) -> Result<MeltQuoteBolt11Response<Uuid>, Error> {
         tracing::debug!("Processing melt quote: {}", melt_request.quote());
         tracing::debug!("Processing melt quote: {}", melt_request.quote());
 
 
-        let quote = self
-            .localstore
-            .get_melt_quote(melt_request.quote())
-            .await?
-            .ok_or(Error::UnknownQuote)?;
-
         let input_ys = melt_request.inputs().ys()?;
         let input_ys = melt_request.inputs().ys()?;
 
 
-        self.localstore
-            .update_proofs_states(&input_ys, State::Spent)
-            .await?;
+        tx.update_proofs_states(&input_ys, State::Spent).await?;
 
 
-        self.localstore
-            .update_melt_quote_state(melt_request.quote(), MeltQuoteState::Paid)
+        tx.update_melt_quote_state(melt_request.quote(), MeltQuoteState::Paid)
             .await?;
             .await?;
 
 
         self.pubsub_manager.melt_quote_status(
         self.pubsub_manager.melt_quote_status(
@@ -668,8 +617,7 @@ impl Mint {
                 let blinded_messages: Vec<PublicKey> =
                 let blinded_messages: Vec<PublicKey> =
                     outputs.iter().map(|b| b.blinded_secret).collect();
                     outputs.iter().map(|b| b.blinded_secret).collect();
 
 
-                if self
-                    .localstore
+                if tx
                     .get_blind_signatures(&blinded_messages)
                     .get_blind_signatures(&blinded_messages)
                     .await?
                     .await?
                     .iter()
                     .iter()
@@ -711,21 +659,22 @@ impl Mint {
                     change_sigs.push(blinded_signature)
                     change_sigs.push(blinded_signature)
                 }
                 }
 
 
-                self.localstore
-                    .add_blind_signatures(
-                        &outputs[0..change_sigs.len()]
-                            .iter()
-                            .map(|o| o.blinded_secret)
-                            .collect::<Vec<PublicKey>>(),
-                        &change_sigs,
-                        Some(quote.id),
-                    )
-                    .await?;
+                tx.add_blind_signatures(
+                    &outputs[0..change_sigs.len()]
+                        .iter()
+                        .map(|o| o.blinded_secret)
+                        .collect::<Vec<PublicKey>>(),
+                    &change_sigs,
+                    Some(quote.id),
+                )
+                .await?;
 
 
                 change = Some(change_sigs);
                 change = Some(change_sigs);
             }
             }
         }
         }
 
 
+        tx.commit().await?;
+
         Ok(MeltQuoteBolt11Response {
         Ok(MeltQuoteBolt11Response {
             amount: quote.amount,
             amount: quote.amount,
             paid: Some(true),
             paid: Some(true),

+ 4 - 7
crates/cdk/src/mint/mod.rs

@@ -7,7 +7,7 @@ use arc_swap::ArcSwap;
 use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
 use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
 #[cfg(feature = "auth")]
 #[cfg(feature = "auth")]
 use cdk_common::database::MintAuthDatabase;
 use cdk_common::database::MintAuthDatabase;
-use cdk_common::database::{self, MintDatabase};
+use cdk_common::database::{self, MintDatabase, MintTransaction};
 use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
 use cdk_common::nuts::{self, BlindSignature, BlindedMessage, CurrencyUnit, Id, Kind};
 use cdk_common::secret;
 use cdk_common::secret;
 use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
 use cdk_signatory::signatory::{Signatory, SignatoryKeySet};
@@ -24,9 +24,9 @@ use crate::cdk_payment::{self, MintPayment};
 use crate::error::Error;
 use crate::error::Error;
 use crate::fees::calculate_fee;
 use crate::fees::calculate_fee;
 use crate::nuts::*;
 use crate::nuts::*;
-use crate::Amount;
 #[cfg(feature = "auth")]
 #[cfg(feature = "auth")]
 use crate::OidcClient;
 use crate::OidcClient;
+use crate::{cdk_database, Amount};
 
 
 #[cfg(feature = "auth")]
 #[cfg(feature = "auth")]
 pub(crate) mod auth;
 pub(crate) mod auth;
@@ -407,14 +407,11 @@ impl Mint {
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn handle_internal_melt_mint(
     pub async fn handle_internal_melt_mint(
         &self,
         &self,
+        tx: &mut Box<dyn MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
         melt_quote: &MeltQuote,
         melt_quote: &MeltQuote,
         melt_request: &MeltRequest<Uuid>,
         melt_request: &MeltRequest<Uuid>,
     ) -> Result<Option<Amount>, Error> {
     ) -> Result<Option<Amount>, Error> {
-        let mint_quote = match self
-            .localstore
-            .get_mint_quote_by_request(&melt_quote.request)
-            .await
-        {
+        let mint_quote = match tx.get_mint_quote_by_request(&melt_quote.request).await {
             Ok(Some(mint_quote)) => mint_quote,
             Ok(Some(mint_quote)) => mint_quote,
             // Not an internal melt -> mint
             // Not an internal melt -> mint
             Ok(None) => return Ok(None),
             Ok(None) => return Ok(None),

+ 14 - 3
crates/cdk/src/mint/start_up_check.rs

@@ -14,12 +14,19 @@ impl Mint {
     pub async fn check_pending_mint_quotes(&self) -> Result<(), Error> {
     pub async fn check_pending_mint_quotes(&self) -> Result<(), Error> {
         let pending_quotes = self.get_pending_mint_quotes().await?;
         let pending_quotes = self.get_pending_mint_quotes().await?;
         tracing::info!("There are {} pending mint quotes.", pending_quotes.len());
         tracing::info!("There are {} pending mint quotes.", pending_quotes.len());
+        let mut tx = self.localstore.begin_transaction().await?;
         for quote in pending_quotes.iter() {
         for quote in pending_quotes.iter() {
+            let mut quote = if let Some(quote) = tx.get_mint_quote(&quote.id).await? {
+                quote
+            } else {
+                continue;
+            };
             tracing::debug!("Checking status of mint quote: {}", quote.id);
             tracing::debug!("Checking status of mint quote: {}", quote.id);
-            if let Err(err) = self.check_mint_quote_paid(&quote.id).await {
+            if let Err(err) = self.check_mint_quote_paid(&mut tx, &mut quote).await {
                 tracing::error!("Could not check status of {}, {}", quote.id, err);
                 tracing::error!("Could not check status of {}, {}", quote.id, err);
             }
             }
         }
         }
+        tx.commit().await?;
         Ok(())
         Ok(())
     }
     }
 
 
@@ -32,6 +39,8 @@ impl Mint {
             .collect();
             .collect();
         tracing::info!("There are {} pending melt quotes.", pending_quotes.len());
         tracing::info!("There are {} pending melt quotes.", pending_quotes.len());
 
 
+        let mut tx = self.localstore.begin_transaction().await?;
+
         for pending_quote in pending_quotes {
         for pending_quote in pending_quotes {
             tracing::debug!("Checking status for melt quote {}.", pending_quote.id);
             tracing::debug!("Checking status for melt quote {}.", pending_quote.id);
 
 
@@ -65,8 +74,7 @@ impl Mint {
                 MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
                 MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
             };
             };
 
 
-            if let Err(err) = self
-                .localstore
+            if let Err(err) = tx
                 .update_melt_quote_state(&pending_quote.id, melt_quote_state)
                 .update_melt_quote_state(&pending_quote.id, melt_quote_state)
                 .await
                 .await
             {
             {
@@ -79,6 +87,9 @@ impl Mint {
                 );
                 );
             };
             };
         }
         }
+
+        tx.commit().await?;
+
         Ok(())
         Ok(())
     }
     }
 }
 }

+ 19 - 23
crates/cdk/src/mint/swap.rs

@@ -12,8 +12,10 @@ impl Mint {
         &self,
         &self,
         swap_request: SwapRequest,
         swap_request: SwapRequest,
     ) -> Result<SwapResponse, Error> {
     ) -> Result<SwapResponse, Error> {
+        let mut tx = self.localstore.begin_transaction().await?;
+
         if let Err(err) = self
         if let Err(err) = self
-            .verify_transaction_balanced(swap_request.inputs(), swap_request.outputs())
+            .verify_transaction_balanced(&mut tx, swap_request.inputs(), swap_request.outputs())
             .await
             .await
         {
         {
             tracing::debug!("Attempt to swap unbalanced transaction, aborting: {err}");
             tracing::debug!("Attempt to swap unbalanced transaction, aborting: {err}");
@@ -24,8 +26,7 @@ impl Mint {
 
 
         // After swap request is fully validated, add the new proofs to DB
         // After swap request is fully validated, add the new proofs to DB
         let input_ys = swap_request.inputs().ys()?;
         let input_ys = swap_request.inputs().ys()?;
-        if let Some(err) = self
-            .localstore
+        if let Some(err) = tx
             .add_proofs(swap_request.inputs().clone(), None)
             .add_proofs(swap_request.inputs().clone(), None)
             .await
             .await
             .err()
             .err()
@@ -38,7 +39,8 @@ impl Mint {
                 err => Err(Error::Database(err)),
                 err => Err(Error::Database(err)),
             };
             };
         }
         }
-        self.check_ys_spendable(&input_ys, State::Pending).await?;
+        self.check_ys_spendable(&mut tx, &input_ys, State::Pending)
+            .await?;
 
 
         let mut promises = Vec::with_capacity(swap_request.outputs().len());
         let mut promises = Vec::with_capacity(swap_request.outputs().len());
 
 
@@ -47,14 +49,7 @@ impl Mint {
             promises.push(blinded_signature);
             promises.push(blinded_signature);
         }
         }
 
 
-        // TODO: It may be possible to have a race condition, that's why an error when changing the
-        // state can be converted to a TokenAlreadySpent error.
-        //
-        // A concept of transaction/writer for the Database trait would eliminate this problem and
-        // will remove all the "reset" codebase, resulting in fewer lines of code, and less
-        // error-prone database updates
-        self.localstore
-            .update_proofs_states(&input_ys, State::Spent)
+        tx.update_proofs_states(&input_ys, State::Spent)
             .await
             .await
             .map_err(|e| match e {
             .map_err(|e| match e {
                 cdk_database::Error::AttemptUpdateSpentProof => Error::TokenAlreadySpent,
                 cdk_database::Error::AttemptUpdateSpentProof => Error::TokenAlreadySpent,
@@ -65,17 +60,18 @@ impl Mint {
             self.pubsub_manager.proof_state((pub_key, State::Spent));
             self.pubsub_manager.proof_state((pub_key, State::Spent));
         }
         }
 
 
-        self.localstore
-            .add_blind_signatures(
-                &swap_request
-                    .outputs()
-                    .iter()
-                    .map(|o| o.blinded_secret)
-                    .collect::<Vec<PublicKey>>(),
-                &promises,
-                None,
-            )
-            .await?;
+        tx.add_blind_signatures(
+            &swap_request
+                .outputs()
+                .iter()
+                .map(|o| o.blinded_secret)
+                .collect::<Vec<PublicKey>>(),
+            &promises,
+            None,
+        )
+        .await?;
+
+        tx.commit().await?;
 
 
         Ok(SwapResponse::new(promises))
         Ok(SwapResponse::new(promises))
     }
     }

+ 12 - 5
crates/cdk/src/mint/verification.rs

@@ -3,6 +3,8 @@ use std::collections::HashSet;
 use cdk_common::{Amount, BlindedMessage, CurrencyUnit, Id, Proofs, ProofsMethods, PublicKey};
 use cdk_common::{Amount, BlindedMessage, CurrencyUnit, Id, Proofs, ProofsMethods, PublicKey};
 use tracing::instrument;
 use tracing::instrument;
 
 
+use crate::cdk_database;
+
 use super::{Error, Mint};
 use super::{Error, Mint};
 
 
 /// Verification result
 /// Verification result
@@ -149,12 +151,12 @@ impl Mint {
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn check_output_already_signed(
     pub async fn check_output_already_signed(
         &self,
         &self,
+        tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
         outputs: &[BlindedMessage],
         outputs: &[BlindedMessage],
     ) -> Result<(), Error> {
     ) -> Result<(), Error> {
         let blinded_messages: Vec<PublicKey> = outputs.iter().map(|o| o.blinded_secret).collect();
         let blinded_messages: Vec<PublicKey> = outputs.iter().map(|o| o.blinded_secret).collect();
 
 
-        if self
-            .localstore
+        if tx
             .get_blind_signatures(&blinded_messages)
             .get_blind_signatures(&blinded_messages)
             .await?
             .await?
             .iter()
             .iter()
@@ -173,7 +175,11 @@ impl Mint {
     /// Verifies outputs
     /// Verifies outputs
     /// Checks outputs are unique, of the same unit and not signed before
     /// Checks outputs are unique, of the same unit and not signed before
     #[instrument(skip_all)]
     #[instrument(skip_all)]
-    pub async fn verify_outputs(&self, outputs: &[BlindedMessage]) -> Result<Verification, Error> {
+    pub async fn verify_outputs(
+        &self,
+        tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
+        outputs: &[BlindedMessage],
+    ) -> Result<Verification, Error> {
         if outputs.is_empty() {
         if outputs.is_empty() {
             return Ok(Verification {
             return Ok(Verification {
                 amount: Amount::ZERO,
                 amount: Amount::ZERO,
@@ -182,7 +188,7 @@ impl Mint {
         }
         }
 
 
         Mint::check_outputs_unique(outputs)?;
         Mint::check_outputs_unique(outputs)?;
-        self.check_output_already_signed(outputs).await?;
+        self.check_output_already_signed(tx, outputs).await?;
 
 
         let unit = self.verify_outputs_keyset(outputs).await?;
         let unit = self.verify_outputs_keyset(outputs).await?;
 
 
@@ -215,10 +221,11 @@ impl Mint {
     #[instrument(skip_all)]
     #[instrument(skip_all)]
     pub async fn verify_transaction_balanced(
     pub async fn verify_transaction_balanced(
         &self,
         &self,
+        tx: &mut Box<dyn cdk_database::MintTransaction<'_, cdk_database::Error> + Send + Sync + '_>,
         inputs: &Proofs,
         inputs: &Proofs,
         outputs: &[BlindedMessage],
         outputs: &[BlindedMessage],
     ) -> Result<(), Error> {
     ) -> Result<(), Error> {
-        let output_verification = self.verify_outputs(outputs).await.map_err(|err| {
+        let output_verification = self.verify_outputs(tx, outputs).await.map_err(|err| {
             tracing::debug!("Output verification failed: {:?}", err);
             tracing::debug!("Output verification failed: {:?}", err);
             err
             err
         })?;
         })?;

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.