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