diff --git a/crates/cdk-lnd/src/lib.rs b/crates/cdk-lnd/src/lib.rs index e45822a6..b540f75b 100644 --- a/crates/cdk-lnd/src/lib.rs +++ b/crates/cdk-lnd/src/lib.rs @@ -28,7 +28,7 @@ use cdk::{mint, Bolt11Invoice}; use error::Error; use fedimint_tonic_lnd::lnrpc::fee_limit::Limit; use fedimint_tonic_lnd::lnrpc::payment::PaymentStatus; -use fedimint_tonic_lnd::lnrpc::{FeeLimit, Hop, HtlcAttempt, MppRecord}; +use fedimint_tonic_lnd::lnrpc::{FeeLimit, Hop, MppRecord}; use fedimint_tonic_lnd::tonic::Code; use fedimint_tonic_lnd::Client; use futures::{Stream, StreamExt}; @@ -52,6 +52,9 @@ pub struct Lnd { } impl Lnd { + /// Maximum number of attempts at a partial payment + pub const MAX_ROUTE_RETRIES: usize = 50; + /// Create new [`Lnd`] pub async fn new( address: String, @@ -282,51 +285,50 @@ impl MintPayment for Lnd { let payer_addr = invoice.payment_secret().0.to_vec(); let payment_hash = invoice.payment_hash(); - // Create a request for the routes - let route_req = fedimint_tonic_lnd::lnrpc::QueryRoutesRequest { - pub_key: hex::encode(pub_key.serialize()), - amt_msat: u64::from(partial_amount_msat) as i64, - fee_limit: max_fee.map(|f| { - let limit = Limit::Fixed(u64::from(f) as i64); - FeeLimit { limit: Some(limit) } - }), - ..Default::default() - }; + for attempt in 0..Self::MAX_ROUTE_RETRIES { + // Create a request for the routes + let route_req = fedimint_tonic_lnd::lnrpc::QueryRoutesRequest { + pub_key: hex::encode(pub_key.serialize()), + amt_msat: u64::from(partial_amount_msat) as i64, + fee_limit: max_fee.map(|f| { + let limit = Limit::Fixed(u64::from(f) as i64); + FeeLimit { limit: Some(limit) } + }), + use_mission_control: true, + ..Default::default() + }; - // Query the routes - let routes_response: fedimint_tonic_lnd::lnrpc::QueryRoutesResponse = self - .client - .lock() - .await - .lightning() - .query_routes(route_req) - .await - .map_err(Error::LndError)? - .into_inner(); + // Query the routes + let mut routes_response: fedimint_tonic_lnd::lnrpc::QueryRoutesResponse = self + .client + .lock() + .await + .lightning() + .query_routes(route_req) + .await + .map_err(Error::LndError)? + .into_inner(); - let mut payment_response: HtlcAttempt = HtlcAttempt { - ..Default::default() - }; - - // For each route: - // update its MPP record, - // attempt it and check the result - for mut route in routes_response.routes.into_iter() { - let last_hop: &mut Hop = route.hops.last_mut().ok_or(Error::MissingLastHop)?; + // update its MPP record, + // attempt it and check the result + let last_hop: &mut Hop = routes_response.routes[0] + .hops + .last_mut() + .ok_or(Error::MissingLastHop)?; let mpp_record = MppRecord { payment_addr: payer_addr.clone(), total_amt_msat: amount_msat as i64, }; last_hop.mpp_record = Some(mpp_record); - tracing::debug!("sendToRouteV2 needle"); - payment_response = self + + let payment_response = self .client .lock() .await .router() .send_to_route_v2(fedimint_tonic_lnd::routerrpc::SendToRouteRequest { payment_hash: payment_hash.to_byte_array().to_vec(), - route: Some(route), + route: Some(routes_response.routes[0].clone()), ..Default::default() }) .await @@ -335,38 +337,44 @@ impl MintPayment for Lnd { if let Some(failure) = payment_response.failure { if failure.code == 15 { - // Try a different route + tracing::debug!( + "Attempt number {}: route has failed. Re-querying...", + attempt + 1 + ); continue; } - } else { - break; } + + // Get status and maybe the preimage + let (status, payment_preimage) = match payment_response.status { + 0 => (MeltQuoteState::Pending, None), + 1 => ( + MeltQuoteState::Paid, + Some(hex::encode(payment_response.preimage)), + ), + 2 => (MeltQuoteState::Unpaid, None), + _ => (MeltQuoteState::Unknown, None), + }; + + // Get the actual amount paid in sats + let mut total_amt: u64 = 0; + if let Some(route) = payment_response.route { + total_amt = (route.total_amt_msat / 1000) as u64; + } + + return Ok(MakePaymentResponse { + payment_lookup_id: hex::encode(payment_hash), + payment_proof: payment_preimage, + status, + total_spent: total_amt.into(), + unit: CurrencyUnit::Sat, + }); } - // Get status and maybe the preimage - let (status, payment_preimage) = match payment_response.status { - 0 => (MeltQuoteState::Pending, None), - 1 => ( - MeltQuoteState::Paid, - Some(hex::encode(payment_response.preimage)), - ), - 2 => (MeltQuoteState::Unpaid, None), - _ => (MeltQuoteState::Unknown, None), - }; - - // Get the actual amount paid in sats - let mut total_amt: u64 = 0; - if let Some(route) = payment_response.route { - total_amt = (route.total_amt_msat / 1000) as u64; - } - - Ok(MakePaymentResponse { - payment_lookup_id: hex::encode(payment_hash), - payment_proof: payment_preimage, - status, - total_spent: total_amt.into(), - unit: CurrencyUnit::Sat, - }) + // "We have exhausted all tactical options" -- STEM, Upgrade (2018) + // The payment was not possible within 50 retries. + tracing::error!("Limit of retries reached, payment couldn't succeed."); + Err(Error::PaymentFailed.into()) } None => { let pay_req = fedimint_tonic_lnd::lnrpc::SendRequest { diff --git a/crates/cdk/src/mint/melt.rs b/crates/cdk/src/mint/melt.rs index ddad5700..381689c8 100644 --- a/crates/cdk/src/mint/melt.rs +++ b/crates/cdk/src/mint/melt.rs @@ -250,13 +250,10 @@ impl Mint { }; let partial_amount = match invoice_amount_msats > quote_msats { - true => { - let partial_msats = invoice_amount_msats - quote_msats; - Some( - to_unit(partial_msats, &CurrencyUnit::Msat, &melt_quote.unit) - .map_err(|_| Error::UnsupportedUnit)?, - ) - } + true => Some( + to_unit(quote_msats, &CurrencyUnit::Msat, &melt_quote.unit) + .map_err(|_| Error::UnsupportedUnit)?, + ), false => None, }; @@ -273,9 +270,11 @@ impl Mint { if amount_to_pay + melt_quote.fee_reserve > inputs_amount_quote_unit { tracing::debug!( - "Not enough inputs provided: {} msats needed {} msats", + "Not enough inputs provided: {} {} needed {} {}", inputs_amount_quote_unit, - amount_to_pay + melt_quote.unit, + amount_to_pay, + melt_quote.unit ); return Err(Error::TransactionUnbalanced(