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::{PaymentDetails, PaymentDirection, PaymentKind, PaymentStatus};
26use lightning::ln::channelmanager::PaymentId;
27use lightning::offers::offer::{Offer, OfferId};
28use lightning::routing::router::RouteParametersConfig;
29use lightning::types::payment::{PaymentHash, PaymentPreimage};
30use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
31use thiserror::Error;
32use tokio::sync::mpsc::Sender;
33use tokio::sync::{RwLock, oneshot};
34use tokio_stream::wrappers::ReceiverStream;
35use tracing::{debug, error, info, trace, warn};
36
37use super::{ChannelInfo, ILnRpcClient, LightningRpcError, ListChannelsResponse, RouteHtlcStream};
38use crate::{
39 CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, CreateInvoiceRequest,
40 CreateInvoiceResponse, GetBalancesResponse, GetLnOnchainAddressResponse, GetNodeInfoResponse,
41 GetRouteHintsResponse, InterceptPaymentRequest, InterceptPaymentResponse, InvoiceDescription,
42 NO_INCOMING_CIRCUIT, OpenChannelRequest, OpenChannelResponse, PayInvoiceResponse,
43 PaymentAction, SendOnchainRequest, SendOnchainResponse,
44};
45
46struct LdkTracingLogger {
54 in_test_env: bool,
58}
59
60impl LdkTracingLogger {
61 fn downgraded_level(&self, record: &LogRecord<'_>) -> LogLevel {
71 if self.in_test_env
72 && record.level == LogLevel::Error
73 && record.module_path == "ldk_node::chain"
74 && format!("{}", record.args).contains("Failed to retrieve fee rate estimates")
75 {
76 LogLevel::Debug
77 } else {
78 record.level
79 }
80 }
81}
82
83impl LogWriter for LdkTracingLogger {
84 fn log(&self, record: LogRecord<'_>) {
85 match self.downgraded_level(&record) {
87 LogLevel::Gossip | LogLevel::Trace => trace!(
88 target: LOG_LIGHTNING_LDK,
89 ldk_module = record.module_path, line = record.line, "{}", record.args,
90 ),
91 LogLevel::Debug => debug!(
92 target: LOG_LIGHTNING_LDK,
93 ldk_module = record.module_path, line = record.line, "{}", record.args,
94 ),
95 LogLevel::Info => info!(
96 target: LOG_LIGHTNING_LDK,
97 ldk_module = record.module_path, line = record.line, "{}", record.args,
98 ),
99 LogLevel::Warn => warn!(
100 target: LOG_LIGHTNING_LDK,
101 ldk_module = record.module_path, line = record.line, "{}", record.args,
102 ),
103 LogLevel::Error => error!(
104 target: LOG_LIGHTNING_LDK,
105 ldk_module = record.module_path, line = record.line, "{}", record.args,
106 ),
107 }
108 }
109}
110
111pub struct GatewayLdkClient {
112 node: Arc<ldk_node::Node>,
114
115 task_group: TaskGroup,
116
117 htlc_stream_receiver_or: Option<tokio::sync::mpsc::Receiver<InterceptPaymentRequest>>,
120
121 outbound_lightning_payment_lock_pool: lockable::LockPool<PaymentId>,
125
126 outbound_offer_lock_pool: lockable::LockPool<LdkOfferId>,
131
132 pending_channels: Arc<RwLock<BTreeMap<UserChannelId, PendingChannelSender>>>,
137
138 pending_payments: Arc<RwLock<HashMap<PaymentId, oneshot::Sender<()>>>>,
144}
145
146type PendingChannelSender = oneshot::Sender<Result<OutPoint, String>>;
149
150impl std::fmt::Debug for GatewayLdkClient {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.debug_struct("GatewayLdkClient").finish_non_exhaustive()
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum MnemonicSource {
159 Generated,
162 Existing,
168}
169
170impl GatewayLdkClient {
171 #[allow(clippy::too_many_arguments)]
183 pub fn new(
184 data_dir: &Path,
185 chain_source: ChainSource,
186 network: Network,
187 lightning_port: u16,
188 alias: String,
189 mnemonic: Mnemonic,
190 mnemonic_source: MnemonicSource,
191 runtime: Arc<tokio::runtime::Runtime>,
192 ) -> Result<Self, LdkClientInitError> {
193 let mut bytes = [0u8; 32];
194 let alias = if alias.is_empty() {
195 "LDK Gateway".to_string()
196 } else {
197 alias
198 };
199 let alias_bytes = alias.as_bytes();
200 let truncated = &alias_bytes[..alias_bytes.len().min(32)];
201 bytes[..truncated.len()].copy_from_slice(truncated);
202 let node_alias = Some(NodeAlias(bytes));
203
204 let mut node_builder = ldk_node::Builder::from_config(ldk_node::config::Config {
205 network,
206 listening_addresses: Some(vec![SocketAddress::TcpIpV4 {
207 addr: [0, 0, 0, 0],
208 port: lightning_port,
209 }]),
210 node_alias,
211 ..Default::default()
212 });
213
214 node_builder.set_custom_logger(Arc::new(LdkTracingLogger {
218 in_test_env: is_running_in_test_env(),
219 }));
220
221 node_builder.set_entropy_bip39_mnemonic(mnemonic, None);
222
223 match chain_source.clone() {
224 ChainSource::Bitcoind {
225 username,
226 password,
227 server_url,
228 } => {
229 node_builder.set_chain_source_bitcoind_rpc(
230 server_url
231 .host_str()
232 .expect("Could not retrieve host from bitcoind RPC url")
233 .to_string(),
234 server_url
235 .port()
236 .expect("Could not retrieve port from bitcoind RPC url"),
237 username,
238 password,
239 );
240 }
241 ChainSource::Esplora { server_url } => {
242 node_builder.set_chain_source_esplora(get_esplora_url(server_url)?, None);
243 }
244 };
245 let Some(data_dir_str) = data_dir.to_str() else {
246 return Err(LdkClientInitError::InvalidDataDir);
247 };
248 node_builder.set_storage_dir_path(data_dir_str.to_string());
249 node_builder.set_runtime(runtime.handle().clone());
250 if mnemonic_source == MnemonicSource::Existing {
251 info!(
252 target: LOG_LIGHTNING,
253 "Mnemonic was not generated by this process, recovering onchain wallet from genesis if it does not exist yet"
254 );
255 node_builder.set_wallet_recovery_mode();
256 }
257
258 info!(chain_source = %chain_source, data_dir = %data_dir_str, alias = %alias, "Starting LDK Node...");
259 let node = Arc::new(node_builder.build().map_err(LdkClientInitError::Build)?);
260 node.start().map_err(|err| {
261 crit!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to start LDK Node");
262 LdkClientInitError::Start(err)
263 })?;
264
265 let (htlc_stream_sender, htlc_stream_receiver) = tokio::sync::mpsc::channel(1024);
266 let task_group = TaskGroup::new();
267
268 let node_clone = node.clone();
269 let pending_channels = Arc::new(RwLock::new(BTreeMap::new()));
270 let pending_channels_clone = pending_channels.clone();
271 let pending_payments = Arc::new(RwLock::new(HashMap::new()));
272 let pending_payments_clone = pending_payments.clone();
273 task_group.spawn("ldk lightning node event handler", |handle| async move {
274 loop {
275 Self::handle_next_event(
276 &node_clone,
277 &htlc_stream_sender,
278 &handle,
279 pending_channels_clone.clone(),
280 pending_payments_clone.clone(),
281 )
282 .await;
283 }
284 });
285
286 info!("Successfully started LDK Gateway");
287 Ok(GatewayLdkClient {
288 node,
289 task_group,
290 htlc_stream_receiver_or: Some(htlc_stream_receiver),
291 outbound_lightning_payment_lock_pool: lockable::LockPool::new(),
292 outbound_offer_lock_pool: lockable::LockPool::new(),
293 pending_channels,
294 pending_payments,
295 })
296 }
297
298 async fn handle_next_event(
299 node: &ldk_node::Node,
300 htlc_stream_sender: &Sender<InterceptPaymentRequest>,
301 handle: &TaskHandle,
302 pending_channels: Arc<RwLock<BTreeMap<UserChannelId, PendingChannelSender>>>,
303 pending_payments: Arc<RwLock<HashMap<PaymentId, oneshot::Sender<()>>>>,
304 ) {
305 let event = tokio::select! {
309 event = node.next_event_async() => {
310 event
311 }
312 () = handle.make_shutdown_rx() => {
313 return;
314 }
315 };
316
317 match event {
318 ldk_node::Event::PaymentClaimable {
319 payment_id: _,
320 payment_hash,
321 claimable_amount_msat,
322 claim_deadline,
323 custom_records: _,
324 } => {
325 if let Err(err) = htlc_stream_sender
326 .send(InterceptPaymentRequest {
327 payment_hash: Hash::from_slice(&payment_hash.0)
328 .expect("Failed to create Hash"),
329 amount_msat: claimable_amount_msat,
332 incoming_amount_msat: claimable_amount_msat,
333 expiry: claim_deadline.unwrap_or_default(),
334 short_channel_id: None,
335 incoming_chan_id: NO_INCOMING_CIRCUIT.0,
338 htlc_id: NO_INCOMING_CIRCUIT.1,
339 })
340 .await
341 {
342 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed send InterceptHtlcRequest to stream");
343 }
344 }
345 ldk_node::Event::ChannelPending {
346 channel_id,
347 user_channel_id,
348 former_temporary_channel_id: _,
349 counterparty_node_id: _,
350 funding_txo,
351 } => {
352 info!(target: LOG_LIGHTNING, %channel_id, "LDK Channel is pending");
353 let mut channels = pending_channels.write().await;
354 if let Some(sender) = channels.remove(&UserChannelId(user_channel_id)) {
355 let _ = sender.send(Ok(funding_txo));
356 } else {
357 debug!(
358 ?user_channel_id,
359 "No channel pending channel open for user channel id"
360 );
361 }
362 }
363 ldk_node::Event::ChannelClosed {
364 channel_id,
365 user_channel_id,
366 counterparty_node_id: _,
367 reason,
368 } => {
369 info!(target: LOG_LIGHTNING, %channel_id, "LDK Channel is closed");
370 let mut channels = pending_channels.write().await;
371 if let Some(sender) = channels.remove(&UserChannelId(user_channel_id)) {
372 let reason = if let Some(reason) = reason {
373 reason.to_string()
374 } else {
375 "Channel has been closed".to_string()
376 };
377 let _ = sender.send(Err(reason));
378 } else {
379 debug!(
380 ?user_channel_id,
381 "No channel pending channel open for user channel id"
382 );
383 }
384 }
385 ldk_node::Event::PaymentSuccessful {
386 payment_id: Some(payment_id),
387 ..
388 }
389 | ldk_node::Event::PaymentFailed {
390 payment_id: Some(payment_id),
391 ..
392 } => {
393 Self::wake_pending_payment(&pending_payments, payment_id).await;
394 }
395 _ => {}
396 }
397
398 if let Err(err) = node.event_handled() {
403 warn!(err = %err.fmt_compact(), "LDK could not mark event handled");
404 }
405 }
406
407 async fn wake_pending_payment(
411 pending_payments: &Arc<RwLock<HashMap<PaymentId, oneshot::Sender<()>>>>,
412 payment_id: PaymentId,
413 ) -> PendingPaymentWakeup {
414 let Some(sender) = pending_payments.write().await.remove(&payment_id) else {
415 return PendingPaymentWakeup::NoWaiter;
416 };
417
418 if sender.send(()).is_ok() {
419 PendingPaymentWakeup::Woken
420 } else {
421 PendingPaymentWakeup::ReceiverDropped
422 }
423 }
424
425 fn outbound_payment(&self, payment_id: PaymentId) -> Option<PaymentDetails> {
435 self.node
436 .payment(&payment_id)
437 .filter(|details| details.direction == PaymentDirection::Outbound)
438 }
439
440 fn ldk_payment_result(
445 &self,
446 payment_id: PaymentId,
447 ) -> Option<Result<PayInvoiceResponse, LightningRpcError>> {
448 let payment_details = self.outbound_payment(payment_id)?;
449 match payment_details.status {
450 PaymentStatus::Pending => None,
451 PaymentStatus::Succeeded => {
452 if let PaymentKind::Bolt11 {
453 preimage: Some(preimage),
454 ..
455 } = payment_details.kind
456 {
457 Some(Ok(PayInvoiceResponse {
458 preimage: Preimage(preimage.0),
459 }))
460 } else {
461 Some(Err(LightningRpcError::FailedPayment {
462 failure_reason: "LDK payment succeeded without preimage".to_string(),
463 }))
464 }
465 }
466 PaymentStatus::Failed => Some(Err(LightningRpcError::FailedPayment {
467 failure_reason: "LDK payment failed".to_string(),
468 })),
469 }
470 }
471}
472
473#[derive(Debug, Error)]
478#[non_exhaustive]
479pub enum LdkClientInitError {
480 #[error("Missing esplora host")]
482 MissingEsploraHost,
483
484 #[error("Invalid data dir path")]
487 InvalidDataDir,
488
489 #[error("The LDK node could not be built")]
492 Build(#[source] ldk_node::BuildError),
493
494 #[error("The LDK node could not be started")]
496 Start(#[source] ldk_node::NodeError),
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501enum InboundRegistrationRefusal {
502 OutboundInFlight,
505 OutboundRecorded,
507}
508
509impl std::fmt::Display for InboundRegistrationRefusal {
510 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
511 match self {
512 Self::OutboundInFlight => {
513 write!(f, "an outbound payment for this hash is in flight")
514 }
515 Self::OutboundRecorded => {
516 write!(f, "the node holds an outbound payment for this hash")
517 }
518 }
519 }
520}
521
522fn check_inbound_registration(
539 outbound_lock_acquired: bool,
540 existing_direction: Option<PaymentDirection>,
541) -> Result<(), InboundRegistrationRefusal> {
542 if !outbound_lock_acquired {
543 return Err(InboundRegistrationRefusal::OutboundInFlight);
544 }
545
546 match existing_direction {
547 Some(PaymentDirection::Outbound) => Err(InboundRegistrationRefusal::OutboundRecorded),
548 Some(PaymentDirection::Inbound) | None => Ok(()),
549 }
550}
551
552fn htlc_completion_error(err: &ldk_node::NodeError, payment_hash: &str) -> LightningRpcError {
564 match err {
565 ldk_node::NodeError::InvalidPaymentHash
566 | ldk_node::NodeError::InvalidPaymentPreimage
567 | ldk_node::NodeError::InvalidAmount => LightningRpcError::HtlcCompletionRejected {
568 failure_reason: format!(
569 "LDK rejected completion of payment with hash {payment_hash}: {err}"
570 ),
571 },
572 _ => LightningRpcError::FailedToCompleteHtlc {
573 failure_reason: format!(
574 "Failed to complete LDK payment with hash {payment_hash}: {err}"
575 ),
576 },
577 }
578}
579
580impl Drop for GatewayLdkClient {
581 fn drop(&mut self) {
582 self.task_group.shutdown();
583
584 info!(target: LOG_LIGHTNING, "Stopping LDK Node...");
585 match self.node.stop() {
586 Err(err) => {
587 warn!(target: LOG_LIGHTNING, err = %err.fmt_compact(), "Failed to stop LDK Node");
588 }
589 _ => {
590 info!(target: LOG_LIGHTNING, "LDK Node stopped.");
591 }
592 }
593 }
594}
595
596#[async_trait]
597impl ILnRpcClient for GatewayLdkClient {
598 async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
599 let node_status = self.node.status();
600 let ldk_block_height = node_status.current_best_block.height;
601 let onchain_sync = node_status.latest_onchain_wallet_sync_timestamp;
602 let lightning_sync = node_status.latest_lightning_wallet_sync_timestamp;
603 let is_running = node_status.is_running;
604 debug!(target: LOG_LIGHTNING, ?onchain_sync, ?lightning_sync, ?is_running, "LDK Sync Status");
605
606 Ok(GetNodeInfoResponse {
607 pub_key: self.node.node_id(),
608 alias: match self.node.node_alias() {
609 Some(alias) => alias.to_string(),
610 None => format!("LDK Fedimint Gateway Node {}", self.node.node_id()),
611 },
612 network: self.node.config().network.to_string(),
613 block_height: ldk_block_height,
614 synced_to_chain: lightning_sync.is_some(),
617 })
618 }
619
620 async fn routehints(
621 &self,
622 _num_route_hints: usize,
623 ) -> Result<GetRouteHintsResponse, LightningRpcError> {
624 Ok(GetRouteHintsResponse {
630 route_hints: vec![],
631 })
632 }
633
634 async fn pay(
635 &self,
636 invoice: Bolt11Invoice,
637 max_delay: u64,
638 max_fee: Amount,
639 ) -> Result<PayInvoiceResponse, LightningRpcError> {
640 let payment_id = PaymentId(*invoice.payment_hash().as_byte_array());
641
642 let _payment_lock_guard = self
648 .outbound_lightning_payment_lock_pool
649 .async_lock(payment_id)
650 .await;
651
652 let (payment_sender, payment_receiver) = oneshot::channel();
656 self.pending_payments
657 .write()
658 .await
659 .insert(payment_id, payment_sender);
660
661 if self.outbound_payment(payment_id).is_none() {
672 let sent_payment_id = match self.node.bolt11_payment().send(
673 &invoice,
674 Some(
675 RouteParametersConfig::default()
676 .with_max_total_routing_fee_msat(max_fee.msats)
677 .with_max_total_cltv_expiry_delta(max_delay as u32),
678 ),
679 ) {
680 Ok(sent_payment_id) => sent_payment_id,
681 Err(err) => {
682 self.pending_payments.write().await.remove(&payment_id);
683 return Err(LightningRpcError::FailedPayment {
686 failure_reason: format!("LDK payment failed to initialize: {err:?}"),
687 });
688 }
689 };
690 assert_eq!(sent_payment_id, payment_id);
691 }
692
693 if let Some(result) = self.ldk_payment_result(payment_id) {
697 self.pending_payments.write().await.remove(&payment_id);
698 return result;
699 }
700
701 let _ = payment_receiver.await;
706
707 self.pending_payments.write().await.remove(&payment_id);
708 self.ldk_payment_result(payment_id).unwrap_or_else(|| {
709 Err(LightningRpcError::FailedPayment {
710 failure_reason: "LDK payment event fired without terminal payment status"
711 .to_string(),
712 })
713 })
714 }
715
716 async fn outbound_payment_exists(
717 &self,
718 payment_hash: sha256::Hash,
719 ) -> Result<bool, LightningRpcError> {
720 Ok(self
721 .outbound_payment(PaymentId(payment_hash.to_byte_array()))
722 .is_some())
723 }
724
725 async fn route_htlcs<'a>(
726 mut self: Box<Self>,
727 _task_group: &TaskGroup,
728 ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
729 let route_htlc_stream = match self.htlc_stream_receiver_or.take() {
730 Some(stream) => Ok(Box::pin(ReceiverStream::new(stream))),
731 None => Err(LightningRpcError::FailedToRouteHtlcs {
732 failure_reason:
733 "Stream does not exist. Likely was already taken by calling `route_htlcs()`."
734 .to_string(),
735 }),
736 }?;
737
738 Ok((route_htlc_stream, Arc::new(*self)))
739 }
740
741 async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError> {
742 let InterceptPaymentResponse {
743 action,
744 payment_hash,
745 incoming_chan_id: _,
746 htlc_id: _,
747 } = htlc;
748
749 let ph = PaymentHash(*payment_hash.clone().as_byte_array());
750
751 let claimable_amount_msat = 999_999_999_999_999;
757
758 let ph_hex_str = hex::encode(payment_hash);
759
760 if let PaymentAction::Settle(preimage) = action {
761 self.node
762 .bolt11_payment()
763 .claim_for_hash(ph, claimable_amount_msat, PaymentPreimage(preimage.0))
764 .map_err(|err| htlc_completion_error(&err, &ph_hex_str))?;
765 } else {
766 warn!(target: LOG_LIGHTNING, payment_hash = %ph_hex_str, "Unwinding payment because the action was not `Settle`");
767 self.node
768 .bolt11_payment()
769 .fail_for_hash(ph)
770 .map_err(|err| htlc_completion_error(&err, &ph_hex_str))?;
771 }
772
773 return Ok(());
774 }
775
776 async fn create_invoice(
777 &self,
778 create_invoice_request: CreateInvoiceRequest,
779 ) -> Result<CreateInvoiceResponse, LightningRpcError> {
780 let payment_hash_or = if let Some(payment_hash) = create_invoice_request.payment_hash {
781 let ph = PaymentHash(*payment_hash.as_byte_array());
782 Some(ph)
783 } else {
784 None
785 };
786
787 let description = match create_invoice_request.description {
788 Some(InvoiceDescription::Direct(desc)) => {
789 Bolt11InvoiceDescription::Direct(Description::new(desc).map_err(|_| {
790 LightningRpcError::FailedToGetInvoice {
791 failure_reason: "Invalid description".to_string(),
792 }
793 })?)
794 }
795 Some(InvoiceDescription::Hash(hash)) => {
796 Bolt11InvoiceDescription::Hash(lightning_invoice::Sha256(hash))
797 }
798 None => Bolt11InvoiceDescription::Direct(Description::empty()),
799 };
800
801 let invoice = match payment_hash_or {
802 Some(payment_hash) => {
803 let payment_id = PaymentId(payment_hash.0);
810 let outbound_lock_guard = self
811 .outbound_lightning_payment_lock_pool
812 .try_lock(payment_id);
813 let existing_direction = self
814 .node
815 .payment(&payment_id)
816 .map(|details| details.direction);
817 if let Err(refusal) =
818 check_inbound_registration(outbound_lock_guard.is_some(), existing_direction)
819 {
820 warn!(
821 target: LOG_LIGHTNING,
822 payment_hash = %hex::encode(payment_hash.0),
823 %refusal,
824 "Refusing to register an invoice for a payment hash we pay outbound"
825 );
826 return Err(LightningRpcError::FailedToGetInvoice {
827 failure_reason: format!(
828 "Payment hash cannot be registered for an invoice: {refusal}"
829 ),
830 });
831 }
832
833 self.node.bolt11_payment().receive_for_hash(
834 create_invoice_request.amount_msat,
835 &description,
836 create_invoice_request.expiry_secs,
837 payment_hash,
838 )
839 }
840 None => self.node.bolt11_payment().receive(
841 create_invoice_request.amount_msat,
842 &description,
843 create_invoice_request.expiry_secs,
844 ),
845 }
846 .map_err(|e| LightningRpcError::FailedToGetInvoice {
847 failure_reason: e.to_string(),
848 })?;
849
850 Ok(CreateInvoiceResponse {
851 invoice: invoice.to_string(),
852 })
853 }
854
855 async fn get_ln_onchain_address(
856 &self,
857 ) -> Result<GetLnOnchainAddressResponse, LightningRpcError> {
858 self.node
859 .onchain_payment()
860 .new_address()
861 .map(|address| GetLnOnchainAddressResponse {
862 address: address.to_string(),
863 })
864 .map_err(|e| LightningRpcError::FailedToGetLnOnchainAddress {
865 failure_reason: e.to_string(),
866 })
867 }
868
869 async fn send_onchain(
870 &self,
871 SendOnchainRequest {
872 address,
873 amount,
874 fee_rate_sats_per_vbyte,
875 }: SendOnchainRequest,
876 ) -> Result<SendOnchainResponse, LightningRpcError> {
877 let onchain = self.node.onchain_payment();
878
879 let retain_reserves = false;
880 let txid = match amount {
881 BitcoinAmountOrAll::All => onchain.send_all_to_address(
882 &address.assume_checked(),
883 retain_reserves,
884 FeeRate::from_sat_per_vb(fee_rate_sats_per_vbyte),
885 ),
886 BitcoinAmountOrAll::Amount(amount_sats) => onchain.send_to_address(
887 &address.assume_checked(),
888 amount_sats.to_sat(),
889 FeeRate::from_sat_per_vb(fee_rate_sats_per_vbyte),
890 ),
891 }
892 .map_err(|e| LightningRpcError::FailedToWithdrawOnchain {
893 failure_reason: e.to_string(),
894 })?;
895
896 Ok(SendOnchainResponse {
897 txid: txid.to_string(),
898 })
899 }
900
901 async fn open_channel(
902 &self,
903 OpenChannelRequest {
904 pubkey,
905 host,
906 channel_size_sats,
907 push_amount_sats,
908 fee_rate_sats_per_vbyte,
909 base_fee_msat,
910 parts_per_million,
911 }: OpenChannelRequest,
912 ) -> Result<OpenChannelResponse, LightningRpcError> {
913 let push_amount_msats_or = if push_amount_sats == 0 {
914 None
915 } else {
916 Some(push_amount_sats * 1000)
917 };
918
919 if fee_rate_sats_per_vbyte.is_some() {
920 warn!(
923 target: LOG_LIGHTNING,
924 "Ignoring fee_rate_sats_per_vbyte on LDK channel open; LDK uses its built-in fee estimator"
925 );
926 }
927
928 let channel_config = match (base_fee_msat, parts_per_million) {
929 (None, None) => None,
930 (base, ppm) => {
931 let mut config = ChannelConfig::default();
932 if let Some(base) = base {
933 config.forwarding_fee_base_msat = u32::try_from(base).map_err(|_| {
934 LightningRpcError::FailedToOpenChannel {
935 failure_reason: format!(
936 "base_fee_msat {base} does not fit in u32 (LDK limit)"
937 ),
938 }
939 })?;
940 }
941 if let Some(ppm) = ppm {
942 config.forwarding_fee_proportional_millionths =
943 u32::try_from(ppm).map_err(|_| LightningRpcError::FailedToOpenChannel {
944 failure_reason: format!(
945 "parts_per_million {ppm} does not fit in u32 (LDK limit)"
946 ),
947 })?;
948 }
949 Some(config)
950 }
951 };
952
953 let (tx, rx) = oneshot::channel::<Result<OutPoint, String>>();
954
955 {
956 let mut channels = self.pending_channels.write().await;
957 let user_channel_id = self
958 .node
959 .open_announced_channel(
960 pubkey,
961 SocketAddress::from_str(&host).map_err(|e| {
962 LightningRpcError::FailedToConnectToPeer {
963 failure_reason: e.to_string(),
964 }
965 })?,
966 channel_size_sats,
967 push_amount_msats_or,
968 channel_config,
969 )
970 .map_err(|e| LightningRpcError::FailedToOpenChannel {
971 failure_reason: e.to_string(),
972 })?;
973
974 channels.insert(UserChannelId(user_channel_id), tx);
975 }
976
977 match rx
978 .await
979 .map_err(|err| LightningRpcError::FailedToOpenChannel {
980 failure_reason: err.to_string(),
981 })? {
982 Ok(outpoint) => {
983 let funding_txid = outpoint.txid;
984
985 Ok(OpenChannelResponse {
986 funding_txid: funding_txid.to_string(),
987 })
988 }
989 Err(failure_reason) => Err(LightningRpcError::FailedToOpenChannel { failure_reason }),
990 }
991 }
992
993 async fn connect_peer(&self, payload: ConnectPeerRequest) -> Result<(), LightningRpcError> {
994 let NodeAddress { pubkey, address } = payload.node_address;
995 self.node.connect(pubkey, address, true).map_err(|e| {
1001 LightningRpcError::FailedToConnectToPeer {
1002 failure_reason: e.to_string(),
1003 }
1004 })
1005 }
1006
1007 async fn close_channels_with_peer(
1008 &self,
1009 CloseChannelsWithPeerRequest {
1010 pubkey,
1011 force,
1012 sats_per_vbyte: _,
1013 }: CloseChannelsWithPeerRequest,
1014 ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
1015 let mut num_channels_closed = 0;
1016
1017 info!(%pubkey, "Closing all channels with peer");
1018 for channel_with_peer in self
1019 .node
1020 .list_channels()
1021 .iter()
1022 .filter(|channel| channel.counterparty_node_id == pubkey)
1023 {
1024 if force {
1025 match self.node.force_close_channel(
1026 &channel_with_peer.user_channel_id,
1027 pubkey,
1028 Some("User initiated force close".to_string()),
1029 ) {
1030 Ok(()) => num_channels_closed += 1,
1031 Err(err) => {
1032 error!(%pubkey, err = %err.fmt_compact(), "Could not force close channel");
1033 }
1034 }
1035 } else {
1036 match self
1037 .node
1038 .close_channel(&channel_with_peer.user_channel_id, pubkey)
1039 {
1040 Ok(()) => {
1041 num_channels_closed += 1;
1042 }
1043 Err(err) => {
1044 error!(%pubkey, err = %err.fmt_compact(), "Could not close channel");
1045 }
1046 }
1047 }
1048 }
1049
1050 Ok(CloseChannelsWithPeerResponse {
1051 num_channels_closed,
1052 })
1053 }
1054
1055 async fn list_channels(&self) -> Result<ListChannelsResponse, LightningRpcError> {
1056 let mut channels = Vec::new();
1057 let network_graph = self.node.network_graph();
1058
1059 let peer_addresses: std::collections::HashMap<_, _> = self
1061 .node
1062 .list_peers()
1063 .into_iter()
1064 .map(|peer| (peer.node_id, peer.address.to_string()))
1065 .collect();
1066
1067 for channel_details in self.node.list_channels().iter() {
1068 let node_id = NodeId::from_pubkey(&channel_details.counterparty_node_id);
1069 let node_info = network_graph.node(&node_id);
1070
1071 let remote_node_alias = node_info.as_ref().and_then(|info| {
1073 info.announcement_info.as_ref().and_then(|announcement| {
1074 let alias = announcement.alias().to_string();
1075 if alias.is_empty() { None } else { Some(alias) }
1076 })
1077 });
1078
1079 let remote_address = peer_addresses
1080 .get(&channel_details.counterparty_node_id)
1081 .cloned();
1082
1083 channels.push(ChannelInfo {
1084 remote_pubkey: channel_details.counterparty_node_id,
1085 channel_size_sats: channel_details.channel_value_sats,
1086 outbound_liquidity_sats: channel_details.outbound_capacity_msat / 1000,
1087 inbound_liquidity_sats: channel_details.inbound_capacity_msat / 1000,
1088 is_active: channel_details.is_usable,
1089 funding_outpoint: channel_details.funding_txo,
1090 remote_node_alias,
1091 remote_address,
1092 base_fee_msat: Some(u64::from(channel_details.config.forwarding_fee_base_msat)),
1093 parts_per_million: Some(u64::from(
1094 channel_details
1095 .config
1096 .forwarding_fee_proportional_millionths,
1097 )),
1098 });
1099 }
1100
1101 Ok(ListChannelsResponse { channels })
1102 }
1103
1104 async fn set_channel_fees(
1105 &self,
1106 payload: SetChannelFeesRequest,
1107 ) -> Result<(), LightningRpcError> {
1108 let channel = self
1112 .node
1113 .list_channels()
1114 .into_iter()
1115 .find(|c| c.funding_txo == Some(payload.funding_outpoint))
1116 .ok_or_else(|| LightningRpcError::FailedToSetChannelFees {
1117 failure_reason: format!(
1118 "No channel found with funding outpoint {}",
1119 payload.funding_outpoint,
1120 ),
1121 })?;
1122
1123 let forwarding_fee_base_msat = u32::try_from(payload.base_fee_msat).map_err(|_| {
1124 LightningRpcError::FailedToSetChannelFees {
1125 failure_reason: format!(
1126 "base_fee_msat {} does not fit in u32 (LDK limit)",
1127 payload.base_fee_msat,
1128 ),
1129 }
1130 })?;
1131 let forwarding_fee_proportional_millionths = u32::try_from(payload.parts_per_million)
1132 .map_err(|_| LightningRpcError::FailedToSetChannelFees {
1133 failure_reason: format!(
1134 "parts_per_million {} does not fit in u32 (LDK limit)",
1135 payload.parts_per_million,
1136 ),
1137 })?;
1138
1139 let new_config = ChannelConfig {
1142 forwarding_fee_base_msat,
1143 forwarding_fee_proportional_millionths,
1144 ..channel.config
1145 };
1146
1147 self.node
1148 .update_channel_config(
1149 &channel.user_channel_id,
1150 channel.counterparty_node_id,
1151 new_config,
1152 )
1153 .map_err(|e| LightningRpcError::FailedToSetChannelFees {
1154 failure_reason: e.to_string(),
1155 })?;
1156
1157 Ok(())
1158 }
1159
1160 async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
1161 let balances = self.node.list_balances();
1162 let channel_lists = self
1163 .node
1164 .list_channels()
1165 .into_iter()
1166 .filter(|chan| chan.is_usable)
1167 .collect::<Vec<_>>();
1168 let total_inbound_liquidity_balance_msat: u64 = channel_lists
1170 .iter()
1171 .map(|channel| channel.inbound_capacity_msat)
1172 .sum();
1173
1174 Ok(GetBalancesResponse {
1175 onchain_balance_sats: balances.total_onchain_balance_sats,
1176 lightning_balance_msats: balances.total_lightning_balance_sats * 1000,
1177 inbound_lightning_liquidity_msats: total_inbound_liquidity_balance_msat,
1178 })
1179 }
1180
1181 async fn get_invoice(
1182 &self,
1183 get_invoice_request: GetInvoiceRequest,
1184 ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
1185 let invoices = self
1186 .node
1187 .list_payments_with_filter(|details| {
1188 details.direction == PaymentDirection::Inbound
1189 && details.id == PaymentId(get_invoice_request.payment_hash.to_byte_array())
1190 && !matches!(details.kind, PaymentKind::Onchain { .. })
1191 })
1192 .iter()
1193 .map(|details| {
1194 let (preimage, payment_hash, _) = get_preimage_and_payment_hash(&details.kind);
1195 let status = match details.status {
1196 PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
1197 PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
1198 PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
1199 };
1200 GetInvoiceResponse {
1201 preimage: preimage.map(|p| p.to_string()),
1202 payment_hash,
1203 amount: Amount::from_msats(
1204 details
1205 .amount_msat
1206 .expect("amountless invoices are not supported"),
1207 ),
1208 created_at: UNIX_EPOCH + Duration::from_secs(details.latest_update_timestamp),
1209 status,
1210 }
1211 })
1212 .collect::<Vec<_>>();
1213
1214 Ok(invoices.first().cloned())
1215 }
1216
1217 async fn list_transactions(
1218 &self,
1219 start_secs: u64,
1220 end_secs: u64,
1221 ) -> Result<ListTransactionsResponse, LightningRpcError> {
1222 let transactions = self
1223 .node
1224 .list_payments_with_filter(|details| {
1225 !matches!(details.kind, PaymentKind::Onchain { .. })
1226 && details.latest_update_timestamp >= start_secs
1227 && details.latest_update_timestamp < end_secs
1228 })
1229 .iter()
1230 .map(|details| {
1231 let (preimage, payment_hash, payment_kind) =
1232 get_preimage_and_payment_hash(&details.kind);
1233 let direction = match details.direction {
1234 PaymentDirection::Outbound => {
1235 fedimint_gateway_common::PaymentDirection::Outbound
1236 }
1237 PaymentDirection::Inbound => fedimint_gateway_common::PaymentDirection::Inbound,
1238 };
1239 let status = match details.status {
1240 PaymentStatus::Failed => fedimint_gateway_common::PaymentStatus::Failed,
1241 PaymentStatus::Succeeded => fedimint_gateway_common::PaymentStatus::Succeeded,
1242 PaymentStatus::Pending => fedimint_gateway_common::PaymentStatus::Pending,
1243 };
1244 fedimint_gateway_common::PaymentDetails {
1245 payment_hash,
1246 preimage: preimage.map(|p| p.to_string()),
1247 payment_kind,
1248 amount: Amount::from_msats(
1249 details
1250 .amount_msat
1251 .expect("amountless invoices are not supported"),
1252 ),
1253 direction,
1254 status,
1255 timestamp_secs: details.latest_update_timestamp,
1256 }
1257 })
1258 .collect::<Vec<_>>();
1259 Ok(ListTransactionsResponse { transactions })
1260 }
1261
1262 fn create_offer(
1263 &self,
1264 amount: Option<Amount>,
1265 description: Option<String>,
1266 expiry_secs: Option<u32>,
1267 quantity: Option<u64>,
1268 ) -> Result<String, LightningRpcError> {
1269 let description = description.unwrap_or_default();
1270 let offer = if let Some(amount) = amount {
1271 self.node
1272 .bolt12_payment()
1273 .receive(amount.msats, &description, expiry_secs, quantity)
1274 .map_err(|err| LightningRpcError::Bolt12Error {
1275 failure_reason: err.to_string(),
1276 })?
1277 } else {
1278 self.node
1279 .bolt12_payment()
1280 .receive_variable_amount(&description, expiry_secs)
1281 .map_err(|err| LightningRpcError::Bolt12Error {
1282 failure_reason: err.to_string(),
1283 })?
1284 };
1285
1286 Ok(offer.to_string())
1287 }
1288
1289 async fn pay_offer(
1290 &self,
1291 offer: String,
1292 quantity: Option<u64>,
1293 amount: Option<Amount>,
1294 payer_note: Option<String>,
1295 ) -> Result<Preimage, LightningRpcError> {
1296 let offer = Offer::from_str(&offer).map_err(|_| LightningRpcError::Bolt12Error {
1297 failure_reason: "Failed to parse Bolt12 Offer".to_string(),
1298 })?;
1299
1300 let _offer_lock_guard = self
1301 .outbound_offer_lock_pool
1302 .blocking_lock(LdkOfferId(offer.id()));
1303
1304 let payment_id = if let Some(amount) = amount {
1305 self.node
1306 .bolt12_payment()
1307 .send_using_amount(&offer, amount.msats, quantity, payer_note, None)
1308 .map_err(|err| LightningRpcError::Bolt12Error {
1309 failure_reason: err.to_string(),
1310 })?
1311 } else {
1312 self.node
1313 .bolt12_payment()
1314 .send(&offer, quantity, payer_note, None)
1315 .map_err(|err| LightningRpcError::Bolt12Error {
1316 failure_reason: err.to_string(),
1317 })?
1318 };
1319
1320 loop {
1321 if let Some(payment_details) = self.node.payment(&payment_id) {
1322 match payment_details.status {
1323 PaymentStatus::Pending => {}
1324 PaymentStatus::Succeeded => match payment_details.kind {
1325 PaymentKind::Bolt12Offer {
1326 preimage: Some(preimage),
1327 ..
1328 } => {
1329 info!(target: LOG_LIGHTNING, offer = %offer, payment_id = %payment_id, preimage = %preimage, "Successfully paid offer");
1330 return Ok(Preimage(preimage.0));
1331 }
1332 _ => {
1333 return Err(LightningRpcError::FailedPayment {
1334 failure_reason: "Unexpected payment kind".to_string(),
1335 });
1336 }
1337 },
1338 PaymentStatus::Failed => {
1339 return Err(LightningRpcError::FailedPayment {
1340 failure_reason: "Bolt12 payment failed".to_string(),
1341 });
1342 }
1343 }
1344 }
1345 fedimint_core::runtime::sleep(Duration::from_millis(100)).await;
1346 }
1347 }
1348
1349 fn sync_wallet(&self) -> Result<(), LightningRpcError> {
1350 block_in_place(|| {
1351 let _ = self.node.sync_wallets();
1352 });
1353 Ok(())
1354 }
1355}
1356
1357fn get_preimage_and_payment_hash(
1360 kind: &PaymentKind,
1361) -> (
1362 Option<Preimage>,
1363 Option<sha256::Hash>,
1364 fedimint_gateway_common::PaymentKind,
1365) {
1366 match kind {
1367 PaymentKind::Bolt11 {
1368 hash,
1369 preimage,
1370 secret: _,
1371 } => (
1372 preimage.map(|p| Preimage(p.0)),
1373 Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
1374 fedimint_gateway_common::PaymentKind::Bolt11,
1375 ),
1376 PaymentKind::Bolt11Jit {
1377 hash,
1378 preimage,
1379 secret: _,
1380 lsp_fee_limits: _,
1381 ..
1382 } => (
1383 preimage.map(|p| Preimage(p.0)),
1384 Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
1385 fedimint_gateway_common::PaymentKind::Bolt11,
1386 ),
1387 PaymentKind::Bolt12Offer {
1388 hash,
1389 preimage,
1390 secret: _,
1391 offer_id: _,
1392 payer_note: _,
1393 quantity: _,
1394 } => (
1395 preimage.map(|p| Preimage(p.0)),
1396 hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
1397 fedimint_gateway_common::PaymentKind::Bolt12Offer,
1398 ),
1399 PaymentKind::Bolt12Refund {
1400 hash,
1401 preimage,
1402 secret: _,
1403 payer_note: _,
1404 quantity: _,
1405 } => (
1406 preimage.map(|p| Preimage(p.0)),
1407 hash.map(|h| sha256::Hash::from_slice(&h.0).expect("Failed to convert payment hash")),
1408 fedimint_gateway_common::PaymentKind::Bolt12Refund,
1409 ),
1410 PaymentKind::Spontaneous { hash, preimage } => (
1411 preimage.map(|p| Preimage(p.0)),
1412 Some(sha256::Hash::from_slice(&hash.0).expect("Failed to convert payment hash")),
1413 fedimint_gateway_common::PaymentKind::Bolt11,
1414 ),
1415 PaymentKind::Onchain { .. } => (None, None, fedimint_gateway_common::PaymentKind::Onchain),
1416 }
1417}
1418
1419fn get_esplora_url(server_url: SafeUrl) -> Result<String, LdkClientInitError> {
1427 let host = server_url
1429 .host_str()
1430 .ok_or(LdkClientInitError::MissingEsploraHost)?;
1431 let server_url = if let Some(port) = server_url.port() {
1432 format!("{}://{}:{}", server_url.scheme(), host, port)
1433 } else {
1434 server_url.to_string()
1435 };
1436 Ok(server_url)
1437}
1438
1439#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1441enum PendingPaymentWakeup {
1442 NoWaiter,
1444 Woken,
1446 ReceiverDropped,
1448}
1449
1450#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1451struct LdkOfferId(OfferId);
1452
1453impl std::hash::Hash for LdkOfferId {
1454 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1455 state.write(&self.0.0);
1456 }
1457}
1458
1459#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1460pub struct UserChannelId(pub ldk_node::UserChannelId);
1461
1462impl PartialOrd for UserChannelId {
1463 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
1464 Some(self.cmp(other))
1465 }
1466}
1467
1468impl Ord for UserChannelId {
1469 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
1470 self.0.0.cmp(&other.0.0)
1471 }
1472}
1473
1474#[cfg(test)]
1475mod tests;