Skip to main content

fedimint_lightning/
ldk.rs

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
44/// Forwards `ldk-node`'s log records into the gateway's `tracing` subscriber.
45///
46/// By default `ldk-node` writes to its own append-only `ldk_node/ldk_node.log`
47/// file, which is invisible to stdout/stderr log collectors and grows without
48/// bound. Routing the records through `tracing` (under the
49/// [`LOG_LIGHTNING_LDK`] target) puts them alongside the rest of gatewayd's
50/// logs and makes them filterable via `RUST_LOG`.
51struct LdkTracingLogger {
52    /// Whether we're running under devimint/tests. When set, some benign LDK
53    /// error logs that are expected in regtest are downgraded to avoid spamming
54    /// the test output. See [`Self::downgraded_level`].
55    in_test_env: bool,
56}
57
58impl LdkTracingLogger {
59    /// Returns the level to emit `record` at, downgrading benign-but-noisy LDK
60    /// errors when running under devimint/tests.
61    ///
62    /// In regtest there is no fee-rate history, so `ldk-node` logs "Failed to
63    /// retrieve fee rate estimates ... Falling back to default" at `Error` on
64    /// essentially every sync. This is harmless (LDK falls back to a default
65    /// feerate), so in test environments we emit it at `Debug` instead. In
66    /// production the original `Error` level is preserved, since a persistent
67    /// failure there can indicate a real problem.
68    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        // `tracing` requires a static level per call-site, so match each LDK level.
84        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    /// The underlying lightning node.
111    node: Arc<ldk_node::Node>,
112
113    task_group: TaskGroup,
114
115    /// The HTLC stream, until it is taken by calling
116    /// `ILnRpcClient::route_htlcs`.
117    htlc_stream_receiver_or: Option<tokio::sync::mpsc::Receiver<InterceptPaymentRequest>>,
118
119    /// Lock pool used to ensure that our implementation of `ILnRpcClient::pay`
120    /// doesn't allow for multiple simultaneous calls with the same invoice to
121    /// execute in parallel. This helps ensure that the function is idempotent.
122    outbound_lightning_payment_lock_pool: lockable::LockPool<PaymentId>,
123
124    /// Lock pool used to ensure that our implementation of
125    /// `ILnRpcClient::pay_offer` doesn't allow for multiple simultaneous
126    /// calls with the same offer to execute in parallel. This helps ensure
127    /// that the function is idempotent.
128    outbound_offer_lock_pool: lockable::LockPool<LdkOfferId>,
129
130    /// A map keyed by the `UserChannelId` of a channel that is currently
131    /// opening. The `Sender` is used to communicate the `OutPoint` back to
132    /// the API handler from the event handler when the channel has been
133    /// opened and is now pending.
134    pending_channels:
135        Arc<RwLock<BTreeMap<UserChannelId, oneshot::Sender<anyhow::Result<OutPoint>>>>>,
136
137    /// Waiters for outgoing LDK payments that are woken by terminal payment
138    /// events (`PaymentSuccessful` / `PaymentFailed`). This lets `pay()` block
139    /// until the payment resolves instead of polling `node.payment()`. The
140    /// actual result is still read from `node.payment()` after the wakeup; this
141    /// map only signals that a terminal event has arrived.
142    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    /// Creates a new `GatewayLdkClient` instance and starts the underlying
153    /// lightning node. All resources, including the lightning node, will be
154    /// cleaned up when the returned `GatewayLdkClient` instance is dropped.
155    /// There's no need to manually stop the node.
156    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        // Route LDK's logs into the gateway's `tracing` subscriber so they land in
187        // the same place (stderr / log file) and honor `RUST_LOG`, instead of LDK's
188        // default append-only `ldk_node/ldk_node.log` file.
189        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        // We manually check for task termination in case we receive a payment while the
272        // task is shutting down. In that case, we want to finish the payment
273        // before shutting this task down.
274        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        // `PaymentClaimable`, `ChannelPending`/`ChannelClosed`, and terminal
360        // outgoing payment events (`PaymentSuccessful` / `PaymentFailed`) are the
361        // only event types that we are interested in. We can safely ignore all
362        // other events.
363        if let Err(err) = node.event_handled() {
364            warn!(err = %err.fmt_compact(), "LDK could not mark event handled");
365        }
366    }
367
368    /// Wakes the `pay()` waiter (if any) for `payment_id` once a terminal
369    /// payment event has been observed. The actual payment result is read from
370    /// `node.payment()` by the woken waiter.
371    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    /// Reads the result of an outgoing payment from `node.payment()`.
387    ///
388    /// Returns `None` while the payment is still pending (or not yet known to
389    /// the node), and `Some` once it has reached a terminal status.
390    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` is used for determining if the Lightning node is ready, so we care
454            // about the `lightning_sync` status.
455            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        // `ILnRpcClient::routehints()` is currently only ever used for LNv1 payment
464        // receives and will be removed when we switch to LNv2. The LDK gateway will
465        // never support LNv1 payment receives, only LNv2 payment receives, which
466        // require that the gateway's lightning node generates invoices rather than the
467        // fedimint client, so it is able to insert the proper route hints on its own.
468        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        // Lock by the payment hash to prevent multiple simultaneous calls with the same
482        // invoice from executing. This prevents `ldk-node::Bolt11Payment::send()` from
483        // being called multiple times with the same invoice. This is important because
484        // `ldk-node::Bolt11Payment::send()` is not idempotent, but this function must
485        // be idempotent.
486        let _payment_lock_guard = self
487            .outbound_lightning_payment_lock_pool
488            .async_lock(payment_id)
489            .await;
490
491        // Register a waiter before initiating the payment so that a terminal
492        // payment event firing immediately after `send()` returns still wakes
493        // us, rather than racing ahead of the registration.
494        let (payment_sender, payment_receiver) = oneshot::channel();
495        self.pending_payments
496            .write()
497            .await
498            .insert(payment_id, payment_sender);
499
500        // If a payment is not known to the node we can initiate it, and if it is known
501        // we can skip calling `ldk-node::Bolt11Payment::send()` and wait for the
502        // payment to complete. The lock guard above guarantees that this block is only
503        // executed once at a time for a given payment hash, ensuring that there is no
504        // race condition between checking if a payment is known and initiating a new
505        // payment if it isn't.
506        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                    // TODO: Investigate whether all error types returned by
520                    // `Bolt11Payment::send()` result in idempotency.
521                    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        // The payment may already be in a terminal state (a known/resumed
530        // payment, or an event that fired before we registered the waiter), so
531        // check once up front before waiting.
532        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        // Otherwise wait for the event handler to wake us when a terminal
538        // `PaymentSuccessful` / `PaymentFailed` event arrives, instead of
539        // polling. A wakeup is delivered exactly once; the payment status is
540        // terminal by the time it fires.
541        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        // TODO: Get the actual amount from the LDK node. Probably makes the
579        // most sense to pipe it through the `InterceptHtlcResponse` struct.
580        // This value is only used by `ldk-node` to ensure that the amount
581        // claimed isn't less than the amount expected, but we've already
582        // verified that the amount is correct when we intercepted the payment.
583        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            // LDK manages its own fee estimation for funding transactions; the
719            // user-supplied rate cannot be applied here.
720            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        // Build a map of peer pubkey -> address from connected/known peers
855        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            // Look up peer alias from network graph
867            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        // ldk-node's `update_channel_config` is keyed by `UserChannelId` +
904        // counterparty pubkey, so resolve the funding outpoint to those by
905        // scanning the live channel list.
906        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        // Copy the channel's current config so we only change the two fee
935        // fields and preserve cltv_expiry_delta, dust limits, etc.
936        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        // map and get the total inbound_capacity_msat in the channels
964        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
1152/// Maps LDK's `PaymentKind` to an optional preimage and an optional payment
1153/// hash depending on the type of payment.
1154fn 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
1214/// When a port is specified in the Esplora URL, the esplora client inside LDK
1215/// node cannot connect to the lightning node when there is a trailing slash.
1216/// The `SafeUrl::Display` function will always serialize the `SafeUrl` with a
1217/// trailing slash, which causes the connection to fail.
1218///
1219/// To handle this, we explicitly construct the esplora URL when a port is
1220/// specified.
1221fn get_esplora_url(server_url: SafeUrl) -> anyhow::Result<String> {
1222    // Esplora client cannot handle trailing slashes
1223    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/// Outcome of attempting to wake a `pay()` waiter for a terminal payment event.
1235#[derive(Debug, Clone, Copy, Eq, PartialEq)]
1236enum PendingPaymentWakeup {
1237    /// No waiter was registered for the payment id.
1238    NoWaiter,
1239    /// A waiter was registered and successfully woken.
1240    Woken,
1241    /// A waiter was registered but its receiver had already been dropped.
1242    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;