fedimint_lightning/
lnd.rs

1use std::collections::BTreeSet;
2use std::fmt::{self, Display};
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use anyhow::ensure;
8use async_trait::async_trait;
9use bitcoin::hashes::{sha256, Hash};
10use fedimint_core::task::{sleep, TaskGroup};
11use fedimint_core::{secp256k1, Amount, BitcoinAmountOrAll};
12use fedimint_ln_common::contracts::Preimage;
13use fedimint_ln_common::route_hints::{RouteHint, RouteHintHop};
14use fedimint_ln_common::PrunedInvoice;
15use fedimint_lnv2_common::contracts::PaymentImage;
16use hex::ToHex;
17use secp256k1::PublicKey;
18use tokio::sync::{mpsc, RwLock};
19use tokio_stream::wrappers::ReceiverStream;
20use tonic_lnd::invoicesrpc::lookup_invoice_msg::InvoiceRef;
21use tonic_lnd::invoicesrpc::{
22    AddHoldInvoiceRequest, CancelInvoiceMsg, LookupInvoiceMsg, SettleInvoiceMsg,
23    SubscribeSingleInvoiceRequest,
24};
25use tonic_lnd::lnrpc::channel_point::FundingTxid;
26use tonic_lnd::lnrpc::failure::FailureCode;
27use tonic_lnd::lnrpc::invoice::InvoiceState;
28use tonic_lnd::lnrpc::payment::PaymentStatus;
29use tonic_lnd::lnrpc::{
30    ChanInfoRequest, ChannelBalanceRequest, ChannelPoint, CloseChannelRequest, ConnectPeerRequest,
31    GetInfoRequest, Invoice, InvoiceSubscription, LightningAddress, ListChannelsRequest,
32    ListInvoiceRequest, ListPeersRequest, OpenChannelRequest, SendCoinsRequest,
33    WalletBalanceRequest,
34};
35use tonic_lnd::routerrpc::{
36    CircuitKey, ForwardHtlcInterceptResponse, ResolveHoldForwardAction, SendPaymentRequest,
37    TrackPaymentRequest,
38};
39use tonic_lnd::tonic::Code;
40use tonic_lnd::walletrpc::AddrRequest;
41use tonic_lnd::{connect, Client as LndClient};
42use tracing::{debug, error, info, trace, warn};
43
44use super::{
45    ChannelInfo, ILnRpcClient, LightningRpcError, ListActiveChannelsResponse, RouteHtlcStream,
46    MAX_LIGHTNING_RETRIES,
47};
48use crate::{
49    CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, CreateInvoiceRequest,
50    CreateInvoiceResponse, GetBalancesResponse, GetLnOnchainAddressResponse, GetNodeInfoResponse,
51    GetRouteHintsResponse, InterceptPaymentRequest, InterceptPaymentResponse, InvoiceDescription,
52    LightningV2Manager, OpenChannelResponse, PayInvoiceResponse, PaymentAction, SendOnchainRequest,
53    SendOnchainResponse,
54};
55
56type HtlcSubscriptionSender = mpsc::Sender<InterceptPaymentRequest>;
57
58const LND_PAYMENT_TIMEOUT_SECONDS: i32 = 180;
59
60#[derive(Clone)]
61pub struct GatewayLndClient {
62    /// LND client
63    address: String,
64    tls_cert: String,
65    macaroon: String,
66    lnd_sender: Option<mpsc::Sender<ForwardHtlcInterceptResponse>>,
67    payment_hashes: Arc<RwLock<BTreeSet<Vec<u8>>>>,
68    lnv2_manager: Arc<dyn LightningV2Manager>,
69}
70
71impl GatewayLndClient {
72    pub fn new(
73        address: String,
74        tls_cert: String,
75        macaroon: String,
76        lnd_sender: Option<mpsc::Sender<ForwardHtlcInterceptResponse>>,
77        lnv2_manager: Arc<dyn LightningV2Manager>,
78    ) -> Self {
79        info!(
80            "Gateway configured to connect to LND LnRpcClient at \n address: {},\n tls cert path: {},\n macaroon path: {} ",
81            address, tls_cert, macaroon
82        );
83        GatewayLndClient {
84            address,
85            tls_cert,
86            macaroon,
87            lnd_sender,
88            lnv2_manager,
89            payment_hashes: Arc::new(RwLock::new(BTreeSet::new())),
90        }
91    }
92
93    async fn connect(&self) -> Result<LndClient, LightningRpcError> {
94        let mut retries = 0;
95        let client = loop {
96            if retries >= MAX_LIGHTNING_RETRIES {
97                return Err(LightningRpcError::FailedToConnect);
98            }
99
100            retries += 1;
101
102            match connect(
103                self.address.clone(),
104                self.tls_cert.clone(),
105                self.macaroon.clone(),
106            )
107            .await
108            {
109                Ok(client) => break client,
110                Err(e) => {
111                    tracing::debug!("Couldn't connect to LND, retrying in 1 second... {e:?}");
112                    sleep(Duration::from_secs(1)).await;
113                }
114            }
115        };
116
117        Ok(client)
118    }
119
120    /// Spawns a new background task that subscribes to updates of a specific
121    /// HOLD invoice. When the HOLD invoice is ACCEPTED, we can request the
122    /// preimage from the Gateway. A new task is necessary because LND's
123    /// global `subscribe_invoices` does not currently emit updates for HOLD invoices: <https://github.com/lightningnetwork/lnd/issues/3120>
124    async fn spawn_lnv2_hold_invoice_subscription(
125        &self,
126        task_group: &TaskGroup,
127        gateway_sender: HtlcSubscriptionSender,
128        payment_hash: Vec<u8>,
129    ) -> Result<(), LightningRpcError> {
130        let mut client = self.connect().await?;
131
132        let self_copy = self.clone();
133        let r_hash = payment_hash.clone();
134        task_group.spawn("LND HOLD Invoice Subscription", |handle| async move {
135            let future_stream =
136                client
137                    .invoices()
138                    .subscribe_single_invoice(SubscribeSingleInvoiceRequest {
139                        r_hash: r_hash.clone(),
140                    });
141
142            let mut hold_stream = tokio::select! {
143                stream = future_stream => {
144                    match stream {
145                        Ok(stream) => stream.into_inner(),
146                        Err(e) => {
147                            error!(?e, "Failed to subscribe to hold invoice updates");
148                            return;
149                        }
150                    }
151                },
152                () = handle.make_shutdown_rx() => {
153                    info!("LND HOLD Invoice Subscription received shutdown signal");
154                    return;
155                }
156            };
157
158            while let Some(hold) = tokio::select! {
159                () = handle.make_shutdown_rx() => {
160                    None
161                }
162                hold_update = hold_stream.message() => {
163                    match hold_update {
164                        Ok(hold) => hold,
165                        Err(e) => {
166                            error!(?e, "Error received over hold invoice update stream");
167                            None
168                        }
169                    }
170                }
171            } {
172                debug!(
173                    ?hold,
174                    "LND HOLD Invoice Update {}",
175                    PrettyPaymentHash(&r_hash)
176                );
177
178                if hold.state() == InvoiceState::Accepted {
179                    let intercept = InterceptPaymentRequest {
180                        payment_hash: Hash::from_slice(&hold.r_hash.clone())
181                            .expect("Failed to convert to Hash"),
182                        amount_msat: hold.amt_paid_msat as u64,
183                        // The rest of the fields are not used in LNv2 and can be removed once LNv1
184                        // support is over
185                        expiry: hold.expiry as u32,
186                        short_channel_id: Some(0),
187                        incoming_chan_id: 0,
188                        htlc_id: 0,
189                    };
190
191                    match gateway_sender.send(intercept).await {
192                        Ok(()) => {}
193                        Err(e) => {
194                            error!(
195                                ?e,
196                                "Hold Invoice Subscription failed to send Intercept to gateway"
197                            );
198                            let _ = self_copy.cancel_hold_invoice(hold.r_hash).await;
199                        }
200                    }
201                }
202            }
203        });
204
205        // Invoice monitor task has already spawned, we can safely remove it from the
206        // set
207        self.payment_hashes.write().await.remove(&payment_hash);
208
209        Ok(())
210    }
211
212    /// Spawns a new background task that subscribes to "add" updates for all
213    /// invoices. This is used to detect when a new invoice has been
214    /// created. If this invoice is a HOLD invoice, it is potentially destined
215    /// for a federation. At this point, we spawn a separate task to monitor the
216    /// status of the HOLD invoice.
217    async fn spawn_lnv2_invoice_subscription(
218        &self,
219        task_group: &TaskGroup,
220        gateway_sender: HtlcSubscriptionSender,
221    ) -> Result<(), LightningRpcError> {
222        let mut client = self.connect().await?;
223
224        // Compute the minimum `add_index` that we need to subscribe to updates for.
225        let add_index = client
226            .lightning()
227            .list_invoices(ListInvoiceRequest {
228                pending_only: true,
229                index_offset: 0,
230                num_max_invoices: u64::MAX,
231                reversed: false,
232            })
233            .await
234            .map_err(|status| {
235                error!(?status, "Failed to list all invoices");
236                LightningRpcError::FailedToRouteHtlcs {
237                    failure_reason: "Failed to list all invoices".to_string(),
238                }
239            })?
240            .into_inner()
241            .first_index_offset;
242
243        let self_copy = self.clone();
244        let hold_group = task_group.make_subgroup();
245        task_group.spawn("LND Invoice Subscription", move |handle| async move {
246            let future_stream = client.lightning().subscribe_invoices(InvoiceSubscription {
247                add_index,
248                settle_index: u64::MAX, // we do not need settle invoice events
249            });
250            let mut invoice_stream = tokio::select! {
251                stream = future_stream => {
252                    match stream {
253                        Ok(stream) => stream.into_inner(),
254                        Err(e) => {
255                            error!(?e, "Failed to subscribe to all invoice updates");
256                            return;
257                        }
258                    }
259                },
260                () = handle.make_shutdown_rx() => {
261                    info!("LND Invoice Subscription received shutdown signal");
262                    return;
263                }
264            };
265
266            info!("LND Invoice Subscription: starting to process invoice updates");
267            while let Some(invoice) = tokio::select! {
268                () = handle.make_shutdown_rx() => {
269                    info!("LND Invoice Subscription task received shutdown signal");
270                    None
271                }
272                invoice_update = invoice_stream.message() => {
273                    match invoice_update {
274                        Ok(invoice) => invoice,
275                        Err(e) => {
276                            error!(?e, "Error received over invoice update stream");
277                            None
278                        }
279                    }
280                }
281            } {
282                // If the `r_preimage` is empty and the invoice is OPEN, this means a new HOLD
283                // invoice has been created, which is potentially an invoice destined for a
284                // federation. We will spawn a new task to monitor the status of
285                // the HOLD invoice.
286                let payment_hash = invoice.r_hash.clone();
287
288                let created_payment_hash = self_copy
289                    .payment_hashes
290                    .read()
291                    .await
292                    .contains(&payment_hash);
293
294                let db_contains_payment_hash = self_copy
295                    .lnv2_manager
296                    .contains_incoming_contract(PaymentImage::Hash(sha256::Hash::from_byte_array(
297                        payment_hash
298                            .clone()
299                            .try_into()
300                            .expect("Malformatted payment hash"),
301                    )))
302                    .await;
303
304                let contains_payment_hash = created_payment_hash || db_contains_payment_hash;
305
306                debug!(
307                    ?invoice,
308                    ?created_payment_hash,
309                    ?db_contains_payment_hash,
310                    "LND Invoice Update {}",
311                    PrettyPaymentHash(&payment_hash),
312                );
313
314                if contains_payment_hash
315                    && invoice.r_preimage.is_empty()
316                    && invoice.state() == InvoiceState::Open
317                {
318                    info!(
319                        "Monitoring new LNv2 invoice with {}",
320                        PrettyPaymentHash(&payment_hash)
321                    );
322                    if let Err(e) = self_copy
323                        .spawn_lnv2_hold_invoice_subscription(
324                            &hold_group,
325                            gateway_sender.clone(),
326                            payment_hash.clone(),
327                        )
328                        .await
329                    {
330                        error!(
331                            ?e,
332                            "Failed to spawn HOLD invoice subscription task {}",
333                            PrettyPaymentHash(&payment_hash),
334                        );
335                    }
336                }
337            }
338        });
339
340        Ok(())
341    }
342
343    /// Spawns a new background task that intercepts HTLCs from the LND node. In
344    /// the LNv1 protocol, this is used as a trigger mechanism for
345    /// requesting the Gateway to retrieve the preimage for a payment.
346    async fn spawn_lnv1_htlc_interceptor(
347        &self,
348        task_group: &TaskGroup,
349        lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
350        lnd_rx: mpsc::Receiver<ForwardHtlcInterceptResponse>,
351        gateway_sender: HtlcSubscriptionSender,
352    ) -> Result<(), LightningRpcError> {
353        let mut client = self.connect().await?;
354
355        // Verify that LND is reachable via RPC before attempting to spawn a new thread
356        // that will intercept HTLCs.
357        client
358            .lightning()
359            .get_info(GetInfoRequest {})
360            .await
361            .map_err(|status| LightningRpcError::FailedToGetNodeInfo {
362                failure_reason: format!("Failed to get node info {status:?}"),
363            })?;
364
365        task_group.spawn("LND HTLC Subscription", |handle| async move {
366                let future_stream = client
367                    .router()
368                    .htlc_interceptor(ReceiverStream::new(lnd_rx));
369                let mut htlc_stream = tokio::select! {
370                    stream = future_stream => {
371                        match stream {
372                            Ok(stream) => stream.into_inner(),
373                            Err(e) => {
374                                error!("Failed to establish htlc stream");
375                                let e = LightningRpcError::FailedToGetRouteHints {
376                                    failure_reason: format!("Failed to subscribe to LND htlc stream {e:?}"),
377                                };
378                                debug!("Error: {e}");
379                                return;
380                            }
381                        }
382                    },
383                    () = handle.make_shutdown_rx() => {
384                        info!("LND HTLC Subscription received shutdown signal while trying to intercept HTLC stream, exiting...");
385                        return;
386                    }
387                };
388
389                debug!("LND HTLC Subscription: starting to process stream");
390                // To gracefully handle shutdown signals, we need to be able to receive signals
391                // while waiting for the next message from the HTLC stream.
392                //
393                // If we're in the middle of processing a message from the stream, we need to
394                // finish before stopping the spawned task. Checking if the task group is
395                // shutting down at the start of each iteration will cause shutdown signals to
396                // not process until another message arrives from the HTLC stream, which may
397                // take a long time, or never.
398                while let Some(htlc) = tokio::select! {
399                    () = handle.make_shutdown_rx() => {
400                        info!("LND HTLC Subscription task received shutdown signal");
401                        None
402                    }
403                    htlc_message = htlc_stream.message() => {
404                        match htlc_message {
405                            Ok(htlc) => htlc,
406                            Err(e) => {
407                                error!(?e, "Error received over HTLC stream");
408                                None
409                            }
410                    }}
411                } {
412                    trace!("LND HTLC Subscription: handling htlc {htlc:?}");
413
414                    if htlc.incoming_circuit_key.is_none() {
415                        error!("Cannot route htlc with None incoming_circuit_key");
416                        continue;
417                    }
418
419                    let incoming_circuit_key = htlc.incoming_circuit_key.unwrap();
420
421                    // Forward all HTLCs to gatewayd, gatewayd will filter them based on scid
422                    let intercept = InterceptPaymentRequest {
423                        payment_hash: Hash::from_slice(&htlc.payment_hash).expect("Failed to convert payment Hash"),
424                        amount_msat: htlc.outgoing_amount_msat,
425                        expiry: htlc.incoming_expiry,
426                        short_channel_id: Some(htlc.outgoing_requested_chan_id),
427                        incoming_chan_id: incoming_circuit_key.chan_id,
428                        htlc_id: incoming_circuit_key.htlc_id,
429                    };
430
431                    match gateway_sender.send(intercept).await {
432                        Ok(()) => {}
433                        Err(e) => {
434                            error!("Failed to send HTLC to gatewayd for processing: {:?}", e);
435                            let _ = Self::cancel_htlc(incoming_circuit_key, lnd_sender.clone())
436                                .await
437                                .map_err(|e| {
438                                    error!("Failed to cancel HTLC: {:?}", e);
439                                });
440                        }
441                    }
442                }
443            });
444
445        Ok(())
446    }
447
448    /// Spawns background tasks for monitoring the status of incoming payments.
449    async fn spawn_interceptor(
450        &self,
451        task_group: &TaskGroup,
452        lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
453        lnd_rx: mpsc::Receiver<ForwardHtlcInterceptResponse>,
454        gateway_sender: HtlcSubscriptionSender,
455    ) -> Result<(), LightningRpcError> {
456        self.spawn_lnv1_htlc_interceptor(task_group, lnd_sender, lnd_rx, gateway_sender.clone())
457            .await?;
458
459        self.spawn_lnv2_invoice_subscription(task_group, gateway_sender)
460            .await?;
461
462        Ok(())
463    }
464
465    async fn cancel_htlc(
466        key: CircuitKey,
467        lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
468    ) -> Result<(), LightningRpcError> {
469        // TODO: Specify a failure code and message
470        let response = ForwardHtlcInterceptResponse {
471            incoming_circuit_key: Some(key),
472            action: ResolveHoldForwardAction::Fail.into(),
473            preimage: vec![],
474            failure_message: vec![],
475            failure_code: FailureCode::TemporaryChannelFailure.into(),
476        };
477        Self::send_lnd_response(lnd_sender, response).await
478    }
479
480    async fn send_lnd_response(
481        lnd_sender: mpsc::Sender<ForwardHtlcInterceptResponse>,
482        response: ForwardHtlcInterceptResponse,
483    ) -> Result<(), LightningRpcError> {
484        // TODO: Consider retrying this if the send fails
485        lnd_sender.send(response).await.map_err(|send_error| {
486            LightningRpcError::FailedToCompleteHtlc {
487                failure_reason: format!(
488                    "Failed to send ForwardHtlcInterceptResponse to LND {send_error:?}"
489                ),
490            }
491        })
492    }
493
494    async fn lookup_payment(
495        &self,
496        payment_hash: Vec<u8>,
497        client: &mut LndClient,
498    ) -> Result<Option<String>, LightningRpcError> {
499        // Loop until we successfully get the status of the payment, or determine that
500        // the payment has not been made yet.
501        loop {
502            let payments = client
503                .router()
504                .track_payment_v2(TrackPaymentRequest {
505                    payment_hash: payment_hash.clone(),
506                    no_inflight_updates: true,
507                })
508                .await;
509
510            match payments {
511                Ok(payments) => {
512                    // Block until LND returns the completed payment
513                    if let Some(payment) =
514                        payments.into_inner().message().await.map_err(|status| {
515                            LightningRpcError::FailedPayment {
516                                failure_reason: status.message().to_string(),
517                            }
518                        })?
519                    {
520                        if payment.status() == PaymentStatus::Succeeded {
521                            return Ok(Some(payment.payment_preimage));
522                        }
523
524                        let failure_reason = payment.failure_reason();
525                        return Err(LightningRpcError::FailedPayment {
526                            failure_reason: format!("{failure_reason:?}"),
527                        });
528                    }
529                }
530                Err(e) => {
531                    // Break if we got a response back from the LND node that indicates the payment
532                    // hash was not found.
533                    if e.code() == Code::NotFound {
534                        return Ok(None);
535                    }
536
537                    warn!("Could not get the status of payment {payment_hash:?} Error: {e:?}. Trying again in 5 seconds");
538                    sleep(Duration::from_secs(5)).await;
539                }
540            }
541        }
542    }
543
544    /// Settles a HOLD invoice that is specified by the `payment_hash` with the
545    /// given `preimage`. If there is no invoice corresponding to the
546    /// `payment_hash`, this function will return an error.
547    async fn settle_hold_invoice(
548        &self,
549        payment_hash: Vec<u8>,
550        preimage: Preimage,
551    ) -> Result<(), LightningRpcError> {
552        let mut client = self.connect().await?;
553        let invoice = client
554            .invoices()
555            .lookup_invoice_v2(LookupInvoiceMsg {
556                invoice_ref: Some(InvoiceRef::PaymentHash(payment_hash.clone())),
557                lookup_modifier: 0,
558            })
559            .await
560            .map_err(|_| LightningRpcError::FailedToCompleteHtlc {
561                failure_reason: "Hold invoice does not exist".to_string(),
562            })?
563            .into_inner();
564
565        let state = invoice.state();
566        if state != InvoiceState::Accepted {
567            error!(
568                ?state,
569                "HOLD invoice state is not accepted {}",
570                PrettyPaymentHash(&payment_hash)
571            );
572            return Err(LightningRpcError::FailedToCompleteHtlc {
573                failure_reason: "HOLD invoice state is not accepted".to_string(),
574            });
575        }
576
577        client
578            .invoices()
579            .settle_invoice(SettleInvoiceMsg {
580                preimage: preimage.0.to_vec(),
581            })
582            .await
583            .map_err(|e| {
584                error!(
585                    ?e,
586                    "Failed to settle HOLD invoice {}",
587                    PrettyPaymentHash(&payment_hash)
588                );
589                LightningRpcError::FailedToCompleteHtlc {
590                    failure_reason: "Failed to settle HOLD invoice".to_string(),
591                }
592            })?;
593
594        Ok(())
595    }
596
597    /// Cancels a HOLD invoice that is specified by the `payment_hash`.
598    /// If there is no invoice corresponding to the `payment_hash`, this
599    /// function will return an error.
600    async fn cancel_hold_invoice(&self, payment_hash: Vec<u8>) -> Result<(), LightningRpcError> {
601        let mut client = self.connect().await?;
602        let invoice = client
603            .invoices()
604            .lookup_invoice_v2(LookupInvoiceMsg {
605                invoice_ref: Some(InvoiceRef::PaymentHash(payment_hash.clone())),
606                lookup_modifier: 0,
607            })
608            .await
609            .map_err(|_| LightningRpcError::FailedToCompleteHtlc {
610                failure_reason: "Hold invoice does not exist".to_string(),
611            })?
612            .into_inner();
613
614        let state = invoice.state();
615        if state != InvoiceState::Open {
616            warn!(?state, "Trying to cancel HOLD invoice with {} that is not OPEN, gateway likely encountered an issue", PrettyPaymentHash(&payment_hash));
617        }
618
619        client
620            .invoices()
621            .cancel_invoice(CancelInvoiceMsg {
622                payment_hash: payment_hash.clone(),
623            })
624            .await
625            .map_err(|e| {
626                error!(
627                    ?e,
628                    "Failed to cancel HOLD invoice {}",
629                    PrettyPaymentHash(&payment_hash)
630                );
631                LightningRpcError::FailedToCompleteHtlc {
632                    failure_reason: "Failed to cancel HOLD invoice".to_string(),
633                }
634            })?;
635
636        Ok(())
637    }
638}
639
640impl fmt::Debug for GatewayLndClient {
641    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
642        write!(f, "LndClient")
643    }
644}
645
646#[async_trait]
647impl ILnRpcClient for GatewayLndClient {
648    async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
649        let mut client = self.connect().await?;
650        let info = client
651            .lightning()
652            .get_info(GetInfoRequest {})
653            .await
654            .map_err(|status| LightningRpcError::FailedToGetNodeInfo {
655                failure_reason: format!("Failed to get node info {status:?}"),
656            })?
657            .into_inner();
658
659        let pub_key: PublicKey =
660            info.identity_pubkey
661                .parse()
662                .map_err(|e| LightningRpcError::FailedToGetNodeInfo {
663                    failure_reason: format!("Failed to parse public key {e:?}"),
664                })?;
665
666        let network = match info
667            .chains
668            .first()
669            .ok_or_else(|| LightningRpcError::FailedToGetNodeInfo {
670                failure_reason: "Failed to parse node network".to_string(),
671            })?
672            .network
673            .as_str()
674        {
675            // LND uses "mainnet", but rust-bitcoin uses "bitcoin".
676            // TODO: create a fedimint `Network` type that understands "mainnet"
677            "mainnet" => "bitcoin",
678            other => other,
679        }
680        .to_string();
681
682        return Ok(GetNodeInfoResponse {
683            pub_key,
684            alias: info.alias,
685            network,
686            block_height: info.block_height,
687            synced_to_chain: info.synced_to_chain,
688        });
689    }
690
691    async fn routehints(
692        &self,
693        num_route_hints: usize,
694    ) -> Result<GetRouteHintsResponse, LightningRpcError> {
695        let mut client = self.connect().await?;
696        let mut channels = client
697            .lightning()
698            .list_channels(ListChannelsRequest {
699                active_only: true,
700                inactive_only: false,
701                public_only: false,
702                private_only: false,
703                peer: vec![],
704            })
705            .await
706            .map_err(|status| LightningRpcError::FailedToGetRouteHints {
707                failure_reason: format!("Failed to list channels {status:?}"),
708            })?
709            .into_inner()
710            .channels;
711
712        // Take the channels with the largest incoming capacity
713        channels.sort_by(|a, b| b.remote_balance.cmp(&a.remote_balance));
714        channels.truncate(num_route_hints);
715
716        let mut route_hints: Vec<RouteHint> = vec![];
717        for chan in &channels {
718            let info = client
719                .lightning()
720                .get_chan_info(ChanInfoRequest {
721                    chan_id: chan.chan_id,
722                })
723                .await
724                .map_err(|status| LightningRpcError::FailedToGetRouteHints {
725                    failure_reason: format!("Failed to get channel info {status:?}"),
726                })?
727                .into_inner();
728
729            let Some(policy) = info.node1_policy.clone() else {
730                continue;
731            };
732            let src_node_id =
733                PublicKey::from_str(&chan.remote_pubkey).expect("Failed to parse pubkey");
734            let short_channel_id = chan.chan_id;
735            let base_msat = policy.fee_base_msat as u32;
736            let proportional_millionths = policy.fee_rate_milli_msat as u32;
737            let cltv_expiry_delta = policy.time_lock_delta;
738            let htlc_maximum_msat = Some(policy.max_htlc_msat);
739            let htlc_minimum_msat = Some(policy.min_htlc as u64);
740
741            let route_hint_hop = RouteHintHop {
742                src_node_id,
743                short_channel_id,
744                base_msat,
745                proportional_millionths,
746                cltv_expiry_delta: cltv_expiry_delta as u16,
747                htlc_minimum_msat,
748                htlc_maximum_msat,
749            };
750            route_hints.push(RouteHint(vec![route_hint_hop]));
751        }
752
753        Ok(GetRouteHintsResponse { route_hints })
754    }
755
756    async fn pay_private(
757        &self,
758        invoice: PrunedInvoice,
759        max_delay: u64,
760        max_fee: Amount,
761    ) -> Result<PayInvoiceResponse, LightningRpcError> {
762        let payment_hash = invoice.payment_hash.to_byte_array().to_vec();
763        info!(
764            "LND Paying invoice with {}",
765            PrettyPaymentHash(&payment_hash)
766        );
767        let mut client = self.connect().await?;
768
769        debug!(
770            ?invoice,
771            "pay_private checking if payment for invoice exists"
772        );
773
774        // If the payment exists, that means we've already tried to pay the invoice
775        let preimage: Vec<u8> = if let Some(preimage) = self
776            .lookup_payment(invoice.payment_hash.to_byte_array().to_vec(), &mut client)
777            .await?
778        {
779            info!(
780                "LND payment already exists for invoice with {}",
781                PrettyPaymentHash(&payment_hash)
782            );
783            hex::FromHex::from_hex(preimage.as_str()).map_err(|error| {
784                LightningRpcError::FailedPayment {
785                    failure_reason: format!("Failed to convert preimage {error:?}"),
786                }
787            })?
788        } else {
789            // LND API allows fee limits in the `i64` range, but we use `u64` for
790            // max_fee_msat. This means we can only set an enforceable fee limit
791            // between 0 and i64::MAX
792            let fee_limit_msat: i64 =
793                max_fee
794                    .msats
795                    .try_into()
796                    .map_err(|error| LightningRpcError::FailedPayment {
797                        failure_reason: format!(
798                            "max_fee_msat exceeds valid LND fee limit ranges {error:?}"
799                        ),
800                    })?;
801
802            let amt_msat = invoice.amount.msats.try_into().map_err(|error| {
803                LightningRpcError::FailedPayment {
804                    failure_reason: format!("amount exceeds valid LND amount ranges {error:?}"),
805                }
806            })?;
807            let final_cltv_delta = invoice.min_final_cltv_delta.try_into().map_err(|error| {
808                LightningRpcError::FailedPayment {
809                    failure_reason: format!("final cltv delta exceeds valid LND range {error:?}"),
810                }
811            })?;
812            let cltv_limit =
813                max_delay
814                    .try_into()
815                    .map_err(|error| LightningRpcError::FailedPayment {
816                        failure_reason: format!("max delay exceeds valid LND range {error:?}"),
817                    })?;
818
819            let dest_features = wire_features_to_lnd_feature_vec(&invoice.destination_features)
820                .map_err(|e| LightningRpcError::FailedPayment {
821                    failure_reason: e.to_string(),
822                })?;
823
824            debug!(
825                "LND payment does not exist for invoice with {}, will attempt to pay",
826                PrettyPaymentHash(&payment_hash)
827            );
828            let payments = client
829                .router()
830                .send_payment_v2(SendPaymentRequest {
831                    amt_msat,
832                    dest: invoice.destination.serialize().to_vec(),
833                    dest_features,
834                    payment_hash: invoice.payment_hash.to_byte_array().to_vec(),
835                    payment_addr: invoice.payment_secret.to_vec(),
836                    route_hints: route_hints_to_lnd(&invoice.route_hints),
837                    final_cltv_delta,
838                    cltv_limit,
839                    no_inflight_updates: false,
840                    timeout_seconds: LND_PAYMENT_TIMEOUT_SECONDS,
841                    fee_limit_msat,
842                    ..Default::default()
843                })
844                .await
845                .map_err(|status| {
846                    error!(
847                        "LND payment request failed for invoice with {} with {status:?}",
848                        PrettyPaymentHash(&payment_hash)
849                    );
850                    LightningRpcError::FailedPayment {
851                        failure_reason: format!("Failed to make outgoing payment {status:?}"),
852                    }
853                })?;
854
855            debug!(
856                "LND payment request sent for invoice with {}, waiting for payment status...",
857                PrettyPaymentHash(&payment_hash),
858            );
859            let mut messages = payments.into_inner();
860            loop {
861                match messages
862                    .message()
863                    .await
864                    .map_err(|error| LightningRpcError::FailedPayment {
865                        failure_reason: format!("Failed to get payment status {error:?}"),
866                    }) {
867                    Ok(Some(payment)) if payment.status() == PaymentStatus::Succeeded => {
868                        info!(
869                            "LND payment succeeded for invoice with {}",
870                            PrettyPaymentHash(&payment_hash)
871                        );
872                        break hex::FromHex::from_hex(payment.payment_preimage.as_str()).map_err(
873                            |error| LightningRpcError::FailedPayment {
874                                failure_reason: format!("Failed to convert preimage {error:?}"),
875                            },
876                        )?;
877                    }
878                    Ok(Some(payment)) if payment.status() == PaymentStatus::InFlight => {
879                        debug!(
880                            "LND payment for invoice with {} is inflight",
881                            PrettyPaymentHash(&payment_hash)
882                        );
883                        continue;
884                    }
885                    Ok(Some(payment)) => {
886                        error!(
887                            "LND payment failed for invoice with {} with {payment:?}",
888                            PrettyPaymentHash(&payment_hash)
889                        );
890                        let failure_reason = payment.failure_reason();
891                        return Err(LightningRpcError::FailedPayment {
892                            failure_reason: format!("{failure_reason:?}"),
893                        });
894                    }
895                    Ok(None) => {
896                        error!(
897                            "LND payment failed for invoice with {} with no payment status",
898                            PrettyPaymentHash(&payment_hash)
899                        );
900                        return Err(LightningRpcError::FailedPayment {
901                            failure_reason: format!(
902                                "Failed to get payment status for payment hash {:?}",
903                                invoice.payment_hash
904                            ),
905                        });
906                    }
907                    Err(e) => {
908                        error!(
909                            "LND payment failed for invoice with {} with {e:?}",
910                            PrettyPaymentHash(&payment_hash)
911                        );
912                        return Err(e);
913                    }
914                }
915            }
916        };
917        Ok(PayInvoiceResponse {
918            preimage: Preimage(preimage.try_into().expect("Failed to create preimage")),
919        })
920    }
921
922    /// Returns true if the lightning backend supports payments without full
923    /// invoices
924    fn supports_private_payments(&self) -> bool {
925        true
926    }
927
928    async fn route_htlcs<'a>(
929        self: Box<Self>,
930        task_group: &TaskGroup,
931    ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
932        const CHANNEL_SIZE: usize = 100;
933
934        // Channel to send intercepted htlc to the gateway for processing
935        let (gateway_sender, gateway_receiver) =
936            mpsc::channel::<InterceptPaymentRequest>(CHANNEL_SIZE);
937
938        let (lnd_sender, lnd_rx) = mpsc::channel::<ForwardHtlcInterceptResponse>(CHANNEL_SIZE);
939
940        self.spawn_interceptor(
941            task_group,
942            lnd_sender.clone(),
943            lnd_rx,
944            gateway_sender.clone(),
945        )
946        .await?;
947        let new_client = Arc::new(Self {
948            address: self.address.clone(),
949            tls_cert: self.tls_cert.clone(),
950            macaroon: self.macaroon.clone(),
951            lnd_sender: Some(lnd_sender.clone()),
952            lnv2_manager: self.lnv2_manager.clone(),
953            payment_hashes: self.payment_hashes.clone(),
954        });
955        Ok((Box::pin(ReceiverStream::new(gateway_receiver)), new_client))
956    }
957
958    async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError> {
959        let InterceptPaymentResponse {
960            action,
961            payment_hash,
962            incoming_chan_id,
963            htlc_id,
964        } = htlc;
965
966        let (action, preimage) = match action {
967            PaymentAction::Settle(preimage) => (ResolveHoldForwardAction::Settle, preimage),
968            PaymentAction::Cancel => (ResolveHoldForwardAction::Fail, Preimage([0; 32])),
969            PaymentAction::Forward => (ResolveHoldForwardAction::Resume, Preimage([0; 32])),
970        };
971
972        // First check if this completion request corresponds to a HOLD LNv2 invoice
973        match action {
974            ResolveHoldForwardAction::Settle => {
975                if let Ok(()) = self
976                    .settle_hold_invoice(payment_hash.to_byte_array().to_vec(), preimage.clone())
977                    .await
978                {
979                    info!("Successfully settled HOLD invoice {}", payment_hash);
980                    return Ok(());
981                }
982            }
983            _ => {
984                if let Ok(()) = self
985                    .cancel_hold_invoice(payment_hash.to_byte_array().to_vec())
986                    .await
987                {
988                    info!("Successfully canceled HOLD invoice {}", payment_hash);
989                    return Ok(());
990                }
991            }
992        }
993
994        // If we can't settle/cancel the payment via LNv2, try LNv1
995        if let Some(lnd_sender) = self.lnd_sender.clone() {
996            let response = ForwardHtlcInterceptResponse {
997                incoming_circuit_key: Some(CircuitKey {
998                    chan_id: incoming_chan_id,
999                    htlc_id,
1000                }),
1001                action: action.into(),
1002                preimage: preimage.0.to_vec(),
1003                failure_message: vec![],
1004                failure_code: FailureCode::TemporaryChannelFailure.into(),
1005            };
1006
1007            Self::send_lnd_response(lnd_sender, response).await?;
1008            return Ok(());
1009        }
1010
1011        error!("Gatewayd has not started to route HTLCs");
1012        Err(LightningRpcError::FailedToCompleteHtlc {
1013            failure_reason: "Gatewayd has not started to route HTLCs".to_string(),
1014        })
1015    }
1016
1017    async fn create_invoice(
1018        &self,
1019        create_invoice_request: CreateInvoiceRequest,
1020    ) -> Result<CreateInvoiceResponse, LightningRpcError> {
1021        let mut client = self.connect().await?;
1022        let description = create_invoice_request
1023            .description
1024            .unwrap_or(InvoiceDescription::Direct(String::new()));
1025
1026        if create_invoice_request.payment_hash.is_none() {
1027            let invoice = match description {
1028                InvoiceDescription::Direct(description) => Invoice {
1029                    memo: description,
1030                    value_msat: create_invoice_request.amount_msat as i64,
1031                    expiry: i64::from(create_invoice_request.expiry_secs),
1032                    ..Default::default()
1033                },
1034                InvoiceDescription::Hash(desc_hash) => Invoice {
1035                    description_hash: desc_hash.to_byte_array().to_vec(),
1036                    value_msat: create_invoice_request.amount_msat as i64,
1037                    expiry: i64::from(create_invoice_request.expiry_secs),
1038                    ..Default::default()
1039                },
1040            };
1041
1042            let add_invoice_response =
1043                client.lightning().add_invoice(invoice).await.map_err(|e| {
1044                    LightningRpcError::FailedToGetInvoice {
1045                        failure_reason: e.to_string(),
1046                    }
1047                })?;
1048
1049            let invoice = add_invoice_response.into_inner().payment_request;
1050            Ok(CreateInvoiceResponse { invoice })
1051        } else {
1052            let payment_hash = create_invoice_request
1053                .payment_hash
1054                .expect("Already checked payment hash")
1055                .to_byte_array()
1056                .to_vec();
1057            let hold_invoice_request = match description {
1058                InvoiceDescription::Direct(description) => AddHoldInvoiceRequest {
1059                    memo: description,
1060                    hash: payment_hash.clone(),
1061                    value_msat: create_invoice_request.amount_msat as i64,
1062                    expiry: i64::from(create_invoice_request.expiry_secs),
1063                    ..Default::default()
1064                },
1065                InvoiceDescription::Hash(desc_hash) => AddHoldInvoiceRequest {
1066                    description_hash: desc_hash.to_byte_array().to_vec(),
1067                    hash: payment_hash.clone(),
1068                    value_msat: create_invoice_request.amount_msat as i64,
1069                    expiry: i64::from(create_invoice_request.expiry_secs),
1070                    ..Default::default()
1071                },
1072            };
1073
1074            self.payment_hashes.write().await.insert(payment_hash);
1075
1076            let hold_invoice_response = client
1077                .invoices()
1078                .add_hold_invoice(hold_invoice_request)
1079                .await
1080                .map_err(|e| LightningRpcError::FailedToGetInvoice {
1081                    failure_reason: e.to_string(),
1082                })?;
1083
1084            let invoice = hold_invoice_response.into_inner().payment_request;
1085            Ok(CreateInvoiceResponse { invoice })
1086        }
1087    }
1088
1089    async fn get_ln_onchain_address(
1090        &self,
1091    ) -> Result<GetLnOnchainAddressResponse, LightningRpcError> {
1092        let mut client = self.connect().await?;
1093
1094        match client
1095            .wallet()
1096            .next_addr(AddrRequest {
1097                account: String::new(), // Default wallet account.
1098                r#type: 4,              // Taproot address.
1099                change: false,
1100            })
1101            .await
1102        {
1103            Ok(response) => Ok(GetLnOnchainAddressResponse {
1104                address: response.into_inner().addr,
1105            }),
1106            Err(e) => Err(LightningRpcError::FailedToGetLnOnchainAddress {
1107                failure_reason: format!("Failed to get funding address {e:?}"),
1108            }),
1109        }
1110    }
1111
1112    async fn send_onchain(
1113        &self,
1114        SendOnchainRequest {
1115            address,
1116            amount,
1117            fee_rate_sats_per_vbyte,
1118        }: SendOnchainRequest,
1119    ) -> Result<SendOnchainResponse, LightningRpcError> {
1120        #[allow(deprecated)]
1121        let request = match amount {
1122            BitcoinAmountOrAll::All => SendCoinsRequest {
1123                addr: address.assume_checked().to_string(),
1124                amount: 0,
1125                target_conf: 0,
1126                sat_per_vbyte: fee_rate_sats_per_vbyte,
1127                sat_per_byte: 0,
1128                send_all: true,
1129                label: String::new(),
1130                min_confs: 0,
1131                spend_unconfirmed: true,
1132            },
1133            BitcoinAmountOrAll::Amount(amount) => SendCoinsRequest {
1134                addr: address.assume_checked().to_string(),
1135                amount: amount.to_sat() as i64,
1136                target_conf: 0,
1137                sat_per_vbyte: fee_rate_sats_per_vbyte,
1138                sat_per_byte: 0,
1139                send_all: false,
1140                label: String::new(),
1141                min_confs: 0,
1142                spend_unconfirmed: true,
1143            },
1144        };
1145
1146        match self.connect().await?.lightning().send_coins(request).await {
1147            Ok(res) => Ok(SendOnchainResponse {
1148                txid: res.into_inner().txid,
1149            }),
1150            Err(e) => Err(LightningRpcError::FailedToWithdrawOnchain {
1151                failure_reason: format!("Failed to withdraw funds on-chain {e:?}"),
1152            }),
1153        }
1154    }
1155
1156    async fn open_channel(
1157        &self,
1158        crate::OpenChannelRequest {
1159            pubkey,
1160            host,
1161            channel_size_sats,
1162            push_amount_sats,
1163        }: crate::OpenChannelRequest,
1164    ) -> Result<OpenChannelResponse, LightningRpcError> {
1165        let mut client = self.connect().await?;
1166
1167        let peers = client
1168            .lightning()
1169            .list_peers(ListPeersRequest { latest_error: true })
1170            .await
1171            .map_err(|e| LightningRpcError::FailedToConnectToPeer {
1172                failure_reason: format!("Could not list peers: {e:?}"),
1173            })?
1174            .into_inner();
1175
1176        // Connect to the peer first if we are not connected already
1177        if !peers.peers.into_iter().any(|peer| {
1178            PublicKey::from_str(&peer.pub_key).expect("could not parse public key") == pubkey
1179        }) {
1180            client
1181                .lightning()
1182                .connect_peer(ConnectPeerRequest {
1183                    addr: Some(LightningAddress {
1184                        pubkey: pubkey.to_string(),
1185                        host,
1186                    }),
1187                    perm: false,
1188                    timeout: 10,
1189                })
1190                .await
1191                .map_err(|e| LightningRpcError::FailedToConnectToPeer {
1192                    failure_reason: format!("Failed to connect to peer {e:?}"),
1193                })?;
1194        }
1195
1196        // Open the channel
1197        match client
1198            .lightning()
1199            .open_channel_sync(OpenChannelRequest {
1200                node_pubkey: pubkey.serialize().to_vec(),
1201                local_funding_amount: channel_size_sats.try_into().expect("u64 -> i64"),
1202                push_sat: push_amount_sats.try_into().expect("u64 -> i64"),
1203                ..Default::default()
1204            })
1205            .await
1206        {
1207            Ok(res) => Ok(OpenChannelResponse {
1208                funding_txid: match res.into_inner().funding_txid {
1209                    Some(txid) => match txid {
1210                        FundingTxid::FundingTxidBytes(mut bytes) => {
1211                            bytes.reverse();
1212                            hex::encode(bytes)
1213                        }
1214                        FundingTxid::FundingTxidStr(str) => str,
1215                    },
1216                    None => String::new(),
1217                },
1218            }),
1219            Err(e) => Err(LightningRpcError::FailedToOpenChannel {
1220                failure_reason: format!("Failed to open channel {e:?}"),
1221            }),
1222        }
1223    }
1224
1225    async fn close_channels_with_peer(
1226        &self,
1227        CloseChannelsWithPeerRequest { pubkey }: CloseChannelsWithPeerRequest,
1228    ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
1229        let mut client = self.connect().await?;
1230
1231        let channels_with_peer = client
1232            .lightning()
1233            .list_channels(ListChannelsRequest {
1234                active_only: false,
1235                inactive_only: false,
1236                public_only: false,
1237                private_only: false,
1238                peer: pubkey.serialize().to_vec(),
1239            })
1240            .await
1241            .map_err(|e| LightningRpcError::FailedToCloseChannelsWithPeer {
1242                failure_reason: format!("Failed to list channels {e:?}"),
1243            })?
1244            .into_inner()
1245            .channels;
1246
1247        for channel in &channels_with_peer {
1248            let channel_point =
1249                bitcoin::OutPoint::from_str(&channel.channel_point).map_err(|e| {
1250                    LightningRpcError::FailedToCloseChannelsWithPeer {
1251                        failure_reason: format!("Failed to parse channel point {e:?}"),
1252                    }
1253                })?;
1254
1255            client
1256                .lightning()
1257                .close_channel(CloseChannelRequest {
1258                    channel_point: Some(ChannelPoint {
1259                        funding_txid: Some(
1260                            tonic_lnd::lnrpc::channel_point::FundingTxid::FundingTxidBytes(
1261                                <bitcoin::Txid as AsRef<[u8]>>::as_ref(&channel_point.txid)
1262                                    .to_vec(),
1263                            ),
1264                        ),
1265                        output_index: channel_point.vout,
1266                    }),
1267                    ..Default::default()
1268                })
1269                .await
1270                .map_err(|e| LightningRpcError::FailedToCloseChannelsWithPeer {
1271                    failure_reason: format!("Failed to close channel {e:?}"),
1272                })?;
1273        }
1274
1275        Ok(CloseChannelsWithPeerResponse {
1276            num_channels_closed: channels_with_peer.len() as u32,
1277        })
1278    }
1279
1280    async fn list_active_channels(&self) -> Result<ListActiveChannelsResponse, LightningRpcError> {
1281        let mut client = self.connect().await?;
1282
1283        match client
1284            .lightning()
1285            .list_channels(ListChannelsRequest {
1286                active_only: true,
1287                inactive_only: false,
1288                public_only: false,
1289                private_only: false,
1290                peer: vec![],
1291            })
1292            .await
1293        {
1294            Ok(response) => Ok(ListActiveChannelsResponse {
1295                channels: response
1296                    .into_inner()
1297                    .channels
1298                    .into_iter()
1299                    .map(|channel| {
1300                        let channel_size_sats = channel.capacity.try_into().expect("i64 -> u64");
1301
1302                        let local_balance_sats: u64 =
1303                            channel.local_balance.try_into().expect("i64 -> u64");
1304                        let local_channel_reserve_sats: u64 = match channel.local_constraints {
1305                            Some(constraints) => constraints.chan_reserve_sat,
1306                            None => 0,
1307                        };
1308
1309                        let outbound_liquidity_sats =
1310                            local_balance_sats.saturating_sub(local_channel_reserve_sats);
1311
1312                        let remote_balance_sats: u64 =
1313                            channel.remote_balance.try_into().expect("i64 -> u64");
1314                        let remote_channel_reserve_sats: u64 = match channel.remote_constraints {
1315                            Some(constraints) => constraints.chan_reserve_sat,
1316                            None => 0,
1317                        };
1318
1319                        let inbound_liquidity_sats =
1320                            remote_balance_sats.saturating_sub(remote_channel_reserve_sats);
1321
1322                        ChannelInfo {
1323                            remote_pubkey: PublicKey::from_str(&channel.remote_pubkey)
1324                                .expect("Lightning node returned invalid remote channel pubkey"),
1325                            channel_size_sats,
1326                            outbound_liquidity_sats,
1327                            inbound_liquidity_sats,
1328                            short_channel_id: channel.chan_id,
1329                        }
1330                    })
1331                    .collect(),
1332            }),
1333            Err(e) => Err(LightningRpcError::FailedToListActiveChannels {
1334                failure_reason: format!("Failed to list active channels {e:?}"),
1335            }),
1336        }
1337    }
1338
1339    async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
1340        let mut client = self.connect().await?;
1341
1342        let wallet_balance_response = client
1343            .lightning()
1344            .wallet_balance(WalletBalanceRequest {})
1345            .await
1346            .map_err(|e| LightningRpcError::FailedToGetBalances {
1347                failure_reason: format!("Failed to get on-chain balance {e:?}"),
1348            })?
1349            .into_inner();
1350
1351        let channel_balance_response = client
1352            .lightning()
1353            .channel_balance(ChannelBalanceRequest {})
1354            .await
1355            .map_err(|e| LightningRpcError::FailedToGetBalances {
1356                failure_reason: format!("Failed to get lightning balance {e:?}"),
1357            })?
1358            .into_inner();
1359        let total_outbound = channel_balance_response.local_balance.unwrap_or_default();
1360        let unsettled_outbound = channel_balance_response
1361            .unsettled_local_balance
1362            .unwrap_or_default();
1363        let pending_outbound = channel_balance_response
1364            .pending_open_local_balance
1365            .unwrap_or_default();
1366        let lightning_balance_msats =
1367            total_outbound.msat - unsettled_outbound.msat - pending_outbound.msat;
1368
1369        let total_inbound = channel_balance_response.remote_balance.unwrap_or_default();
1370        let unsettled_inbound = channel_balance_response
1371            .unsettled_remote_balance
1372            .unwrap_or_default();
1373        let pending_inbound = channel_balance_response
1374            .pending_open_remote_balance
1375            .unwrap_or_default();
1376        let inbound_lightning_liquidity_msats =
1377            total_inbound.msat - unsettled_inbound.msat - pending_inbound.msat;
1378
1379        Ok(GetBalancesResponse {
1380            onchain_balance_sats: (wallet_balance_response.total_balance
1381                + wallet_balance_response.reserved_balance_anchor_chan)
1382                as u64,
1383            lightning_balance_msats,
1384            inbound_lightning_liquidity_msats,
1385        })
1386    }
1387}
1388
1389fn route_hints_to_lnd(
1390    route_hints: &[fedimint_ln_common::route_hints::RouteHint],
1391) -> Vec<tonic_lnd::lnrpc::RouteHint> {
1392    route_hints
1393        .iter()
1394        .map(|hint| tonic_lnd::lnrpc::RouteHint {
1395            hop_hints: hint
1396                .0
1397                .iter()
1398                .map(|hop| tonic_lnd::lnrpc::HopHint {
1399                    node_id: hop.src_node_id.serialize().encode_hex(),
1400                    chan_id: hop.short_channel_id,
1401                    fee_base_msat: hop.base_msat,
1402                    fee_proportional_millionths: hop.proportional_millionths,
1403                    cltv_expiry_delta: u32::from(hop.cltv_expiry_delta),
1404                })
1405                .collect(),
1406        })
1407        .collect()
1408}
1409
1410fn wire_features_to_lnd_feature_vec(features_wire_encoded: &[u8]) -> anyhow::Result<Vec<i32>> {
1411    ensure!(
1412        features_wire_encoded.len() <= 1_000,
1413        "Will not process feature bit vectors larger than 1000 byte"
1414    );
1415
1416    let lnd_features = features_wire_encoded
1417        .iter()
1418        .rev()
1419        .enumerate()
1420        .flat_map(|(byte_idx, &feature_byte)| {
1421            (0..8).filter_map(move |bit_idx| {
1422                if (feature_byte & (1u8 << bit_idx)) != 0 {
1423                    Some(
1424                        i32::try_from(byte_idx * 8 + bit_idx)
1425                            .expect("Index will never exceed i32::MAX for feature vectors <8MB"),
1426                    )
1427                } else {
1428                    None
1429                }
1430            })
1431        })
1432        .collect::<Vec<_>>();
1433
1434    Ok(lnd_features)
1435}
1436
1437/// Utility struct for logging payment hashes. Useful for debugging.
1438struct PrettyPaymentHash<'a>(&'a Vec<u8>);
1439
1440impl Display for PrettyPaymentHash<'_> {
1441    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1442        write!(f, "payment_hash={}", self.0.encode_hex::<String>())
1443    }
1444}
1445
1446#[cfg(test)]
1447mod tests {
1448    use fedimint_core::encode_bolt11_invoice_features_without_length;
1449    use hex::FromHex;
1450    use lightning::ln::features::Bolt11InvoiceFeatures;
1451
1452    use super::wire_features_to_lnd_feature_vec;
1453
1454    #[test]
1455    fn features_to_lnd() {
1456        assert_eq!(
1457            wire_features_to_lnd_feature_vec(&[]).unwrap(),
1458            Vec::<i32>::new()
1459        );
1460
1461        let features_payment_secret = {
1462            let mut f = Bolt11InvoiceFeatures::empty();
1463            f.set_payment_secret_optional();
1464            encode_bolt11_invoice_features_without_length(&f)
1465        };
1466        assert_eq!(
1467            wire_features_to_lnd_feature_vec(&features_payment_secret).unwrap(),
1468            vec![15]
1469        );
1470
1471        // Phoenix feature flags
1472        let features_payment_secret =
1473            Vec::from_hex("20000000000000000000000002000000024100").unwrap();
1474        assert_eq!(
1475            wire_features_to_lnd_feature_vec(&features_payment_secret).unwrap(),
1476            vec![8, 14, 17, 49, 149]
1477        );
1478    }
1479}