فهرست منبع

[PATCH + BUGFIX] Multinut LND re-query and `use_mission_control` (#746)

* patch LND re-query and use mission control + bugfix on the melt verification

* remove unusued import

* chore: fmt

---------

Co-authored-by: thesimplekid <tsk@thesimplekid.com>
lollerfirst 1 ماه پیش
والد
کامیت
a4c2454e94
2فایلهای تغییر یافته به همراه74 افزوده شده و 67 حذف شده
  1. 66 58
      crates/cdk-lnd/src/lib.rs
  2. 8 9
      crates/cdk/src/mint/melt.rs

+ 66 - 58
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()
-                };
-
-                // 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();
+                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()
+                    };
 
-                let mut payment_response: HtlcAttempt = HtlcAttempt {
-                    ..Default::default()
-                };
+                    // 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();
 
-                // 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 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;
+                    // 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,
+                    });
                 }
 
-                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 {

+ 8 - 9
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(