mirror of
https://github.com/aljazceru/cdk.git
synced 2025-12-23 15:44:50 +01:00
feat: add cancel to wait invoice
This commit is contained in:
@@ -16,6 +16,7 @@ cdk = { path = "../cdk", version = "0.4.0", default-features = false, features =
|
||||
cln-rpc = "0.2.0"
|
||||
futures = { version = "0.3.28", default-features = false }
|
||||
tokio = { version = "1", default-features = false }
|
||||
tokio-util = { version = "0.7.11", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = ["attributes", "log"] }
|
||||
thiserror = "1"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
use std::path::PathBuf;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -25,13 +26,15 @@ use cln_rpc::model::requests::{
|
||||
InvoiceRequest, ListinvoicesRequest, ListpaysRequest, PayRequest, WaitanyinvoiceRequest,
|
||||
};
|
||||
use cln_rpc::model::responses::{
|
||||
ListinvoicesInvoicesStatus, ListpaysPaysStatus, PayStatus, WaitanyinvoiceResponse,
|
||||
ListinvoicesInvoices, ListinvoicesInvoicesStatus, ListpaysPaysStatus, PayStatus,
|
||||
WaitanyinvoiceResponse, WaitanyinvoiceStatus,
|
||||
};
|
||||
use cln_rpc::model::Request;
|
||||
use cln_rpc::primitives::{Amount as CLN_Amount, AmountOrAny};
|
||||
use error::Error;
|
||||
use futures::{Stream, StreamExt};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod error;
|
||||
@@ -44,6 +47,8 @@ pub struct Cln {
|
||||
fee_reserve: FeeReserve,
|
||||
mint_settings: MintMethodSettings,
|
||||
melt_settings: MeltMethodSettings,
|
||||
wait_invoice_cancel_token: CancellationToken,
|
||||
wait_invoice_is_active: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl Cln {
|
||||
@@ -62,6 +67,8 @@ impl Cln {
|
||||
fee_reserve,
|
||||
mint_settings,
|
||||
melt_settings,
|
||||
wait_invoice_cancel_token: CancellationToken::new(),
|
||||
wait_invoice_is_active: Arc::new(AtomicBool::new(false)),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -80,43 +87,123 @@ impl MintLightning for Cln {
|
||||
}
|
||||
}
|
||||
|
||||
/// Is wait invoice active
|
||||
fn is_wait_invoice_active(&self) -> bool {
|
||||
self.wait_invoice_is_active.load(Ordering::SeqCst)
|
||||
}
|
||||
|
||||
/// Cancel wait invoice
|
||||
fn cancel_wait_invoice(&self) {
|
||||
self.wait_invoice_cancel_token.cancel()
|
||||
}
|
||||
|
||||
#[allow(clippy::incompatible_msrv)]
|
||||
// Clippy thinks select is not stable but it compiles fine on MSRV (1.63.0)
|
||||
async fn wait_any_invoice(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
let last_pay_index = self.get_last_pay_index().await?;
|
||||
let cln_client = cln_rpc::ClnRpc::new(&self.rpc_socket).await?;
|
||||
|
||||
Ok(futures::stream::unfold(
|
||||
(cln_client, last_pay_index),
|
||||
|(mut cln_client, mut last_pay_idx)| async move {
|
||||
let stream = futures::stream::unfold(
|
||||
(
|
||||
cln_client,
|
||||
last_pay_index,
|
||||
self.wait_invoice_cancel_token.clone(),
|
||||
Arc::clone(&self.wait_invoice_is_active),
|
||||
),
|
||||
|(mut cln_client, mut last_pay_idx, cancel_token, is_active)| async move {
|
||||
// Set the stream as active
|
||||
is_active.store(true, Ordering::SeqCst);
|
||||
|
||||
loop {
|
||||
let invoice_res = cln_client
|
||||
.call(cln_rpc::Request::WaitAnyInvoice(WaitanyinvoiceRequest {
|
||||
tokio::select! {
|
||||
_ = cancel_token.cancelled() => {
|
||||
// Set the stream as inactive
|
||||
is_active.store(false, Ordering::SeqCst);
|
||||
// End the stream
|
||||
return None;
|
||||
}
|
||||
result = cln_client.call(cln_rpc::Request::WaitAnyInvoice(WaitanyinvoiceRequest {
|
||||
timeout: None,
|
||||
lastpay_index: last_pay_idx,
|
||||
}))
|
||||
.await;
|
||||
})) => {
|
||||
match result {
|
||||
Ok(invoice) => {
|
||||
|
||||
let invoice: WaitanyinvoiceResponse = match invoice_res {
|
||||
Ok(invoice) => invoice,
|
||||
// Try to convert the invoice to WaitanyinvoiceResponse
|
||||
let wait_any_response_result: Result<WaitanyinvoiceResponse, _> =
|
||||
invoice.try_into();
|
||||
|
||||
let wait_any_response = match wait_any_response_result {
|
||||
Ok(response) => response,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error fetching invoice: {e}");
|
||||
// Let's not spam CLN with requests on failure
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
// Retry same request
|
||||
tracing::warn!(
|
||||
"Failed to parse WaitAnyInvoice response: {:?}",
|
||||
e
|
||||
);
|
||||
// Continue to the next iteration without panicking
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
// Check the status of the invoice
|
||||
// We only want to yield invoices that have been paid
|
||||
match wait_any_response.status {
|
||||
WaitanyinvoiceStatus::PAID => (),
|
||||
WaitanyinvoiceStatus::EXPIRED => continue,
|
||||
}
|
||||
|
||||
last_pay_idx = wait_any_response.pay_index;
|
||||
|
||||
let payment_hash = wait_any_response.payment_hash.to_string();
|
||||
|
||||
let request_look_up = match wait_any_response.bolt12 {
|
||||
// If it is a bolt12 payment we need to get the offer_id as this is what we use as the request look up.
|
||||
// Since this is not returned in the wait any response,
|
||||
// we need to do a second query for it.
|
||||
Some(_) => {
|
||||
match fetch_invoice_by_payment_hash(
|
||||
&mut cln_client,
|
||||
&payment_hash,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Some(invoice)) => {
|
||||
if let Some(local_offer_id) = invoice.local_offer_id {
|
||||
local_offer_id.to_string()
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
.try_into()
|
||||
.expect("Wrong response from CLN");
|
||||
Ok(None) => continue,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
"Error fetching invoice by payment hash: {e}"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => payment_hash,
|
||||
};
|
||||
|
||||
last_pay_idx = invoice.pay_index;
|
||||
|
||||
break Some((invoice.payment_hash.to_string(), (cln_client, last_pay_idx)));
|
||||
return Some((request_look_up, (cln_client, last_pay_idx, cancel_token, is_active)));
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Error fetching invoice: {e}");
|
||||
tokio::time::sleep(Duration::from_secs(1)).await;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
.boxed())
|
||||
.boxed();
|
||||
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
async fn get_payment_quote(
|
||||
@@ -425,3 +512,33 @@ fn cln_pays_status_to_mint_state(status: ListpaysPaysStatus) -> MeltQuoteState {
|
||||
ListpaysPaysStatus::FAILED => MeltQuoteState::Failed,
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_invoice_by_payment_hash(
|
||||
cln_client: &mut cln_rpc::ClnRpc,
|
||||
payment_hash: &str,
|
||||
) -> Result<Option<ListinvoicesInvoices>, Error> {
|
||||
match cln_client
|
||||
.call(cln_rpc::Request::ListInvoices(ListinvoicesRequest {
|
||||
payment_hash: Some(payment_hash.to_string()),
|
||||
index: None,
|
||||
invstring: None,
|
||||
label: None,
|
||||
limit: None,
|
||||
offer_id: None,
|
||||
start: None,
|
||||
}))
|
||||
.await
|
||||
{
|
||||
Ok(cln_rpc::Response::ListInvoices(invoice_response)) => {
|
||||
Ok(invoice_response.invoices.first().cloned())
|
||||
}
|
||||
Ok(_) => {
|
||||
tracing::warn!("CLN returned an unexpected response type");
|
||||
Err(Error::WrongClnResponse)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Error fetching invoice: {e}");
|
||||
Err(Error::from(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,14 @@ impl MintLightning for FakeWallet {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wait_invoice_active(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cancel_wait_invoice(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn wait_any_invoice(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
|
||||
@@ -80,6 +80,14 @@ impl MintLightning for LNbits {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wait_invoice_active(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cancel_wait_invoice(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn wait_any_invoice(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
|
||||
@@ -88,6 +88,14 @@ impl MintLightning for Lnd {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wait_invoice_active(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cancel_wait_invoice(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn wait_any_invoice(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
|
||||
@@ -86,6 +86,14 @@ impl MintLightning for Phoenixd {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wait_invoice_active(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cancel_wait_invoice(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn wait_any_invoice(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
|
||||
@@ -78,6 +78,14 @@ impl MintLightning for Strike {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_wait_invoice_active(&self) -> bool {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn cancel_wait_invoice(&self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn wait_any_invoice(
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err> {
|
||||
|
||||
@@ -85,6 +85,12 @@ pub trait MintLightning {
|
||||
&self,
|
||||
) -> Result<Pin<Box<dyn Stream<Item = String> + Send>>, Self::Err>;
|
||||
|
||||
/// Is wait invoice active
|
||||
fn is_wait_invoice_active(&self) -> bool;
|
||||
|
||||
/// Cancel wait invoice
|
||||
fn cancel_wait_invoice(&self);
|
||||
|
||||
/// Check the status of an incoming payment
|
||||
async fn check_incoming_invoice_status(
|
||||
&self,
|
||||
|
||||
@@ -190,6 +190,7 @@ impl Mint {
|
||||
let mut join_set = JoinSet::new();
|
||||
|
||||
for (key, ln) in self.ln.iter() {
|
||||
if !ln.is_wait_invoice_active() {
|
||||
let mint = Arc::clone(&mint_arc);
|
||||
let ln = Arc::clone(ln);
|
||||
let shutdown = Arc::clone(&shutdown);
|
||||
@@ -199,6 +200,7 @@ impl Mint {
|
||||
tokio::select! {
|
||||
_ = shutdown.notified() => {
|
||||
tracing::info!("Shutdown signal received, stopping task for {:?}", key);
|
||||
ln.cancel_wait_invoice();
|
||||
break;
|
||||
}
|
||||
result = ln.wait_any_invoice() => {
|
||||
@@ -220,6 +222,7 @@ impl Mint {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Spawn a task to manage the JoinSet
|
||||
while let Some(result) = join_set.join_next().await {
|
||||
|
||||
Reference in New Issue
Block a user