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 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 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 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 self.payment_hashes.write().await.remove(&payment_hash);
208
209 Ok(())
210 }
211
212 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 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, });
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 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 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 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 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 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 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 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 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 {
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 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 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 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 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 "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 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 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 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 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 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 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 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(), r#type: 4, 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 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 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
1437struct 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 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}