Ver Fonte

Remove `melt_request` (#819)

* Fix SQLite race condition

Bug: https://github.com/crodas/cdk/actions/runs/15732950296/job/44339804072#step:5:1853

Reason: When melting in parallel, many update the melt status and attempt to
add proofs and they fail when adding the proof and the rollback code kicks in.
The loser process removes all the proofs, and the winner process has no proof
later on.

Fix: Modify `update_melt_quote_state` requirements and implementation to allow
only one winner.

This will be solved by design with a transaction writer trait

* Remove `melt_request`

Fixes #809

* Remove `get_melt_request` from db trait
C há 1 semana atrás
pai
commit
ad5f29c9a6

+ 3 - 15
crates/cdk-common/src/database/mint/mod.rs

@@ -7,11 +7,11 @@ use cashu::MintInfo;
 use uuid::Uuid;
 
 use super::Error;
-use crate::common::{PaymentProcessorKey, QuoteTTL};
+use crate::common::QuoteTTL;
 use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote};
 use crate::nuts::{
-    BlindSignature, CurrencyUnit, Id, MeltQuoteState, MeltRequest, MintQuoteState, Proof, Proofs,
-    PublicKey, State,
+    BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintQuoteState, Proof, Proofs, PublicKey,
+    State,
 };
 
 #[cfg(feature = "auth")]
@@ -95,18 +95,6 @@ pub trait QuotesDatabase {
     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>;
-
-    /// Add melt request
-    async fn add_melt_request(
-        &self,
-        melt_request: MeltRequest<Uuid>,
-        ln_key: PaymentProcessorKey,
-    ) -> Result<(), Self::Err>;
-    /// Get melt request
-    async fn get_melt_request(
-        &self,
-        quote_id: &Uuid,
-    ) -> Result<Option<(MeltRequest<Uuid>, PaymentProcessorKey)>, Self::Err>;
 }
 
 /// Mint Proof Database trait

+ 3 - 47
crates/cdk-redb/src/mint/mod.rs

@@ -7,7 +7,7 @@ use std::str::FromStr;
 use std::sync::Arc;
 
 use async_trait::async_trait;
-use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
+use cdk_common::common::QuoteTTL;
 use cdk_common::database::{
     self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
     MintSignaturesDatabase,
@@ -18,8 +18,8 @@ use cdk_common::nut00::ProofsMethods;
 use cdk_common::state::check_state_transition;
 use cdk_common::util::unix_time;
 use cdk_common::{
-    BlindSignature, CurrencyUnit, Id, MeltQuoteState, MeltRequest, MintInfo, MintQuoteState, Proof,
-    Proofs, PublicKey, State,
+    BlindSignature, CurrencyUnit, Id, MeltQuoteState, MintInfo, MintQuoteState, Proof, Proofs,
+    PublicKey, State,
 };
 use migrations::{migrate_01_to_02, migrate_04_to_05};
 use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
@@ -55,9 +55,6 @@ const QUOTE_PROOFS_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
 const QUOTE_SIGNATURES_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
     MultimapTableDefinition::new("quote_signatures");
 
-const MELT_REQUESTS: TableDefinition<[u8; 16], (&str, &str)> =
-    TableDefinition::new("melt_requests");
-
 const DATABASE_VERSION: u32 = 5;
 
 /// Mint Redbdatabase
@@ -540,47 +537,6 @@ impl MintQuotesDatabase for MintRedbDatabase {
 
         Ok(())
     }
-
-    /// Add melt request
-    async fn add_melt_request(
-        &self,
-        melt_request: MeltRequest<Uuid>,
-        ln_key: PaymentProcessorKey,
-    ) -> Result<(), Self::Err> {
-        let write_txn = self.db.begin_write().map_err(Error::from)?;
-        let mut table = write_txn.open_table(MELT_REQUESTS).map_err(Error::from)?;
-
-        table
-            .insert(
-                melt_request.quote().as_bytes(),
-                (
-                    serde_json::to_string(&melt_request)?.as_str(),
-                    serde_json::to_string(&ln_key)?.as_str(),
-                ),
-            )
-            .map_err(Error::from)?;
-
-        Ok(())
-    }
-    /// Get melt request
-    async fn get_melt_request(
-        &self,
-        quote_id: &Uuid,
-    ) -> Result<Option<(MeltRequest<Uuid>, PaymentProcessorKey)>, Self::Err> {
-        let read_txn = self.db.begin_read().map_err(Error::from)?;
-        let table = read_txn.open_table(MELT_REQUESTS).map_err(Error::from)?;
-
-        match table.get(quote_id.as_bytes()).map_err(Error::from)? {
-            Some(melt_request) => {
-                let (melt_request_str, ln_key_str) = melt_request.value();
-                let melt_request = serde_json::from_str(melt_request_str)?;
-                let ln_key = serde_json::from_str(ln_key_str)?;
-
-                Ok(Some((melt_request, ln_key)))
-            }
-            None => Ok(None),
-        }
-    }
 }
 
 #[async_trait]

