diff --git a/crates/cdk-axum/src/router_handlers.rs b/crates/cdk-axum/src/router_handlers.rs index 5c571059..ec2a5c37 100644 --- a/crates/cdk-axum/src/router_handlers.rs +++ b/crates/cdk-axum/src/router_handlers.rs @@ -12,6 +12,7 @@ use cdk::nuts::{ }; use cdk::util::unix_time; use paste::paste; +use tracing::instrument; use uuid::Uuid; use crate::ws::main_websocket; @@ -232,6 +233,7 @@ pub async fn post_mint_bolt11( (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json") ) ))] +#[instrument(skip_all)] /// Request a quote for melting tokens pub async fn post_melt_bolt11_quote( State(state): State, @@ -261,6 +263,7 @@ pub async fn post_melt_bolt11_quote( /// Get melt quote by ID /// /// Get melt quote state. +#[instrument(skip_all)] pub async fn get_check_melt_bolt11_quote( State(state): State, Path(quote_id): Path, @@ -290,6 +293,7 @@ pub async fn get_check_melt_bolt11_quote( /// Melt tokens for a Bitcoin payment that the mint will make for the user in exchange /// /// Requests tokens to be destroyed and sent out via Lightning. +#[instrument(skip_all)] pub async fn post_melt_bolt11( State(state): State, Json(payload): Json>, diff --git a/crates/cdk-integration-tests/src/init_mint.rs b/crates/cdk-integration-tests/src/init_mint.rs index 0ca0d883..6ff23803 100644 --- a/crates/cdk-integration-tests/src/init_mint.rs +++ b/crates/cdk-integration-tests/src/init_mint.rs @@ -5,7 +5,9 @@ use axum::Router; use cdk::mint::Mint; use tokio::sync::Notify; use tower_http::cors::CorsLayer; +use tracing::instrument; +#[instrument(skip_all)] pub async fn start_mint(addr: &str, port: u16, mint: Mint) -> Result<()> { let mint_arc = Arc::new(mint); diff --git a/crates/cdk-integration-tests/src/init_regtest.rs b/crates/cdk-integration-tests/src/init_regtest.rs index aff4d924..839775f4 100644 --- a/crates/cdk-integration-tests/src/init_regtest.rs +++ b/crates/cdk-integration-tests/src/init_regtest.rs @@ -1,5 +1,5 @@ use std::env; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::sync::Arc; use anyhow::Result; @@ -14,6 +14,7 @@ use ln_regtest_rs::bitcoin_client::BitcoinClient; use ln_regtest_rs::bitcoind::Bitcoind; use ln_regtest_rs::ln_client::{ClnClient, LightningClient, LndClient}; use ln_regtest_rs::lnd::Lnd; +use tracing::instrument; use crate::init_mint::start_mint; @@ -93,11 +94,11 @@ pub fn get_lnd_dir(name: &str) -> PathBuf { dir } -pub fn get_lnd_cert_file_path(lnd_dir: &PathBuf) -> PathBuf { +pub fn get_lnd_cert_file_path(lnd_dir: &Path) -> PathBuf { lnd_dir.join("tls.cert") } -pub fn get_lnd_macaroon_path(lnd_dir: &PathBuf) -> PathBuf { +pub fn get_lnd_macaroon_path(lnd_dir: &Path) -> PathBuf { lnd_dir.join("data/chain/bitcoin/regtest/admin.macaroon") } @@ -147,6 +148,7 @@ pub async fn create_lnd_backend(lnd_client: &LndClient) -> Result { .await?) } +#[instrument(skip_all)] pub async fn create_mint(addr: &str, port: u16, database: D, lighting: L) -> Result<()> where D: MintDatabase + Send + Sync + 'static, diff --git a/crates/cdk-integration-tests/tests/regtest.rs b/crates/cdk-integration-tests/tests/regtest.rs index dde231e8..73d3010e 100644 --- a/crates/cdk-integration-tests/tests/regtest.rs +++ b/crates/cdk-integration-tests/tests/regtest.rs @@ -15,7 +15,8 @@ use cdk::nuts::{ use cdk::wallet::client::{HttpClient, MintConnector}; use cdk::wallet::{Wallet, WalletSubscription}; use cdk_integration_tests::init_regtest::{ - get_cln_dir, get_lnd_dir, get_mint_url, get_mint_ws_url, LND_RPC_ADDR, + get_cln_dir, get_lnd_cert_file_path, get_lnd_dir, get_lnd_macaroon_path, get_mint_port, + get_mint_url, get_mint_ws_url, LND_RPC_ADDR, LND_TWO_RPC_ADDR, }; use futures::{SinkExt, StreamExt}; use lightning_invoice::Bolt11Invoice; @@ -258,7 +259,10 @@ async fn test_pay_invoice_twice() -> Result<()> { let mint_quote = wallet.mint_quote(100.into(), None).await?; - lnd_client.pay_invoice(mint_quote.request).await?; + lnd_client + .pay_invoice(mint_quote.request) + .await + .expect("Could not pay invoice"); let proofs = wallet .mint(&mint_quote.id, SplitTarget::default(), None) @@ -343,13 +347,33 @@ async fn test_internal_payment() -> Result<()> { .await .unwrap(); - let cln_one_dir = get_cln_dir("one"); - let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?; + let check_paid = match get_mint_port() { + 8085 => { + let cln_one_dir = get_cln_dir("one"); + let cln_client = ClnClient::new(cln_one_dir.clone(), None).await?; - let payment_hash = Bolt11Invoice::from_str(&mint_quote.request)?; - let check_paid = cln_client - .check_incoming_payment_status(&payment_hash.payment_hash().to_string()) - .await?; + let payment_hash = Bolt11Invoice::from_str(&mint_quote.request)?; + cln_client + .check_incoming_payment_status(&payment_hash.payment_hash().to_string()) + .await + .expect("Could not check invoice") + } + 8087 => { + let lnd_two_dir = get_lnd_dir("two"); + let lnd_client = LndClient::new( + format!("https://{}", LND_TWO_RPC_ADDR), + get_lnd_cert_file_path(&lnd_two_dir), + get_lnd_macaroon_path(&lnd_two_dir), + ) + .await?; + let payment_hash = Bolt11Invoice::from_str(&mint_quote.request)?; + lnd_client + .check_incoming_payment_status(&payment_hash.payment_hash().to_string()) + .await + .expect("Could not check invoice") + } + _ => panic!("Unknown mint port"), + }; match check_paid { InvoiceStatus::Unpaid => (), diff --git a/crates/cdk-lnd/src/lib.rs b/crates/cdk-lnd/src/lib.rs index 3a4a59b2..0f379915 100644 --- a/crates/cdk-lnd/src/lib.rs +++ b/crates/cdk-lnd/src/lib.rs @@ -25,6 +25,7 @@ use error::Error; use fedimint_tonic_lnd::lnrpc::fee_limit::Limit; use fedimint_tonic_lnd::lnrpc::payment::PaymentStatus; use fedimint_tonic_lnd::lnrpc::FeeLimit; +use fedimint_tonic_lnd::tonic::Code; use fedimint_tonic_lnd::Client; use futures::{Stream, StreamExt}; use tokio::sync::Mutex; @@ -76,6 +77,7 @@ impl Lnd { impl MintLightning for Lnd { type Err = cdk_lightning::Error; + #[instrument(skip_all)] fn get_settings(&self) -> Settings { Settings { mpp: false, @@ -84,14 +86,17 @@ impl MintLightning for Lnd { } } + #[instrument(skip_all)] fn is_wait_invoice_active(&self) -> bool { self.wait_invoice_is_active.load(Ordering::SeqCst) } + #[instrument(skip_all)] fn cancel_wait_invoice(&self) { self.wait_invoice_cancel_token.cancel() } + #[instrument(skip_all)] async fn wait_any_invoice( &self, ) -> Result + Send>>, Self::Err> { @@ -164,6 +169,7 @@ impl MintLightning for Lnd { .boxed()) } + #[instrument(skip_all)] async fn get_payment_quote( &self, melt_quote_request: &MeltQuoteBolt11Request, @@ -198,6 +204,23 @@ impl MintLightning for Lnd { max_fee: Option, ) -> Result { let payment_request = melt_quote.request; + let bolt11 = Bolt11Invoice::from_str(&payment_request)?; + + let pay_state = self + .check_outgoing_payment(&bolt11.payment_hash().to_string()) + .await?; + + match pay_state.status { + MeltQuoteState::Unpaid | MeltQuoteState::Unknown | MeltQuoteState::Failed => (), + 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); + } + } let amount_msat: u64 = match melt_quote.msat_to_pay { Some(amount_msat) => amount_msat.into(), @@ -255,6 +278,7 @@ impl MintLightning for Lnd { }) } + #[instrument(skip(self, description))] async fn create_invoice( &self, amount: Amount, @@ -292,6 +316,7 @@ impl MintLightning for Lnd { }) } + #[instrument(skip(self))] async fn check_incoming_invoice_status( &self, request_lookup_id: &str, @@ -324,6 +349,7 @@ impl MintLightning for Lnd { } } + #[instrument(skip(self))] async fn check_outgoing_payment( &self, payment_hash: &str, @@ -332,15 +358,32 @@ impl MintLightning for Lnd { payment_hash: hex::decode(payment_hash).map_err(|_| Error::InvalidHash)?, no_inflight_updates: true, }; - let mut payment_stream = self + + let payment_response = self .client .lock() .await .router() .track_payment_v2(track_request) - .await - .unwrap() - .into_inner(); + .await; + + let mut payment_stream = match payment_response { + Ok(stream) => stream.into_inner(), + Err(err) => { + let err_code = err.code(); + if err_code == Code::NotFound { + return Ok(PayInvoiceResponse { + payment_lookup_id: payment_hash.to_string(), + payment_preimage: None, + status: MeltQuoteState::Unknown, + total_spent: Amount::ZERO, + unit: self.get_settings().unit, + }); + } else { + return Err(cdk_lightning::Error::UnknownPaymentState); + } + } + }; while let Some(update_result) = payment_stream.next().await { match update_result { diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index f2679b52..064efdc6 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -21,6 +21,7 @@ use crate::util::unix_time; use crate::{cdk_lightning, Amount, Error}; impl Mint { + #[instrument(skip_all)] fn check_melt_request_acceptable( &self, amount: Amount, diff --git a/misc/itests.sh b/misc/itests.sh index 7d2ce3ff..cb127606 100755 --- a/misc/itests.sh +++ b/misc/itests.sh @@ -28,7 +28,7 @@ cleanup() { } # Set up trap to call cleanup on script exit -# trap cleanup EXIT +trap cleanup EXIT # Create a temporary directory export cdk_itests=$(mktemp -d) @@ -47,7 +47,10 @@ export MINT_DATABASE="$1"; cargo build -p cdk-integration-tests cargo build --bin regtest_mint +# cargo run --bin regtest_mint > "$cdk_itests/mint.log" 2>&1 & cargo run --bin regtest_mint & + +echo $cdk_itests # Capture its PID CDK_ITEST_MINT_BIN_PID=$! @@ -82,10 +85,10 @@ done # Run cargo test -# cargo test -p cdk-integration-tests --test regtest +cargo test -p cdk-integration-tests --test regtest # # Run cargo test with the http_subscription feature -# cargo test -p cdk-integration-tests --test regtest --features http_subscription +cargo test -p cdk-integration-tests --test regtest --features http_subscription # Run tests with lnd mint export cdk_itests_mint_port=8087;