Sfoglia il codice sorgente

feat: check if invoice already paid on melt

feat: check if internal invoice already settled
thesimplekid 8 mesi fa
parent
commit
46f8689b1f

+ 12 - 1
crates/cdk-axum/src/router_handlers.rs

@@ -213,6 +213,12 @@ pub async fn post_melt_bolt11(
 
     let (preimage, amount_spent_quote_unit) = match mint_quote {
         Some(mint_quote) => {
+            if mint_quote.state == MintQuoteState::Issued
+                || mint_quote.state == MintQuoteState::Paid
+            {
+                return Err(into_response(Error::RequestAlreadyPaid));
+            }
+
             let mut mint_quote = mint_quote;
 
             if mint_quote.amount > inputs_amount_quote_unit {
@@ -320,7 +326,12 @@ pub async fn post_melt_bolt11(
                         tracing::error!("Could not reset melt quote state: {}", err);
                     }
 
-                    return Err(into_response(Error::PaymentFailed));
+                    let err = match err {
+                        cdk::cdk_lightning::Error::InvoiceAlreadyPaid => Error::RequestAlreadyPaid,
+                        _ => Error::PaymentFailed,
+                    };
+
+                    return Err(into_response(err));
                 }
             };
 

+ 3 - 3
crates/cdk-cln/src/error.rs

@@ -2,15 +2,15 @@ use thiserror::Error;
 
 #[derive(Debug, Error)]
 pub enum Error {
+    /// Invoice amount not defined
+    #[error("Unknown invoice amount")]
+    UnknownInvoiceAmount,
     /// Wrong CLN response
     #[error("Wrong cln response")]
     WrongClnResponse,
     /// Unknown invoice
     #[error("Unknown invoice")]
     UnknownInvoice,
-    /// Invoice amount not defined
-    #[error("Unknown invoice amount")]
-    UnknownInvoiceAmount,
     /// Cln Error
     #[error(transparent)]
     Cln(#[from] cln_rpc::Error),

+ 56 - 3
crates/cdk-cln/src/lib.rs

@@ -15,9 +15,11 @@ use cdk::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuoteS
 use cdk::util::{hex, unix_time};
 use cdk::{mint, Bolt11Invoice};
 use cln_rpc::model::requests::{
-    InvoiceRequest, ListinvoicesRequest, PayRequest, WaitanyinvoiceRequest,
+    InvoiceRequest, ListinvoicesRequest, ListpaysRequest, PayRequest, WaitanyinvoiceRequest,
+};
+use cln_rpc::model::responses::{
+    ListinvoicesInvoicesStatus, ListpaysPaysStatus, PayStatus, WaitanyinvoiceResponse,
 };
-use cln_rpc::model::responses::{ListinvoicesInvoicesStatus, PayStatus, WaitanyinvoiceResponse};
 use cln_rpc::model::Request;
 use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
 use error::Error;
@@ -131,6 +133,22 @@ impl MintLightning for Cln {
         max_fee_msats: Option<u64>,
     ) -> Result<PayInvoiceResponse, Self::Err> {
         let mut cln_client = self.cln_client.lock().await;
+
+        let pay_state =
+            check_pay_invoice_status(&mut cln_client, melt_quote.request.to_string()).await?;
+
+        match pay_state {
+            MeltQuoteState::Paid => {
+                tracing::debug!("Melt attempted on invoice already paid");
+                return Err(Self::Err::InvoiceAlreadyPaid);
+            }
+            MeltQuoteState::Pending => {
+                tracing::debug!("Melt attempted on invoice already pending");
+                return Err(Self::Err::InvoicePaymentPending);
+            }
+            MeltQuoteState::Unpaid => (),
+        }
+
         let cln_response = cln_client
             .call(Request::Pay(PayRequest {
                 bolt11: melt_quote.request.to_string(),
@@ -190,7 +208,7 @@ impl MintLightning for Cln {
             .call(cln_rpc::Request::Invoice(InvoiceRequest {
                 amount_msat,
                 description,
-                label: Uuid::new_v4().to_string(),
+                label: label.clone(),
                 expiry: Some(unix_expiry - time_now),
                 fallbacks: None,
                 preimage: None,
@@ -293,3 +311,38 @@ fn cln_invoice_status_to_mint_state(status: ListinvoicesInvoicesStatus) -> MintQ
         ListinvoicesInvoicesStatus::EXPIRED => MintQuoteState::Unpaid,
     }
 }
+
+async fn check_pay_invoice_status(
+    cln_client: &mut cln_rpc::ClnRpc,
+    bolt11: String,
+) -> Result<MeltQuoteState, cdk_lightning::Error> {
+    let cln_response = cln_client
+        .call(Request::ListPays(ListpaysRequest {
+            bolt11: Some(bolt11),
+            payment_hash: None,
+            status: None,
+        }))
+        .await
+        .map_err(Error::from)?;
+
+    let state = match cln_response {
+        cln_rpc::Response::ListPays(pay_response) => {
+            let pay = pay_response.pays.first();
+
+            match pay {
+                Some(pay) => match pay.status {
+                    ListpaysPaysStatus::COMPLETE => MeltQuoteState::Paid,
+                    ListpaysPaysStatus::PENDING => MeltQuoteState::Pending,
+                    ListpaysPaysStatus::FAILED => MeltQuoteState::Unpaid,
+                },
+                None => MeltQuoteState::Unpaid,
+            }
+        }
+        _ => {
+            tracing::warn!("CLN returned wrong response kind. When checking pay status");
+            return Err(cdk_lightning::Error::from(Error::WrongClnResponse));
+        }
+    };
+
+    Ok(state)
+}

+ 6 - 0
crates/cdk-mintd/src/main.rs

@@ -202,11 +202,17 @@ async fn main() -> anyhow::Result<()> {
 
 /// Update mint quote when called for a paid invoice
 async fn handle_paid_invoice(mint: Arc<Mint>, request_lookup_id: &str) -> Result<()> {
+    tracing::debug!("Invoice with lookup id paid: {}", request_lookup_id);
     if let Ok(Some(mint_quote)) = mint
         .localstore
         .get_mint_quote_by_request_lookup_id(request_lookup_id)
         .await
     {
+        tracing::debug!(
+            "Quote {} paid by lookup id {}",
+            mint_quote.id,
+            request_lookup_id
+        );
         mint.localstore
             .update_mint_quote_state(&mint_quote.id, cdk::nuts::MintQuoteState::Paid)
             .await?;

+ 6 - 0
crates/cdk/src/cdk_lightning/mod.rs

@@ -14,6 +14,12 @@ use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Request, MeltQuoteState, MintQuot
 /// CDK Lightning Error
 #[derive(Debug, Error)]
 pub enum Error {
+    /// Invoice already paid
+    #[error("Invoice already paid")]
+    InvoiceAlreadyPaid,
+    /// Invoice pay pending
+    #[error("Invoice pay is pending")]
+    InvoicePaymentPending,
     /// Lightning Error
     #[error(transparent)]
     Lightning(Box<dyn std::error::Error + Send + Sync>),

+ 3 - 0
crates/cdk/src/error.rs

@@ -30,6 +30,9 @@ pub enum Error {
     /// Melt Request is not valid
     #[error("Melt request is not valid")]
     MeltRequestInvalid,
+    /// Invoice already paid
+    #[error("Request already paid")]
+    RequestAlreadyPaid,
     /// Amount is not what expected
     #[error("Amount miss match")]
     Amount,

+ 15 - 1
crates/cdk/src/mint/mod.rs

@@ -124,7 +124,21 @@ impl Mint {
         expiry: u64,
         ln_lookup: String,
     ) -> Result<MintQuote, Error> {
-        let quote = MintQuote::new(mint_url, request, unit, amount, expiry, ln_lookup);
+        let quote = MintQuote::new(
+            mint_url,
+            request,
+            unit.clone(),
+            amount,
+            expiry,
+            ln_lookup.clone(),
+        );
+        tracing::debug!(
+            "New mint quote {} for {} {} with request id {}",
+            quote.id,
+            amount,
+            unit,
+            &ln_lookup
+        );
 
         self.localstore.add_mint_quote(quote.clone()).await?;