+ 1 - 8
crates/cdk-sqlite/src/mint/memory.rs

@@ -1,14 +1,12 @@
 //! In-memory database that is provided by the `cdk-sqlite` crate, mainly for testing purposes.
 use std::collections::HashMap;
 
-use cdk_common::common::PaymentProcessorKey;
 use cdk_common::database::{
     self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
 };
 use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
-use cdk_common::nuts::{CurrencyUnit, Id, MeltRequest, Proofs};
+use cdk_common::nuts::{CurrencyUnit, Id, Proofs};
 use cdk_common::MintInfo;
-use uuid::Uuid;
 
 use super::MintSqliteDatabase;
 
@@ -30,7 +28,6 @@ pub async fn new_with_state(
     melt_quotes: Vec<mint::MeltQuote>,
     pending_proofs: Proofs,
     spent_proofs: Proofs,
-    melt_request: Vec<(MeltRequest<Uuid>, PaymentProcessorKey)>,
     mint_info: MintInfo,
 ) -> Result<MintSqliteDatabase, database::Error> {
     let db = empty().await?;
@@ -55,10 +52,6 @@ pub async fn new_with_state(
     db.add_proofs(pending_proofs, None).await?;
     db.add_proofs(spent_proofs, None).await?;
 
-    for (melt_request, ln_key) in melt_request {
-        db.add_melt_request(melt_request, ln_key).await?;
-    }
-
     db.set_mint_info(mint_info).await?;
 
     Ok(db)

+ 3 - 86
crates/cdk-sqlite/src/mint/mod.rs

@@ -8,7 +8,7 @@ use std::str::FromStr;
 use async_rusqlite::{query, DatabaseExecutor};
 use async_trait::async_trait;
 use bitcoin::bip32::DerivationPath;
-use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
+use cdk_common::common::QuoteTTL;
 use cdk_common::database::{
     self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
     MintSignaturesDatabase,
@@ -20,8 +20,8 @@ use cdk_common::secret::Secret;
 use cdk_common::state::check_state_transition;
 use cdk_common::util::unix_time;
 use cdk_common::{
-    Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltQuoteState, MeltRequest,
-    MintInfo, MintQuoteState, PaymentMethod, Proof, Proofs, PublicKey, SecretKey, State,
+    Amount, BlindSignature, BlindSignatureDleq, CurrencyUnit, Id, MeltQuoteState, MintInfo,
+    MintQuoteState, Proof, Proofs, PublicKey, SecretKey, State,
 };
 use error::Error;
 use lightning_invoice::Bolt11Invoice;
@@ -733,60 +733,6 @@ impl MintQuotesDatabase for MintSqliteDatabase {
 
         Ok(())
     }
-
-    async fn add_melt_request(
-        &self,
-        melt_request: MeltRequest<Uuid>,
-        ln_key: PaymentProcessorKey,
-    ) -> Result<(), Self::Err> {
-        query(
-            r#"
-                INSERT INTO melt_request
-                (id, inputs, outputs, method, unit)
-                VALUES
-                (:id, :inputs, :outputs, :method, :unit)
-                ON CONFLICT(id) DO UPDATE SET
-                    inputs = excluded.inputs,
-                    outputs = excluded.outputs,
-                    method = excluded.method,
-                    unit = excluded.unit
-        "#,
-        )
-        .bind(":id", melt_request.quote().to_string())
-        .bind(":inputs", serde_json::to_string(&melt_request.inputs())?)
-        .bind(":outputs", serde_json::to_string(&melt_request.outputs())?)
-        .bind(":method", ln_key.method.to_string())
-        .bind(":unit", ln_key.unit.to_string())
-        .execute(&self.pool)
-        .await?;
-
-        Ok(())
-    }
-
-    async fn get_melt_request(
-        &self,
-        quote_id: &Uuid,
-    ) -> Result<Option<(MeltRequest<Uuid>, PaymentProcessorKey)>, Self::Err> {
-        Ok(query(
-            r#"
-            SELECT
-                id,
-                inputs,
-                outputs,
-                method,
-                unit
-            FROM
-                melt_request
-            WHERE
-                id=?;
-            "#,
-        )
-        .bind(":id", quote_id.hyphenated().to_string())
-        .fetch_one(&self.pool)
-        .await?
-        .map(sqlite_row_to_melt_request)
-        .transpose()?)
-    }
 }
 
 #[async_trait]
@@ -1353,35 +1299,6 @@ fn sqlite_row_to_blind_signature(row: Vec<Column>) -> Result<BlindSignature, Err
     })
 }
 
