1use std::collections::{BTreeMap, HashMap};
2use std::path::Path;
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::{Duration, UNIX_EPOCH};
6
7use async_trait::async_trait;
8use bitcoin::hashes::{Hash, sha256};
9use bitcoin::{FeeRate, Network, OutPoint};
10use fedimint_bip39::Mnemonic;
11use fedimint_core::envs::is_running_in_test_env;
12use fedimint_core::task::{TaskGroup, TaskHandle, block_in_place};
13use fedimint_core::util::{FmtCompact, SafeUrl};
14use fedimint_core::{Amount, BitcoinAmountOrAll, crit};
15use fedimint_gateway_common::{
16 ChainSource, ConnectPeerRequest, GetInvoiceRequest, GetInvoiceResponse,
17 ListTransactionsResponse, NodeAddress, SetChannelFeesRequest,
18};
19use fedimint_ln_common::contracts::Preimage;
20use fedimint_logging::{LOG_LIGHTNING, LOG_LIGHTNING_LDK};
21use ldk_node::config::ChannelConfig;
22use ldk_node::lightning::ln::msgs::SocketAddress;
23use ldk_node::lightning::routing::gossip::{NodeAlias, NodeId};
24use ldk_node::logger::{LogLevel, LogRecord, LogWriter};
25use ldk_node::payment::{PaymentDirection, PaymentKind, PaymentStatus, SendingParameters};
26use lightning::ln::channelmanager::PaymentId;
27use lightning::offers::offer::{Offer, OfferId};
28use lightning::types::payment::{PaymentHash, PaymentPreimage};
29use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
30use tokio::sync::mpsc::Sender;
31use tokio::sync::{RwLock, oneshot};
32use tokio_stream::wrappers::ReceiverStream;
33use tracing::{debug, error, info, trace, warn};
34
35use super::{ChannelInfo, ILnRpcClient, LightningRpcError, ListChannelsResponse, RouteHtlcStream};
36use crate::{
37 CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, CreateInvoiceRequest,
38 CreateInvoiceResponse, GetBalancesResponse, GetLnOnchainAddressResponse, GetNodeInfoResponse,
39 GetRouteHintsResponse, InterceptPaymentRequest, InterceptPaymentResponse, InvoiceDescription,
40 OpenChannelRequest, OpenChannelResponse, PayInvoiceResponse, PaymentAction, SendOnchainRequest,
41 SendOnchainResponse,
42};
43
44struct LdkTracingLogger {
52 in_test_env: bool,
56}
57
58impl LdkTracingLogger {
59 fn downgraded_level(&self, record: &LogRecord<'_>) -> LogLevel {
69 if self.in_test_env
70 && record.level == LogLevel::Error
71 && record.module_path == "ldk_node::chain"
72 && format!("{}", record.args).contains("Failed to retrieve fee rate estimates")
73 {
74 LogLevel::Debug
75 } else {
76 record.level
77 }
78 }
79}
80
81impl LogWriter for LdkTracingLogger {
82 fn log(&self, record: LogRecord<'_>) {
83 match self.downgraded_level(&record) {
85 LogLevel::Gossip | LogLevel::Trace => trace!(
86 target: LOG_LIGHTNING_LDK,
87 ldk_module = record.module_path, line = record.line, "{}", record.args,
88 ),
89 LogLevel::Debug => debug!(
90 target: LOG_LIGHTNING_LDK,
91 ldk_module = record.module_path, line = record.line, "{}", record.args,
92 ),
93 LogLevel::Info => info!(
94 target: LOG_LIGHTNING_LDK,
95 ldk_module = record.module_path, line = record.line, "{}", record.args,
96 ),
97 LogLevel::Warn => warn!(
98 target: LOG_LIGHTNING_LDK,
99 ldk_module = record.module_path, line = record.line, "{}", record.args,
100 ),
101 LogLevel::Error => error!(
102 target: LOG_LIGHTNING_LDK,
103 ldk_module = record.module_path, line = record.line, "{}", record.args,
104 ),
105 }
106 }
107}
108
109pub struct GatewayLdkClient {
110 node: Arc<ldk_node::Node>,
112
113 task_group: TaskGroup,
114
115 htlc_stream_receiver_or: Option<tokio::sync::mpsc::Receiver<InterceptPaymentRequest>>,
118
119 outbound_lightning_payment_lock_pool: lockable::LockPool<PaymentId>,
123
124 outbound_offer_lock_pool: lockable::LockPool<LdkOfferId>,
129
130 pending_channels:
135 Arc<RwLock<BTreeMap<UserChannelId, oneshot::Sender<anyhow::Result<OutPoint>>>>>,
136
137 pending_payments: Arc<RwLock<HashMap<PaymentId, oneshot::Sender<()>>>>,
143}
144
145impl std::fmt::Debug for GatewayLdkClient {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 f.debug_struct("GatewayLdkClient").finish_non_exhaustive()
148 }
149}
150
151impl GatewayLdkClient {
152 pub fn new(
157 data_dir: &Path,
158 chain_source: ChainSource,
159 network: Network,
160 lightning_port: u16,
161 alias: String,
162 mnemonic: Mnemonic,
163 runtime: Arc<tokio::runtime::Runtime>,
164 ) -> anyhow::Result<Self> {
165 let mut bytes = [0u8; 32];
166 let alias = if alias.is_empty() {
167 "LDK Gateway".to_string()
168 } else {
169 alias
170 };
171 let alias_bytes = alias.as_bytes();
172 let truncated = &alias_bytes[..alias_bytes.len().min(32)];
173 bytes[..truncated.len()].copy_from_slice(truncated);
174 let node_alias = Some(NodeAlias(bytes));
175
176 let mut node_builder = ldk_node::Builder::from_config(ldk_node::config::Config {
177 network,
178 listening_addresses: Some(vec![SocketAddress::TcpIpV4 {
179 addr: [0, 0, 0, 0],
180 port: lightning_port,
181 }]),
182 node_alias,
183 ..Default::default()
184 });
185
186 node_builder.set_custom_logger(Arc::new(LdkTracingLogger {
190 in_test_env: is_running_in_test_env(),
191 }));
192
193 node_builder.set_entropy_bip39_mnemonic(mnemonic, None);
194
195 match chain_source.clone() {
196 ChainSource::Bitcoind {
197 username,
198 password,
199 server_url,
200 } => {
201 node_builder.set_chain_source_bitcoind_rpc(
202 server_url
203 .host_str()
204 .expect("Could not retrieve host from bitcoind RPC url")
205 .to_string(),
206 server_url
207 .port()
208 .expect("Could not retrieve port from bitcoind RPC url"),
209 username,
210 password,
211 );
212 }
213 ChainSource::Esplora { server_url } => {
214 node_builder.set_chain_source_esplora(get_esplora_url(server_url)?, None);
215 }
216 };
217 let Some(data_dir_str) = data_dir.to_str() else {
218 return Err(anyhow::anyhow!("Invalid data dir path"));
219 };
220 node_builder.set_storage_dir_path(data_dir_str.to_string());
221
222 info!(chain_source = %chain_source, data_dir = %data_dir_str, alias = %alias, "Starting LDK Node...");
223 let node = Arc::new(node_builder.build()?);
224 node.start_with_runtime(runtime).map_err(|err| {
225 crit!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to start LDK Node");
226 LightningRpcError::FailedToConnect
227 })?;
228
229 let (htlc_stream_sender, htlc_stream_receiver) = tokio::sync::mpsc::channel(1024);
230 let task_group = TaskGroup::new();
231
232 let node_clone = node.clone();
233 let pending_channels = Arc::new(RwLock::new(BTreeMap::new()));
234 let pending_channels_clone = pending_channels.clone();
235 let pending_payments = Arc::new(RwLock::new(HashMap::new()));
236 let pending_payments_clone = pending_payments.clone();
237 task_group.spawn("ldk lightning node event handler", |handle| async move {
238 loop {
239 Self::handle_next_event(
240 &node_clone,
241 &htlc_stream_sender,
242 &handle,
243 pending_channels_clone.clone(),
244 pending_payments_clone.clone(),
245 )
246 .await;
247 }
248 });
249
250 info!("Successfully started LDK Gateway");
251 Ok(GatewayLdkClient {
252 node,
253 task_group,
254 htlc_stream_receiver_or: Some(htlc_stream_receiver),
255 outbound_lightning_payment_lock_pool: lockable::LockPool::new(),
256 outbound_offer_lock_pool: lockable::LockPool::new(),
257 pending_channels,
258 pending_payments,
259 })
260 }
261
262 async fn handle_next_event(
263 node: &ldk_node::Node,
264 htlc_stream_sender: &Sender<InterceptPaymentRequest>,
265 handle: &TaskHandle,
266 pending_channels: Arc<
267 RwLock<BTreeMap<UserChannelId, oneshot::Sender<anyhow::Result<OutPoint>>>>,
268 >,
269 pending_payments: Arc<RwLock<HashMap<PaymentId, oneshot::Sender<()>>>>,
270 ) {
271 let event = tokio::select! {
275 event = node.next_event_async() => {
276 event
277 }
278 () = handle.make_shutdown_rx() => {
279 return;
280 }
281 };
282
283 match event {
284 ldk_node::Event::PaymentClaimable {
285 payment_id: _,
286 payment_hash,
287 claimable_amount_msat,
288 claim_deadline,
289 custom_records: _,
290 } => {
291 if let Err(err) = htlc_stream_sender
292 .send(InterceptPaymentRequest {
293 payment_hash: Hash::from_slice(&payment_hash.0)
294 .expect("Failed to create Hash"),
295 amount_msat: claimable_amount_msat,
296 expiry: claim_deadline.unwrap_or_default(),
297 short_channel_id: None,
298 incoming_chan_id: 0,
299 htlc_id: 0,
300 })
301 .await
302 {
303 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed send InterceptHtlcRequest to stream");
304 }
305 }
306 ldk_node::Event::ChannelPending {
307 channel_id,
308 user_channel_id,
309 former_temporary_channel_id: _,
310 counterparty_node_id: _,
311 funding_txo,
312 } => {
313 info!(target: LOG_LIGHTNING, %channel_id, "LDK Channel is pending");
314 let mut channels = pending_channels.write().await;
315 if let Some(sender) = channels.remove(&UserChannelId(user_channel_id)) {
316 let _ = sender.send(Ok(funding_txo));
317 } else {
318 debug!(
319 ?user_channel_id,
320 "No channel pending channel open for user channel id"
321 );
322 }
323 }
324 ldk_node::Event::ChannelClosed {
325 channel_id,
326 user_channel_id,
327 counterparty_node_id: _,
328 reason,
329 } => {
330 info!(target: LOG_LIGHTNING, %channel_id, "LDK Channel is closed");
331 let mut channels = pending_channels.write().await;
332 if let Some(sender) = channels.remove(&UserChannelId(user_channel_id)) {
333 let reason = if let Some(reason) = reason {
334 reason.to_string()
335 } else {
336 "Channel has been closed".to_string()
337 };
338 let _ = sender.send(Err(anyhow::anyhow!(reason)));
339 } else {
340 debug!(
341 ?user_channel_id,
342 "No channel pending channel open for user channel id"
343 );
344 }
345 }
346 ldk_node::Event::PaymentSuccessful {
347 payment_id: Some(payment_id),
348 ..
349 }
350 | ldk_node::Event::PaymentFailed {
351 payment_id: Some(payment_id),
352 ..
353 } => {
354 Self::wake_pending_payment(&pending_payments, payment_id).await;
355 }
356 _ => {}
357 }
358
359 if let Err(err) = node.event_handled() {
364 warn!(err = %err.fmt_compact(), "LDK could not mark event handled");
365 }
366 }
367
368 async fn wake_pending_payment(
372 pending_payments: &Arc<RwLock<HashMap<PaymentId, oneshot::Sender<()>>>>,
373 payment_id: PaymentId,
374 ) -> PendingPaymentWakeup {
375 let Some(sender) = pending_payments.write().await.remove(&payment_id) else {
376 return PendingPaymentWakeup::NoWaiter;
377 };
378
379 if sender.send(()).is_ok() {
380 PendingPaymentWakeup::Woken
381 } else {
382 PendingPaymentWakeup::ReceiverDropped
383 }
384 }
385
386 fn ldk_payment_result(
391 &self,
392 payment_id: PaymentId,
393 ) -> Option<Result<PayInvoiceResponse, LightningRpcError>> {
394 let payment_details = self.node.payment(&payment_id)?;
395 match payment_details.status {
396 PaymentStatus::Pending => None,
397 PaymentStatus::Succeeded => {
398 if let PaymentKind::Bolt11 {
399 preimage: Some(preimage),
400 ..
401 } = payment_details.kind
402 {
403 Some(Ok(PayInvoiceResponse {
404 preimage: Preimage(preimage.0),
405 }))
406 } else {
407 Some(Err(LightningRpcError::FailedPayment {
408 failure_reason: "LDK payment succeeded without preimage".to_string(),
409 }))
410 }
411 }
412 PaymentStatus::Failed => Some(Err(LightningRpcError::FailedPayment {
413 failure_reason: "LDK payment failed".to_string(),
414 })),
415 }
416 }
417}
418
419impl Drop for GatewayLdkClient {
420 fn drop(&mut self) {
421 self.task_group.shutdown();
422
423 info!(target: LOG_LIGHTNING, "Stopping LDK Node...");
424 match self.node.stop() {
425 Err(err) => {
426 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to stop LDK Node");
427 }
428 _ => {
429 info!(target: LOG_LIGHTNING, "LDK Node stopped.");
430 }
431 }
432 }
433}
434
435#[async_trait]
436impl ILnRpcClient for GatewayLdkClient {
437 async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
438 let node_status = self.node.status();
439 let ldk_block_height = node_status.current_best_block.height;
440 let onchain_sync = node_status.latest_onchain_wallet_sync_timestamp;
441 let lightning_sync = node_status.latest_lightning_wallet_sync_timestamp;
442 let is_running = node_status.is_running;
443 debug!(target: LOG_LIGHTNING, ?onchain_sync, ?lightning_sync, ?is_running, "LDK Sync Status");
444
445 Ok(GetNodeInfoResponse {
446 pub_key: self.node.node_id(),
447 alias: match self.node.node_alias() {
448 Some(alias) => alias.to_string(),
449 None => format!("LDK Fedimint Gateway Node {}", self.node.node_id()),
450 },
451 network: self.node.config().network.to_string(),
452 block_height: ldk_block_height,
453 synced_to_chain: lightning_sync.is_some(),
456 })
457 }
458
459 async fn routehints(
460 &self,
461 _num_route_hints: usize,
462 ) -> Result<GetRouteHintsResponse, LightningRpcError> {
463 Ok(GetRouteHintsResponse {
469 route_hints: vec![],
470 })
471 }
472
473 async fn pay(
474 &self,
475 invoice: Bolt11Invoice,
476 max_delay: u64,
477 max_fee: Amount,
478 ) -> Result<PayInvoiceResponse, LightningRpcError> {
479 let payment_id = PaymentId(*invoice.payment_hash().as_byte_array());
480
481 let _payment_lock_guard = self
487 .outbound_lightning_payment_lock_pool
488 .async_lock(payment_id)
489 .await;
490
491 let (payment_sender, payment_receiver) = oneshot::channel();
495 self.pending_payments
496 .write()
497 .await
498 .insert(payment_id, payment_sender);
499
500 if self.node.payment(&payment_id).is_none() {
507 let sent_payment_id = match self.node.bolt11_payment().send(
508 &invoice,
509 Some(SendingParameters {
510 max_total_routing_fee_msat: Some(Some(max_fee.msats)),
511 max_total_cltv_expiry_delta: Some(max_delay as u32),
512 max_path_count: None,
513 max_channel_saturation_power_of_half: None,
514 }),
515 ) {
516 Ok(sent_payment_id) => sent_payment_id,
517 Err(err) => {
518 self.pending_payments.write().await.remove(&payment_id);
519 return Err(LightningRpcError::FailedPayment {
522 failure_reason: format!("LDK payment failed to initialize: {err:?}"),
523 });
524 }
525 };
526 assert_eq!(sent_payment_id, payment_id);
527 }
528
529 if let Some(result) = self.ldk_payment_result(payment_id) {
533 self.pending_payments.write().await.remove(&payment_id);
534 return result;
535 }
536
537 let _ = payment_receiver.await;
542
543 self.pending_payments.write().await.remove(&payment_id);
544 self.ldk_payment_result(payment_id).unwrap_or_else(|| {
545 Err(LightningRpcError::FailedPayment {
546 failure_reason: "LDK payment event fired without terminal payment status"
547 .to_string(),
548 })
549 })
550 }
551
552 async fn route_htlcs<'a>(
553 mut self: Box<Self>,
554 _task_group: &TaskGroup,
555 ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
556 let route_htlc_stream = match self.htlc_stream_receiver_or.take() {
557 Some(stream) => Ok(Box::pin(ReceiverStream::new(stream))),
558 None => Err(LightningRpcError::FailedToRouteHtlcs {
559 failure_reason:
560 "Stream does not exist. Likely was already taken by calling `route_htlcs()`."
561 .to_string(),
562 }),
563 }?;
564
565 Ok((route_htlc_stream, Arc::new(*self)))
566 }
567
568 async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError> {
569 let InterceptPaymentResponse {
570 action,
571 payment_hash,
572 incoming_chan_id: _,
573 htlc_id: _,
574 } = htlc;
575
576 let ph = PaymentHash(*payment_hash.clone().as_byte_array());
577
578 let claimable_amount_msat = 999_999_999_999_999;
584
585 let ph_hex_str = hex::encode(payment_hash);
586
587 if let PaymentAction::Settle(preimage) = action {
588 self.node
589 .bolt11_payment()
590 .claim_for_hash(ph, claimable_amount_msat, PaymentPreimage(preimage.0))
591 .map_err(|_| LightningRpcError::FailedToCompleteHtlc {
592 failure_reason: format!("Failed to claim LDK payment with hash {ph_hex_str}"),
593 })?;
594 } else {
595 warn!(target: LOG_LIGHTNING, payment_hash = %ph_hex_str, "Unwinding payment because the action was not `Settle`");
596 self.node.bolt11_payment().fail_for_hash(ph).map_err(|_| {
597 LightningRpcError::FailedToCompleteHtlc {
598 failure_reason: format!("Failed to unwind LDK payment with hash {ph_hex_str}"),
599 }
600 })?;
601 }
602
603 return Ok(());
604 }
605
606 async fn create_invoice(
607 &self,
608 create_invoice_request: CreateInvoiceRequest,
609 ) -> Result<CreateInvoiceResponse, LightningRpcError> {
610 let payment_hash_or = if let Some(payment_hash) = create_invoice_request.payment_hash {
611 let ph = PaymentHash(*payment_hash.as_byte_array());
612 Some(ph)
613 } else {
614 None
615 };
616
617 let description = match create_invoice_request.description {
618 Some(InvoiceDescription::Direct(desc)) => {
619 Bolt11InvoiceDescription::Direct(Description::new(desc).map_err(|_| {
620 LightningRpcError::FailedToGetInvoice {
621 failure_reason: "Invalid description".to_string(),
622 }
623 })?)
624 }
625 Some(InvoiceDescription::Hash(hash)) => {
626 Bolt11InvoiceDescription::Hash(lightning_invoice::Sha256(hash))
627 }
628 None => Bolt11InvoiceDescription::Direct(Description::empty()),
629 };
630
631 let invoice = match payment_hash_or {
632 Some(payment_hash) => self.node.bolt11_payment().receive_for_hash(
633 create_invoice_request.amount_msat,
634 &description,
635 create_invoice_request.expiry_secs,
636 payment_hash,
637 ),
638 None => self.node.bolt11_payment().receive(
639 create_invoice_request.amount_msat,
640 &description,
641 create_invoice_request.expiry_secs,
642 ),
643 }
644 .map_err(|e| LightningRpcError::FailedToGetInvoice {
645 failure_reason: e.to_string(),
646 })?;
647
648 Ok(CreateInvoiceResponse {
649 invoice: invoice.to_string(),
650 })
651 }
652
653 async fn get_ln_onchain_address(
654 &self,
655 ) -> Result<GetLnOnchainAddressResponse, LightningRpcError> {
656 self.node
657 .onchain_payment()
658 .new_address()
659 .map(|address| GetLnOnchainAddressResponse {
660 address: address.to_string(),
661 })
662 .map_err(|e| LightningRpcError::FailedToGetLnOnchainAddress {
663 failure_reason: e.to_string(),
664 })
665 }
666
667 async fn send_onchain(
668 &self,
669 SendOnchainRequest {
670 address,
671 amount,
672 fee_rate_sats_per_vbyte,
673 }: SendOnchainRequest,
674 ) -> Result<SendOnchainResponse, LightningRpcError> {
675 let onchain = self.node.onchain_payment();
676
677 let retain_reserves = false;
678 let txid = match amount {
679 BitcoinAmountOrAll::All => onchain.send_all_to_address(
680 &address.assume_checked(),
681 retain_reserves,
682 FeeRate::from_sat_per_vb(fee_rate_sats_per_vbyte),
683 ),
684 BitcoinAmountOrAll::Amount(amount_sats) => onchain.send_to_address(
685 &address.assume_checked(),
686 amount_sats.to_sat(),
687 FeeRate::from_sat_per_vb(fee_rate_sats_per_vbyte),
688 ),
689 }
690 .map_err(|e| LightningRpcError::FailedToWithdrawOnchain {
691 failure_reason: e.to_string(),
692 })?;
693
694 Ok(SendOnchainResponse {
695 txid: txid.to_string(),
696 })
697 }
698
699 async fn open_channel(
700 &self,
701 OpenChannelRequest {
702 pubkey,
703 host,
704 channel_size_sats,
705 push_amount_sats,
706 fee_rate_sats_per_vbyte,
707 base_fee_msat,
708 parts_per_million,
709 }: OpenChannelRequest,
710 ) -> Result<OpenChannelResponse, LightningRpcError> {
711 let push_amount_msats_or = if push_amount_sats == 0 {
712 None
713 } else {
714 Some(push_amount_sats * 1000)
715 };
716
717 if fee_rate_sats_per_vbyte.is_some() {
718 warn!(
721 target: LOG_LIGHTNING,
722 "Ignoring fee_rate_sats_per_vbyte on LDK channel open; LDK uses its built-in fee estimator"
723 );
724 }
725
726 let channel_config = match (base_fee_msat, parts_per_million) {
727 (None, None) => None,
728 (base, ppm) => {
729 let mut config = ChannelConfig::default();
730 if let Some(base) = base {
731 config.forwarding_fee_base_msat = u32::try_from(base).map_err(|_| {
732 LightningRpcError::FailedToOpenChannel {
733 failure_reason: format!(
734 "base_fee_msat {base} does not fit in u32 (LDK limit)"
735 ),
736 }
737 })?;
738 }
739 if let Some(ppm) = ppm {
740 config.forwarding_fee_proportional_millionths =
741 u32::try_from(ppm).map_err(|_| LightningRpcError::FailedToOpenChannel {
742 failure_reason: format!(
743 "parts_per_million {ppm} does not fit in u32 (LDK limit)"
744 ),
745 })?;
746 }
747 Some(config)
748 }
749 };
750
751 let (tx, rx) = oneshot::channel::<anyhow::Result<OutPoint>>();
752
753 {
754 let mut channels = self.pending_channels.write().await;
755 let user_channel_id = self
756 .node
757 .open_announced_channel(
758 pubkey,
759 SocketAddress::from_str(&host).map_err(|e| {
760 LightningRpcError::FailedToConnectToPeer {
761 failure_reason: e.to_string(),
762 }
763 })?,
764 channel_size_sats,
765 push_amount_msats_or,
766 channel_config,
767 )
768 .map_err(|e| LightningRpcError::FailedToOpenChannel {
769 failure_reason: e.to_string(),
770 })?;
771
772 channels.insert(UserChannelId(user_channel_id), tx);
773 }
774
775 match rx
776 .await
777 .map_err(|err| LightningRpcError::FailedToOpenChannel {
778 failure_reason: err.to_string(),
779 })? {
780 Ok(outpoint) => {
781 let funding_txid = outpoint.txid;
782
783 Ok(OpenChannelResponse {
784 funding_txid: funding_txid.to_string(),
785 })
786 }
787 Err(err) => Err(LightningRpcError::FailedToOpenChannel {
788 failure_reason: err.to_string(),
789 }),
790 }
791 }
792
793 async fn connect_peer(&self, payload: ConnectPeerRequest) -> Result<(), LightningRpcError> {
794 let NodeAddress { pubkey, address } = payload.node_address;
795 self.node.connect(pubkey, address, false).map_err(|e| {
796 LightningRpcError::FailedToConnectToPeer {
797 failure_reason: e.to_string(),
798 }
799 })
800 }
801
802 async fn close_channels_with_peer(
803 &self,
804 CloseChannelsWithPeerRequest {
805 pubkey,
806 force,
807 sats_per_vbyte: _,
808 }: CloseChannelsWithPeerRequest,
809 ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
810 let mut num_channels_closed = 0;
811
812 info!(%pubkey, "Closing all channels with peer");
813 for channel_with_peer in self
814 .node
815 .list_channels()
816 .iter()
817 .filter(|channel| channel.counterparty_node_id == pubkey)
818 {
819 if force {
820 match self.node.force_close_channel(
821 &channel_with_peer.user_channel_id,
822 pubkey,
823 Some("User initiated force close".to_string()),
824 ) {
825 Ok(()) => num_channels_closed += 1,
826 Err(err) => {
827 error!(%pubkey, err = %err.fmt_compact(), "Could not force close channel");
828 }
829 }
830 } else {
831 match self
832 .node
833 .close_channel(&channel_with_peer.user_channel_id, pubkey)
834 {
835 Ok(()) => {
836 num_channels_closed += 1;
837 }
838 Err(err) => {
839 error!(%pubkey, err = %err.fmt_compact(), "Could not close channel");
840 }
841 }
842 }
843 }
844
845 Ok(CloseChannelsWithPeerResponse {
846 num_channels_closed,
847 })
848 }
849
850 async fn list_channels(&self) -> Result<ListChannelsResponse, LightningRpcError> {
851 let mut channels = Vec::new();
852 let network_graph = self.node.network_graph();
853
854 let peer_addresses: std::collections::HashMap<_, _> = self
856 .node
857 .list_peers()
858 .into_iter()
859 .map(|peer| (peer.node_id, peer.address.to_string()))
860 .collect();
861
862 for channel_details in self.node.list_channels().iter() {
863 let node_id = NodeId::from_pubkey(&channel_details.counterparty_node_id);
864 let node_info = network_graph.node(&node_id);
865
866 let remote_node_alias = node_info.as_ref().and_then(|info| {
868 info.announcement_info.as_ref().and_then(|announcement| {
869 let alias = announcement.alias().to_string();
870 if alias.is_empty() { None } else { Some(alias) }
871 })
872 });
873
874 let remote_address = peer_addresses
875 .get(&channel_details.counterparty_node_id)
876 .cloned();
877
878 channels.push(ChannelInfo {
879 remote_pubkey: channel_details.counterparty_node_id,
880 channel_size_sats: channel_details.channel_value_sats,
881 outbound_liquidity_sats: channel_details.outbound_capacity_msat / 1000,
882 inbound_liquidity_sats: channel_details.inbound_capacity_msat / 1000,
883 is_active: channel_details.is_usable,
884 funding_outpoint: channel_details.funding_txo,
885 remote_node_alias,
886 remote_address,
887 base_fee_msat: Some(u64::from(channel_details.config.forwarding_fee_base_msat)),
888 parts_per_million: Some(u64::from(
889 channel_details
890 .config
891 .forwarding_fee_proportional_millionths,
892 )),
893 });
894 }
895
896 Ok(ListChannelsResponse { channels })
897 }
898
899 async fn set_channel_fees(
900 &self,
901 payload: SetChannelFeesRequest,
902 ) -> Result<(), LightningRpcError> {
903 let channel = self
907 .node
908 .list_channels()
909 .into_iter()
910 .find(|c| c.funding_txo == Some(payload.funding_outpoint))
911 .ok_or_else(|| LightningRpcError::FailedToSetChannelFees {
912 failure_reason: format!(
913 "No channel found with funding outpoint {}",
914 payload.funding_outpoint,
915 ),
916 })?;
917
918 let forwarding_fee_base_msat = u32::try_from(payload.base_fee_msat).map_err(|_| {
919 LightningRpcError::FailedToSetChannelFees {
920 failure_reason: format!(
921 "base_fee_msat {} does not fit in u32 (LDK limit)",
922 payload.base_fee_msat,
923 ),
924 }
925 })?;
926 let forwarding_fee_proportional_millionths = u32::try_from(payload.parts_per_million)
927 .map_err(|_| LightningRpcError::FailedToSetChannelFees {
928 failure_reason: format!(
929 "parts_per_million {} does not fit in u32 (LDK limit)",
930 payload.parts_per_million,
931 ),
932 })?;
933
934 let new_config = ChannelConfig {
937 forwarding_fee_base_msat,
938 forwarding_fee_proportional_millionths,
939 ..channel.config
940 };
941
942 self.node
943 .update_channel_config(
944 &channel.user_channel_id,
945 channel.counterparty_node_id,
946 new_config,
947 )
948 .map_err(|e| LightningRpcError::FailedToSetChannelFees {
949 failure_reason: e.to_string(),
950 })?;
951
952 Ok(())
953 }
954
955 async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
956 let balances = self.node.list_balances();
957 let channel_lists = self
958 .node
959 .list_channels()
960 .into_iter()
961 .filter(|chan| chan.is_usable)
962 .collect::<Vec<_>>();
963 let total_inbound_liquidity_balance_msat: u64 = channel_lists
965 .iter()
966 .map(|channel| channel.inbound_capacity_msat)
967 .sum();
968
969 Ok(GetBalancesResponse {
970 onchain_balance_sats: balances.total_onchain_balance_sats,
971 lightning_balance_msats: balances.total_lightning_balance_sats * 1000,
972 inbound_lightning_liquidity_msats: total_inbound_liquidity_balance_msat,
973 })
974 }
975
976 async fn get_invoice(
977 &self,
978 get_invoice_request: GetInvoiceRequest,
979 ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
980 let invoices = self
981 .node
982 .list_payments_with_filter(|details| {
983 details.direction == PaymentDirection::Inbound
984 && details.id == PaymentId(get_invoice_request.payment_hash.to_byte_array())
985 && !matches!(details.kind, PaymentKind::Onchain { .. })
986 })
987 .iter()
988 .map(|details| {
989 let (preimage, payment_hash, _) = get_preimage_and_payment_hash(&details.kind);
990 let status = match details.status {
991 PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
992 PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
993 PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
994 };
995 GetInvoiceResponse {
996 preimage: preimage.map(|p| p.to_string()),
997 payment_hash,
998 amount: Amount::from_msats(
999 details
1000 .amount_msat
1001 .expect("amountless invoices are not supported"),
1002 ),
1003 created_at: UNIX_EPOCH + Duration::from_secs(details.latest_update_timestamp),
1004 status,
1005 }
1006 })
1007 .collect::<Vec<_>>();
1008
1009 Ok(invoices.first().cloned())
1010 }
1011
1012 async fn list_transactions(
1013 &self,
1014 start_secs: u64,
1015 end_secs: u64,
1016 ) -> Result<ListTransactionsResponse, LightningRpcError> {
1017 let transactions = self
1018 .node
1019 .list_payments_with_filter(|details| {
1020 !matches!(details.kind, PaymentKind::Onchain { .. })
1021 && details.latest_update_timestamp >= start_secs
1022 && details.latest_update_timestamp < end_secs
1023 })
1024 .iter()
1025 .map(|details| {
1026 let (preimage, payment_hash, payment_kind) =
1027 get_preimage_and_payment_hash(&details.kind);
1028 let direction = match details.direction {
1029 PaymentDirection::Outbound => {
1030 fedimint_gateway_common::PaymentDirection::Outbound
1031 }
1032 PaymentDirection::Inbound => fedimint_gateway_common::PaymentDirection::Inbound,
1033 };
1034 let status = match details.status {
1035 PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
1036 PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
1037 PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
1038 };
1039 fedimint_gateway_common::PaymentDetails {
1040 payment_hash,
1041 preimage: preimage.map(|p| p.to_string()),
1042 payment_kind,
1043 amount: Amount::from_msats(
1044 details
1045 .amount_msat
1046 .expect("amountless invoices are not supported"),
1047 ),
1048 direction,
1049 status,
1050 timestamp_secs: details.latest_update_timestamp,
1051 }
1052 })
1053 .collect::<Vec<_>>();
1054 Ok(ListTransactionsResponse { transactions })
1055 }
1056
1057 fn create_offer(
1058 &self,
1059 amount: Option<Amount>,
1060 description: Option<String>,
1061 expiry_secs: Option<u32>,
1062 quantity: Option<u64>,
1063 ) -> Result<String, LightningRpcError> {
1064 let description = description.unwrap_or_default();
1065 let offer = if let Some(amount) = amount {
1066 self.node
1067 .bolt12_payment()
1068 .receive(amount.msats, &description, expiry_secs, quantity)
1069 .map_err(|err| LightningRpcError::Bolt12Error {
1070 failure_reason: err.to_string(),
1071 })?
1072 } else {
1073 self.node
1074 .bolt12_payment()
1075 .receive_variable_amount(&description, expiry_secs)
1076 .map_err(|err| LightningRpcError::Bolt12Error {
1077 failure_reason: err.to_string(),
1078 })?
1079 };
1080
1081 Ok(offer.to_string())
1082 }
1083
1084 async fn pay_offer(
1085 &self,
1086 offer: String,
1087 quantity: Option<u64>,
1088 amount: Option<Amount>,
1089 payer_note: Option<String>,
1090 ) -> Result<Preimage, LightningRpcError> {
1091 let offer = Offer::from_str(&offer).map_err(|_| LightningRpcError::Bolt12Error {
1092 failure_reason: "Failed to parse Bolt12 Offer".to_string(),
1093 })?;
1094
1095 let _offer_lock_guard = self
1096 .outbound_offer_lock_pool
1097 .blocking_lock(LdkOfferId(offer.id()));
1098
1099 let payment_id = if let Some(amount) = amount {
1100 self.node
1101 .bolt12_payment()
1102 .send_using_amount(&offer, amount.msats, quantity, payer_note)
1103 .map_err(|err| LightningRpcError::Bolt12Error {
1104 failure_reason: err.to_string(),
1105 })?
1106 } else {
1107 self.node
1108 .bolt12_payment()
1109 .send(&offer, quantity, payer_note)
1110 .map_err(|err| LightningRpcError::Bolt12Error {
1111 failure_reason: err.to_string(),
1112 })?
1113 };
1114
1115 loop {
1116 if let Some(payment_details) = self.node.payment(&payment_id) {
1117 match payment_details.status {
1118 PaymentStatus::Pending => {}
1119 PaymentStatus::Succeeded => match payment_details.kind {
1120 PaymentKind::Bolt12Offer {
1121 preimage: Some(preimage),
1122 ..
1123 } => {
1124 info!(target: LOG_LIGHTNING, offer = %offer, payment_id = %payment_id, preimage = %preimage, "Successfully paid offer");
1125 return Ok(Preimage(preimage.0));
1126 }
1127 _ => {
1128 return Err(LightningRpcError::FailedPayment {
1129 failure_reason: "Unexpected payment kind".to_string(),
1130 });
1131 }
1132 },
1133 PaymentStatus::Failed => {
1134 return Err(LightningRpcError::FailedPayment {
1135 failure_reason: "Bolt12 payment failed".to_string(),
1136 });
1137 }
1138 }
1139 }
1140 fedimint_core::runtime::sleep(Duration::from_millis(100)).await;
1141 }
1142 }
1143
1144 fn sync_wallet(&self) -> Result<(), LightningRpcError> {
1145 block_in_place(|| {
1146 let _ = self.node.sync_wallets();
1147 });
1148 Ok(())
1149 }
1150}
1151
1152fn get_preimage_and_payment_hash(
1155 kind: &PaymentKind,
1156) -> (
1157 Option<Preimage>,
1158 Option<sha256::Hash>,
1159 fedimint_gateway_common::PaymentKind,
1160) {
1161 match kind {
1162 PaymentKind::Bolt11 {
1163 hash,
1164 preimage,
1165 secret: _,
1166 } => (
1167 preimage.map(|p| Preimage(p.0)),
1168 Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
1169 fedimint_gateway_common::PaymentKind::Bolt11,
1170 ),
1171 PaymentKind::Bolt11Jit {
1172 hash,
1173 preimage,
1174 secret: _,
1175 lsp_fee_limits: _,
1176 ..
1177 } => (
1178 preimage.map(|p| Preimage(p.0)),
1179 Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
1180 fedimint_gateway_common::PaymentKind::Bolt11,
1181 ),
1182 PaymentKind::Bolt12Offer {
1183 hash,
1184 preimage,
1185 secret: _,
1186 offer_id: _,
1187 payer_note: _,
1188 quantity: _,
1189 } => (
1190 preimage.map(|p| Preimage(p.0)),
1191 hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
1192 fedimint_gateway_common::PaymentKind::Bolt12Offer,
1193 ),
1194 PaymentKind::Bolt12Refund {
1195 hash,
1196 preimage,
1197 secret: _,
1198 payer_note: _,
1199 quantity: _,
1200 } => (
1201 preimage.map(|p| Preimage(p.0)),
1202 hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
1203 fedimint_gateway_common::PaymentKind::Bolt12Refund,
1204 ),
1205 PaymentKind::Spontaneous { hash, preimage } => (
1206 preimage.map(|p| Preimage(p.0)),
1207 Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
1208 fedimint_gateway_common::PaymentKind::Bolt11,
1209 ),
1210 PaymentKind::Onchain { .. } => (None, None, fedimint_gateway_common::PaymentKind::Onchain),
1211 }
1212}
1213
1214fn get_esplora_url(server_url: SafeUrl) -> anyhow::Result<String> {
1222 let host = server_url
1224 .host_str()
1225 .ok_or(anyhow::anyhow!("Missing esplora host"))?;
1226 let server_url = if let Some(port) = server_url.port() {
1227 format!("{}://{}:{}", server_url.scheme(), host, port)
1228 } else {
1229 server_url.to_string()
1230 };
1231 Ok(server_url)
1232}
1233
1234#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1236enum PendingPaymentWakeup {
1237 NoWaiter,
1239 Woken,
1241 ReceiverDropped,
1243}
1244
1245#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1246struct LdkOfferId(OfferId);
1247
1248impl std::hash::Hash for LdkOfferId {
1249 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1250 state.write(&self.0.0);
1251 }
1252}
1253
1254#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1255pub struct UserChannelId(pub ldk_node::UserChannelId);
1256
1257impl PartialOrd for UserChannelId {
1258 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1259 Some(self.cmp(other))
1260 }
1261}
1262
1263impl Ord for UserChannelId {
1264 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1265 self.0.0.cmp(&other.0.0)
1266 }
1267}
1268
1269#[cfg(test)]
1270mod tests;