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