-fn sqlite_row_to_melt_request(
-    row: Vec<Column>,
-) -> Result<(MeltRequest<Uuid>, PaymentProcessorKey), Error> {
-    unpack_into!(
-        let (
-            id,
-            inputs,
-            outputs,
-            method,
-            unit
-        ) = row
-    );
-
-    let id = column_as_string!(id);
-
-    let melt_request = MeltRequest::new(
-        Uuid::parse_str(&id).map_err(|_| Error::InvalidUuid(id))?,
-        column_as_string!(&inputs, serde_json::from_str),
-        column_as_nullable_string!(&outputs).and_then(|w| serde_json::from_str(&w).ok()),
-    );
-
-    let ln_key = PaymentProcessorKey {
-        unit: column_as_string!(&unit, CurrencyUnit::from_str),
-        method: column_as_string!(&method, PaymentMethod::from_str),
-    };
-
-    Ok((melt_request, ln_key))
-}
-
 #[cfg(test)]
 mod tests {
     use std::fs::remove_file;

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

@@ -538,9 +538,7 @@ impl Mint {
 mod tests {
     use std::str::FromStr;
 
-    use cdk_common::common::PaymentProcessorKey;
     use cdk_sqlite::mint::memory::new_with_state;
-    use uuid::Uuid;
 
     use super::*;
 
@@ -555,7 +553,6 @@ mod tests {
         seed: &'a [u8],
         mint_info: MintInfo,
         supported_units: HashMap<CurrencyUnit, (u64, u8)>,
-        melt_requests: Vec<(MeltRequest<Uuid>, PaymentProcessorKey)>,
     }
 
     async fn create_mint(config: MintConfig<'_>) -> Mint {
@@ -567,7 +564,6 @@ mod tests {
                 config.melt_quotes,
                 config.pending_proofs,
                 config.spent_proofs,
-                config.melt_requests,
                 config.mint_info,
             )
             .await

+ 26 - 78
crates/cdk/src/mint/start_up_check.rs

@@ -34,18 +34,10 @@ impl Mint {
 
         for pending_quote in pending_quotes {
             tracing::debug!("Checking status for melt quote {}.", pending_quote.id);
-            let melt_request_ln_key = self.localstore.get_melt_request(&pending_quote.id).await?;
 
-            let (melt_request, ln_key) = match melt_request_ln_key {
-                None => {
-                    let ln_key = PaymentProcessorKey {
-                        unit: pending_quote.unit,
-                        method: PaymentMethod::Bolt11,
-                    };
-
-                    (None, ln_key)
-                }
-                Some((melt_request, ln_key)) => (Some(melt_request), ln_key),
+            let ln_key = PaymentProcessorKey {
+                unit: pending_quote.unit,
+                method: PaymentMethod::Bolt11,
             };
 
             let ln_backend = match self.ln.get(&ln_key) {
@@ -60,75 +52,31 @@ impl Mint {
                 .check_outgoing_payment(&pending_quote.request_lookup_id)
                 .await?;
 
-            match melt_request {
-                Some(melt_request) => {
-                    match pay_invoice_response.status {
-                        MeltQuoteState::Paid => {
-                            if let Err(err) = self
-                                .process_melt_request(
-                                    &melt_request,
-                                    pay_invoice_response.payment_proof,
-                                    pay_invoice_response.total_spent,
-                                )
-                                .await
-                            {
-                                tracing::error!(
-                                    "Could not process melt request for pending quote: {}",
-                                    melt_request.quote()
-                                );
-                                tracing::error!("{}", err);
-                            }
-                        }
-                        MeltQuoteState::Unpaid
-                        | MeltQuoteState::Unknown
-                        | MeltQuoteState::Failed => {
-                            // Payment has not been made we want to unset
-                            tracing::info!(
-                                "Lightning payment for quote {} failed.",
-                                pending_quote.id
-                            );
-                            if let Err(err) = self.process_unpaid_melt(&melt_request).await {
-                                tracing::error!("Could not reset melt quote state: {}", err);
-                            }
-                        }
-                        MeltQuoteState::Pending => {
-                            tracing::warn!(
-                                "LN payment pending, proofs are stuck as pending for quote: {}",
-                                melt_request.quote()
-                            );
-                            // Quote is still pending we do not want to do anything
-                            // continue to check next quote
-                        }
-                    }
-                }
-                None => {
-                    tracing::warn!(
-                        "There is no stored melt request for pending melt quote: {}",
-                        pending_quote.id
-                    );
+            tracing::warn!(
+                "There is no stored melt request for pending melt quote: {}",
+                pending_quote.id
+            );
 
-                    let melt_quote_state = match pay_invoice_response.status {
-                        MeltQuoteState::Unpaid => MeltQuoteState::Unpaid,
-                        MeltQuoteState::Paid => MeltQuoteState::Paid,
-                        MeltQuoteState::Pending => MeltQuoteState::Pending,
-                        MeltQuoteState::Failed => MeltQuoteState::Unpaid,
-                        MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
-                    };
+            let melt_quote_state = match pay_invoice_response.status {
+                MeltQuoteState::Unpaid => MeltQuoteState::Unpaid,
+                MeltQuoteState::Paid => MeltQuoteState::Paid,
+                MeltQuoteState::Pending => MeltQuoteState::Pending,
+                MeltQuoteState::Failed => MeltQuoteState::Unpaid,
+                MeltQuoteState::Unknown => MeltQuoteState::Unpaid,
+            };
 
-                    if let Err(err) = self
-                        .localstore
-                        .update_melt_quote_state(&pending_quote.id, melt_quote_state)
-                        .await
-                    {
-                        tracing::error!(
-                            "Could not update quote {} to state {}, current state {}, {}",
-                            pending_quote.id,
-                            melt_quote_state,
-                            pending_quote.state,
-                            err
-                        );
-                    };
-                }
+            if let Err(err) = self
+                .localstore
+                .update_melt_quote_state(&pending_quote.id, melt_quote_state)
+                .await
+            {
+                tracing::error!(
+                    "Could not update quote {} to state {}, current state {}, {}",
+                    pending_quote.id,
+                    melt_quote_state,
+                    pending_quote.state,
+                    err
+                );
             };
         }
         Ok(())