Skip to main content

fedimint_lightning/
lib.rs

1pub mod ldk;
2pub mod lnd;
3pub mod metrics;
4
5use std::fmt::Debug;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use bitcoin::Network;
11use bitcoin::hashes::sha256;
12use fedimint_core::Amount;
13use fedimint_core::encoding::{Decodable, Encodable};
14use fedimint_core::envs::{FM_IN_DEVIMINT_ENV, is_env_var_set};
15use fedimint_core::secp256k1::PublicKey;
16use fedimint_core::task::TaskGroup;
17use fedimint_core::time::now;
18use fedimint_core::util::{FmtCompact as _, FmtCompactResult as _, backoff_util, retry};
19use fedimint_gateway_common::{
20    ChannelInfo, CloseChannelsWithPeerRequest, CloseChannelsWithPeerResponse, ConnectPeerRequest,
21    GetInvoiceRequest, GetInvoiceResponse, LightningInfo, ListTransactionsResponse,
22    OpenChannelRequest, SendOnchainRequest, SetChannelFeesRequest,
23};
24use fedimint_ln_common::PrunedInvoice;
25pub use fedimint_ln_common::contracts::Preimage;
26use fedimint_ln_common::route_hints::RouteHint;
27use fedimint_logging::LOG_LIGHTNING;
28use fedimint_metrics::HistogramExt as _;
29use futures::future::BoxFuture;
30use futures::stream::BoxStream;
31use lightning_invoice::Bolt11Invoice;
32use serde::{Deserialize, Serialize};
33use thiserror::Error;
34use tracing::{info, trace, warn};
35
36pub const MAX_LIGHTNING_RETRIES: u32 = 10;
37
38pub type RouteHtlcStream<'a> = BoxStream<'a, InterceptPaymentRequest>;
39
40/// Returns `true` if the given payment hash corresponds to a HOLD invoice that
41/// the gateway created on behalf of a federation. Used by the LND backend to
42/// ignore unrelated HOLD invoices on a shared LND node, which would otherwise
43/// be mistaken for federation-bound payments and produce invalid responses on
44/// LND's HTLC interceptor wire.
45pub type Lnv2HoldInvoiceFilter =
46    Arc<dyn Fn(sha256::Hash) -> BoxFuture<'static, bool> + Send + Sync + 'static>;
47
48#[derive(
49    Error, Debug, Serialize, Deserialize, Encodable, Decodable, Clone, Eq, PartialEq, Hash,
50)]
51pub enum LightningRpcError {
52    #[error("Failed to connect to Lightning node")]
53    FailedToConnect,
54    #[error("Failed to retrieve node info: {failure_reason}")]
55    FailedToGetNodeInfo { failure_reason: String },
56    #[error("Failed to retrieve route hints: {failure_reason}")]
57    FailedToGetRouteHints { failure_reason: String },
58    #[error("Payment failed: {failure_reason}")]
59    FailedPayment { failure_reason: String },
60    #[error("Failed to route HTLCs: {failure_reason}")]
61    FailedToRouteHtlcs { failure_reason: String },
62    #[error("Failed to complete HTLC: {failure_reason}")]
63    FailedToCompleteHtlc { failure_reason: String },
64    #[error("Failed to open channel: {failure_reason}")]
65    FailedToOpenChannel { failure_reason: String },
66    #[error("Failed to close channel: {failure_reason}")]
67    FailedToCloseChannelsWithPeer { failure_reason: String },
68    #[error("Failed to set channel fees: {failure_reason}")]
69    FailedToSetChannelFees { failure_reason: String },
70    #[error("Failed to get Invoice: {failure_reason}")]
71    FailedToGetInvoice { failure_reason: String },
72    #[error("Failed to list transactions: {failure_reason}")]
73    FailedToListTransactions { failure_reason: String },
74    #[error("Failed to get funding address: {failure_reason}")]
75    FailedToGetLnOnchainAddress { failure_reason: String },
76    #[error("Failed to withdraw funds on-chain: {failure_reason}")]
77    FailedToWithdrawOnchain { failure_reason: String },
78    #[error("Failed to connect to peer: {failure_reason}")]
79    FailedToConnectToPeer { failure_reason: String },
80    #[error("Failed to list active channels: {failure_reason}")]
81    FailedToListChannels { failure_reason: String },
82    #[error("Failed to get balances: {failure_reason}")]
83    FailedToGetBalances { failure_reason: String },
84    #[error("Failed to sync to chain: {failure_reason}")]
85    FailedToSyncToChain { failure_reason: String },
86    #[error("Invalid metadata: {failure_reason}")]
87    InvalidMetadata { failure_reason: String },
88    #[error("Bolt12 Error: {failure_reason}")]
89    Bolt12Error { failure_reason: String },
90    // This type is consensus-encoded with positional variant indices and is
91    // persisted in gateway client state machines: only append new variants.
92    #[error("HTLC completion cannot reach the requested outcome: {failure_reason}")]
93    HtlcCompletionRejected { failure_reason: String },
94}
95
96/// Represents an active connection to the lightning node.
97#[derive(Clone, Debug)]
98pub struct LightningContext {
99    pub lnrpc: Arc<dyn ILnRpcClient>,
100    pub lightning_public_key: PublicKey,
101    pub lightning_alias: String,
102    pub lightning_network: Network,
103}
104
105/// A trait that the gateway uses to interact with a lightning node. This allows
106/// the gateway to be agnostic to the specific lightning node implementation
107/// being used.
108#[async_trait]
109pub trait ILnRpcClient: Debug + Send + Sync {
110    /// Returns high-level info about the lightning node.
111    async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError>;
112
113    /// Returns route hints to the lightning node.
114    ///
115    /// Note: This is only used for inbound LNv1 payments and will be removed
116    /// when we switch to LNv2.
117    async fn routehints(
118        &self,
119        num_route_hints: usize,
120    ) -> Result<GetRouteHintsResponse, LightningRpcError>;
121
122    /// Attempts to pay an invoice using the lightning node, waiting for the
123    /// payment to complete and returning the preimage.
124    ///
125    /// Caller restrictions:
126    /// May be called multiple times for the same invoice, but _should_ be done
127    /// with all the same parameters. This is because the payment may be
128    /// in-flight from a previous call, in which case fee or delay limits cannot
129    /// be changed and will be ignored.
130    ///
131    /// Implementor restrictions:
132    /// This _must_ be idempotent for a given invoice, since it is called by
133    /// state machines. In more detail, when called for a given invoice:
134    /// * If the payment is already in-flight, wait for that payment to complete
135    ///   as if it were the first call.
136    /// * If the payment has already been attempted and failed, return an error.
137    /// * If the payment has already succeeded, return a success response.
138    ///
139    /// Consult that record before enforcing `max_delay` or `max_fee`: a state
140    /// machine resuming a payment it dispatched before a restart may pass a
141    /// placeholder `max_delay` of `0`, which must only ever fail a dispatch
142    /// that would otherwise start fresh.
143    async fn pay(
144        &self,
145        invoice: Bolt11Invoice,
146        max_delay: u64,
147        max_fee: Amount,
148    ) -> Result<PayInvoiceResponse, LightningRpcError> {
149        self.pay_private(
150            PrunedInvoice::try_from(invoice).map_err(|_| LightningRpcError::FailedPayment {
151                failure_reason: "Invoice has no amount".to_string(),
152            })?,
153            max_delay,
154            max_fee,
155        )
156        .await
157    }
158
159    /// Attempts to pay an invoice using the lightning node, waiting for the
160    /// payment to complete and returning the preimage.
161    ///
162    /// This is more private than [`ILnRpcClient::pay`], as it does not require
163    /// the invoice description. If this is implemented,
164    /// [`ILnRpcClient::supports_private_payments`] must return true.
165    async fn pay_private(
166        &self,
167        _invoice: PrunedInvoice,
168        _max_delay: u64,
169        _max_fee: Amount,
170    ) -> Result<PayInvoiceResponse, LightningRpcError> {
171        Err(LightningRpcError::FailedPayment {
172            failure_reason: "Private payments not supported".to_string(),
173        })
174    }
175
176    /// Returns true if the lightning backend supports payments without full
177    /// invoices. If this returns true, [`ILnRpcClient::pay_private`] must
178    /// be implemented.
179    fn supports_private_payments(&self) -> bool {
180        false
181    }
182
183    /// Returns whether the node has any record of an outbound payment for
184    /// `payment_hash`, whatever its state: in-flight, succeeded, or failed.
185    ///
186    /// State machines call this when they resume after a restart to
187    /// distinguish a payment dispatched before the crash from one that never
188    /// left the gateway: pre-dispatch gates such as invoice expiry must not
189    /// cancel a payment the node may still settle. Implementations must
190    /// answer from the node's own payment store without waiting for the
191    /// payment to reach a terminal state, and must not count inbound records
192    /// for the same hash.
193    async fn outbound_payment_exists(
194        &self,
195        payment_hash: sha256::Hash,
196    ) -> Result<bool, LightningRpcError>;
197
198    /// Consumes the current client and returns a stream of intercepted HTLCs
199    /// and a new client. `complete_htlc` must be called for all successfully
200    /// intercepted HTLCs sent to the returned stream.
201    ///
202    /// `route_htlcs` can only be called once for a given client, since the
203    /// returned stream grants exclusive routing decisions to the caller.
204    /// For this reason, `route_htlc` consumes the client and returns one
205    /// wrapped in an `Arc`. This lets the compiler enforce that `route_htlcs`
206    /// can only be called once for a given client, since the value inside
207    /// the `Arc` cannot be consumed.
208    async fn route_htlcs<'a>(
209        self: Box<Self>,
210        task_group: &TaskGroup,
211    ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError>;
212
213    /// Completes an HTLC that was intercepted by the gateway. Must be called
214    /// for all successfully intercepted HTLCs sent to the stream returned
215    /// by `route_htlcs`.
216    ///
217    /// The gateway retries [`LightningRpcError::FailedToCompleteHtlc`] until
218    /// the call succeeds and records only
219    /// [`LightningRpcError::HtlcCompletionRejected`] as a terminal outcome, so
220    /// implementations must return the latter for a failure no retry can
221    /// change and the former for anything transient.
222    async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError>;
223
224    /// Requests the lightning node to create an invoice. The presence of a
225    /// payment hash in the `CreateInvoiceRequest` determines if the invoice is
226    /// intended to be an ecash payment or a direct payment to this lightning
227    /// node.
228    async fn create_invoice(
229        &self,
230        create_invoice_request: CreateInvoiceRequest,
231    ) -> Result<CreateInvoiceResponse, LightningRpcError>;
232
233    /// Gets a funding address belonging to the lightning node's on-chain
234    /// wallet.
235    async fn get_ln_onchain_address(
236        &self,
237    ) -> Result<GetLnOnchainAddressResponse, LightningRpcError>;
238
239    /// Executes an onchain transaction using the lightning node's on-chain
240    /// wallet.
241    async fn send_onchain(
242        &self,
243        payload: SendOnchainRequest,
244    ) -> Result<SendOnchainResponse, LightningRpcError>;
245
246    /// Opens a channel with a peer lightning node.
247    async fn open_channel(
248        &self,
249        payload: OpenChannelRequest,
250    ) -> Result<OpenChannelResponse, LightningRpcError>;
251
252    /// Connects to a peer lightning node without opening a channel.
253    async fn connect_peer(&self, payload: ConnectPeerRequest) -> Result<(), LightningRpcError>;
254
255    /// Closes all channels with a peer lightning node.
256    async fn close_channels_with_peer(
257        &self,
258        payload: CloseChannelsWithPeerRequest,
259    ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError>;
260
261    /// Lists the lightning node's active channels with all peers.
262    async fn list_channels(&self) -> Result<ListChannelsResponse, LightningRpcError>;
263
264    /// Updates the local-side routing fee policy (base fee in msat and
265    /// proportional fee in parts-per-million) advertised on a single channel
266    /// identified by its funding outpoint.
267    async fn set_channel_fees(
268        &self,
269        payload: SetChannelFeesRequest,
270    ) -> Result<(), LightningRpcError>;
271
272    /// Returns a summary of the lightning node's balance, including the onchain
273    /// wallet, outbound liquidity, and inbound liquidity.
274    async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError>;
275
276    async fn get_invoice(
277        &self,
278        get_invoice_request: GetInvoiceRequest,
279    ) -> Result<Option<GetInvoiceResponse>, LightningRpcError>;
280
281    async fn list_transactions(
282        &self,
283        start_secs: u64,
284        end_secs: u64,
285    ) -> Result<ListTransactionsResponse, LightningRpcError>;
286
287    fn create_offer(
288        &self,
289        amount: Option<Amount>,
290        description: Option<String>,
291        expiry_secs: Option<u32>,
292        quantity: Option<u64>,
293    ) -> Result<String, LightningRpcError>;
294
295    async fn pay_offer(
296        &self,
297        offer: String,
298        quantity: Option<u64>,
299        amount: Option<Amount>,
300        payer_note: Option<String>,
301    ) -> Result<Preimage, LightningRpcError>;
302
303    fn sync_wallet(&self) -> Result<(), LightningRpcError>;
304}
305
306impl dyn ILnRpcClient {
307    /// Retrieve route hints from the Lightning node, capped at
308    /// `num_route_hints`. The route hints should be ordered based on liquidity
309    /// of incoming channels.
310    pub async fn parsed_route_hints(&self, num_route_hints: u32) -> Vec<RouteHint> {
311        if num_route_hints == 0 {
312            return vec![];
313        }
314
315        let route_hints =
316            self.routehints(num_route_hints as usize)
317                .await
318                .unwrap_or(GetRouteHintsResponse {
319                    route_hints: Vec::new(),
320                });
321        route_hints.route_hints
322    }
323
324    /// Retrieves the basic information about the Gateway's connected Lightning
325    /// node.
326    pub async fn parsed_node_info(&self) -> LightningInfo {
327        if let Ok(info) = self.info().await
328            && let Ok(network) =
329                Network::from_str(&info.network).map_err(|e| LightningRpcError::InvalidMetadata {
330                    failure_reason: format!("Invalid network {}: {e}", info.network),
331                })
332        {
333            return LightningInfo::Connected {
334                public_key: info.pub_key,
335                alias: info.alias,
336                network,
337                block_height: info.block_height as u64,
338                synced_to_chain: info.synced_to_chain,
339            };
340        }
341
342        LightningInfo::NotConnected
343    }
344
345    /// Waits for the Lightning node to be synced to the Bitcoin blockchain.
346    pub async fn wait_for_chain_sync(&self) -> std::result::Result<(), LightningRpcError> {
347        // In devimint, we explicitly sync the onchain wallet to start the sync quicker
348        // than background sync would. In production, background sync is
349        // sufficient
350        if is_env_var_set(FM_IN_DEVIMINT_ENV) {
351            self.sync_wallet()?;
352        }
353
354        // Wait for the Lightning node to sync
355        retry(
356            "Wait for chain sync",
357            backoff_util::background_backoff(),
358            || async {
359                let info = self.info().await?;
360                let block_height = info.block_height;
361                if info.synced_to_chain {
362                    Ok(())
363                } else {
364                    warn!(target: LOG_LIGHTNING, block_height = %block_height, "Lightning node is not synced yet");
365                    Err(ChainSyncAttemptError::NotSynced)
366                }
367            },
368        )
369        .await
370        .map_err(|e| LightningRpcError::FailedToSyncToChain {
371            failure_reason: format!("Failed to sync to chain: {}", e.fmt_compact()),
372        })?;
373
374        info!(target: LOG_LIGHTNING, "Gateway successfully synced with the chain");
375        Ok(())
376    }
377}
378
379#[derive(Debug, Serialize, Deserialize, Clone)]
380pub struct GetNodeInfoResponse {
381    pub pub_key: PublicKey,
382    pub alias: String,
383    pub network: String,
384    pub block_height: u32,
385    pub synced_to_chain: bool,
386}
387
388/// The `(incoming_chan_id, htlc_id)` circuit key reported for a payment that
389/// did not arrive as an intercepted forward and therefore has no incoming
390/// circuit to resolve: an LNv2 payment held by a HOLD invoice on the gateway's
391/// own node, and every payment reported by the LDK backend.
392///
393/// Zero is unambiguous as a marker because no channel is assigned a zero short
394/// channel id: a confirmed channel's id encodes its funding block height, and
395/// an unconfirmed one gets an alias from a high range. LND reserves zero for
396/// locally originated payments and exit hops, neither of which is a forward
397/// the gateway intercepts.
398pub const NO_INCOMING_CIRCUIT: (u64, u64) = (0, 0);
399
400#[derive(Debug, Serialize, Deserialize, Clone)]
401pub struct InterceptPaymentRequest {
402    pub payment_hash: sha256::Hash,
403    /// The amount the HTLC claims to deliver. On the LND forward-intercept path
404    /// this is the sender-written onion `amt_to_forward`, so it must never be
405    /// trusted for funding decisions. On the HOLD-invoice and LDK paths it is
406    /// the real received amount.
407    pub amount_msat: u64,
408    /// The amount actually locked in the incoming HTLC -- the real value the
409    /// gateway receives on settlement. Funding and fee checks must use this.
410    pub incoming_amount_msat: u64,
411    /// Block height at which the HTLC can no longer be claimed; `0` if unknown.
412    /// On the LND forward-intercept path this is the HTLC's raw incoming
413    /// expiry instead, which the sender chooses and which LND fails back
414    /// `DefaultFinalCltvRejectDelta` blocks before (19 since LND v0.21, 13
415    /// before).
416    pub expiry: u32,
417    pub incoming_chan_id: u64,
418    pub short_channel_id: Option<u64>,
419    pub htlc_id: u64,
420}
421
422impl InterceptPaymentRequest {
423    /// The incoming circuit of an intercepted forward, or `None` when the
424    /// payment is held by a HOLD invoice or LDK (see [`NO_INCOMING_CIRCUIT`]).
425    pub fn incoming_circuit(&self) -> Option<(u64, u64)> {
426        let circuit = (self.incoming_chan_id, self.htlc_id);
427        (circuit != NO_INCOMING_CIRCUIT).then_some(circuit)
428    }
429
430    /// Blocks left before the gateway can no longer settle this payment, for
431    /// deciding whether a fresh LNv2 incoming contract may be funded from it.
432    ///
433    /// Honest LNv2 payments arrive through an LND HOLD invoice or an LDK
434    /// claimable payment, never as an intercepted forward. A forward's expiry
435    /// is chosen by the sender and does not account for LND failing it back
436    /// early, so it counts as having no time left. Without the current height
437    /// the deadline cannot be checked, which counts the same.
438    pub fn lnv2_blocks_to_claim_deadline(&self, current_block_height: Option<u32>) -> u32 {
439        if self.incoming_circuit().is_some() {
440            return 0;
441        }
442
443        current_block_height.map_or(0, |height| self.expiry.saturating_sub(height))
444    }
445}
446
447#[derive(Debug, Serialize, Deserialize, Clone)]
448pub struct InterceptPaymentResponse {
449    pub incoming_chan_id: u64,
450    pub htlc_id: u64,
451    pub payment_hash: sha256::Hash,
452    pub action: PaymentAction,
453}
454
455impl InterceptPaymentResponse {
456    /// The incoming circuit this response resolves, or `None` when the payment
457    /// was not an intercepted forward (see [`NO_INCOMING_CIRCUIT`]).
458    ///
459    /// Backends must pick how to resolve a payment from this, never from the
460    /// payment hash. The hash is chosen by whoever is being paid, so two
461    /// unrelated payments — one intercepted forward and one HOLD invoice —
462    /// can carry the same hash, and resolving by hash would let a completion
463    /// for one settle or cancel the other.
464    pub fn incoming_circuit(&self) -> Option<(u64, u64)> {
465        let circuit = (self.incoming_chan_id, self.htlc_id);
466        (circuit != NO_INCOMING_CIRCUIT).then_some(circuit)
467    }
468}
469
470#[derive(Debug, Serialize, Deserialize, Clone)]
471pub enum PaymentAction {
472    Settle(Preimage),
473    Cancel,
474    Forward,
475}
476
477#[derive(Debug, Serialize, Deserialize, Clone)]
478pub struct GetRouteHintsResponse {
479    pub route_hints: Vec<RouteHint>,
480}
481
482#[derive(Debug, Serialize, Deserialize, Clone)]
483pub struct PayInvoiceResponse {
484    pub preimage: Preimage,
485}
486
487#[derive(Debug, Serialize, Deserialize, Clone)]
488pub struct CreateInvoiceRequest {
489    pub payment_hash: Option<sha256::Hash>,
490    pub amount_msat: u64,
491    pub expiry_secs: u32,
492    pub description: Option<InvoiceDescription>,
493}
494
495#[derive(Debug, Serialize, Deserialize, Clone)]
496pub enum InvoiceDescription {
497    Direct(String),
498    Hash(sha256::Hash),
499}
500
501#[derive(Debug, Serialize, Deserialize, Clone)]
502pub struct CreateInvoiceResponse {
503    pub invoice: String,
504}
505
506#[derive(Debug, Serialize, Deserialize, Clone)]
507pub struct GetLnOnchainAddressResponse {
508    pub address: String,
509}
510
511#[derive(Debug, Serialize, Deserialize, Clone)]
512pub struct SendOnchainResponse {
513    pub txid: String,
514}
515
516#[derive(Debug, Serialize, Deserialize, Clone)]
517pub struct OpenChannelResponse {
518    pub funding_txid: String,
519}
520
521#[derive(Debug, Serialize, Deserialize, Clone)]
522pub struct ListChannelsResponse {
523    pub channels: Vec<ChannelInfo>,
524}
525
526#[derive(Debug, Serialize, Deserialize, Clone)]
527pub struct GetBalancesResponse {
528    pub onchain_balance_sats: u64,
529    pub lightning_balance_msats: u64,
530    pub inbound_lightning_liquidity_msats: u64,
531}
532
533/// A wrapper around `Arc<dyn ILnRpcClient>` that tracks metrics for each RPC
534/// call.
535///
536/// This wrapper records the duration and success/error status of each
537/// Lightning RPC call to Prometheus metrics, allowing monitoring of
538/// Lightning node connectivity and performance.
539///
540/// Note: This wrapper is designed to wrap the `Arc<dyn ILnRpcClient>` returned
541/// from `route_htlcs`. Calling `route_htlcs` on this wrapper will panic, as
542/// `route_htlcs` should only be called once on the original client before
543/// wrapping.
544pub struct LnRpcTracked {
545    inner: Arc<dyn ILnRpcClient>,
546    name: &'static str,
547}
548
549impl std::fmt::Debug for LnRpcTracked {
550    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
551        f.debug_struct("LnRpcTracked")
552            .field("name", &self.name)
553            .field("inner", &self.inner)
554            .finish()
555    }
556}
557
558impl LnRpcTracked {
559    /// Wraps an `Arc<dyn ILnRpcClient>` with metrics tracking.
560    ///
561    /// The `name` parameter is used to distinguish different uses of the
562    /// Lightning RPC client in metrics (e.g., "gateway").
563    #[allow(clippy::new_ret_no_self)]
564    pub fn new(inner: Arc<dyn ILnRpcClient>, name: &'static str) -> Arc<dyn ILnRpcClient> {
565        Arc::new(Self { inner, name })
566    }
567
568    fn record_call<T, E>(&self, method: &str, result: &Result<T, E>) {
569        let result_label = if result.is_ok() { "success" } else { "error" };
570        metrics::LN_RPC_REQUESTS_TOTAL
571            .with_label_values(&[method, self.name, result_label])
572            .inc();
573    }
574}
575
576macro_rules! tracked_call {
577    ($self:ident, $method:expr, $call:expr) => {{
578        trace!(
579            target: LOG_LIGHTNING,
580            method = $method,
581            name = $self.name,
582            "starting lightning rpc"
583        );
584        let start = now();
585        let timer = metrics::LN_RPC_DURATION_SECONDS
586            .with_label_values(&[$method, $self.name])
587            .start_timer_ext();
588        let result = $call;
589        timer.observe_duration();
590        $self.record_call($method, &result);
591        let duration_ms = now()
592            .duration_since(start)
593            .unwrap_or_default()
594            .as_secs_f64()
595            * 1000.0;
596        trace!(
597            target: LOG_LIGHTNING,
598            method = $method,
599            name = $self.name,
600            duration_ms,
601            error = %result.fmt_compact_result(),
602            "completed lightning rpc"
603        );
604        result
605    }};
606}
607
608#[async_trait]
609impl ILnRpcClient for LnRpcTracked {
610    async fn info(&self) -> Result<GetNodeInfoResponse, LightningRpcError> {
611        tracked_call!(self, "info", self.inner.info().await)
612    }
613
614    async fn routehints(
615        &self,
616        num_route_hints: usize,
617    ) -> Result<GetRouteHintsResponse, LightningRpcError> {
618        tracked_call!(
619            self,
620            "routehints",
621            self.inner.routehints(num_route_hints).await
622        )
623    }
624
625    async fn pay(
626        &self,
627        invoice: Bolt11Invoice,
628        max_delay: u64,
629        max_fee: Amount,
630    ) -> Result<PayInvoiceResponse, LightningRpcError> {
631        tracked_call!(
632            self,
633            "pay",
634            self.inner.pay(invoice, max_delay, max_fee).await
635        )
636    }
637
638    async fn pay_private(
639        &self,
640        invoice: PrunedInvoice,
641        max_delay: u64,
642        max_fee: Amount,
643    ) -> Result<PayInvoiceResponse, LightningRpcError> {
644        tracked_call!(
645            self,
646            "pay_private",
647            self.inner.pay_private(invoice, max_delay, max_fee).await
648        )
649    }
650
651    fn supports_private_payments(&self) -> bool {
652        self.inner.supports_private_payments()
653    }
654
655    async fn outbound_payment_exists(
656        &self,
657        payment_hash: sha256::Hash,
658    ) -> Result<bool, LightningRpcError> {
659        tracked_call!(
660            self,
661            "outbound_payment_exists",
662            self.inner.outbound_payment_exists(payment_hash).await
663        )
664    }
665
666    async fn route_htlcs<'a>(
667        self: Box<Self>,
668        _task_group: &TaskGroup,
669    ) -> Result<(RouteHtlcStream<'a>, Arc<dyn ILnRpcClient>), LightningRpcError> {
670        // route_htlcs should only be called once on the original client before
671        // wrapping with LnRpcTracked. The Arc returned from route_htlcs should
672        // be wrapped with LnRpcTracked::new.
673        panic!(
674            "route_htlcs should not be called on LnRpcTracked. \
675             Wrap the Arc returned from route_htlcs instead."
676        );
677    }
678
679    async fn complete_htlc(&self, htlc: InterceptPaymentResponse) -> Result<(), LightningRpcError> {
680        tracked_call!(self, "complete_htlc", self.inner.complete_htlc(htlc).await)
681    }
682
683    async fn create_invoice(
684        &self,
685        create_invoice_request: CreateInvoiceRequest,
686    ) -> Result<CreateInvoiceResponse, LightningRpcError> {
687        tracked_call!(
688            self,
689            "create_invoice",
690            self.inner.create_invoice(create_invoice_request).await
691        )
692    }
693
694    async fn get_ln_onchain_address(
695        &self,
696    ) -> Result<GetLnOnchainAddressResponse, LightningRpcError> {
697        tracked_call!(
698            self,
699            "get_ln_onchain_address",
700            self.inner.get_ln_onchain_address().await
701        )
702    }
703
704    async fn send_onchain(
705        &self,
706        payload: SendOnchainRequest,
707    ) -> Result<SendOnchainResponse, LightningRpcError> {
708        tracked_call!(self, "send_onchain", self.inner.send_onchain(payload).await)
709    }
710
711    async fn open_channel(
712        &self,
713        payload: OpenChannelRequest,
714    ) -> Result<OpenChannelResponse, LightningRpcError> {
715        tracked_call!(self, "open_channel", self.inner.open_channel(payload).await)
716    }
717
718    async fn connect_peer(&self, payload: ConnectPeerRequest) -> Result<(), LightningRpcError> {
719        tracked_call!(self, "connect_peer", self.inner.connect_peer(payload).await)
720    }
721
722    async fn close_channels_with_peer(
723        &self,
724        payload: CloseChannelsWithPeerRequest,
725    ) -> Result<CloseChannelsWithPeerResponse, LightningRpcError> {
726        tracked_call!(
727            self,
728            "close_channels_with_peer",
729            self.inner.close_channels_with_peer(payload).await
730        )
731    }
732
733    async fn list_channels(&self) -> Result<ListChannelsResponse, LightningRpcError> {
734        tracked_call!(self, "list_channels", self.inner.list_channels().await)
735    }
736
737    async fn set_channel_fees(
738        &self,
739        payload: SetChannelFeesRequest,
740    ) -> Result<(), LightningRpcError> {
741        tracked_call!(
742            self,
743            "set_channel_fees",
744            self.inner.set_channel_fees(payload).await
745        )
746    }
747
748    async fn get_balances(&self) -> Result<GetBalancesResponse, LightningRpcError> {
749        tracked_call!(self, "get_balances", self.inner.get_balances().await)
750    }
751
752    async fn get_invoice(
753        &self,
754        get_invoice_request: GetInvoiceRequest,
755    ) -> Result<Option<GetInvoiceResponse>, LightningRpcError> {
756        tracked_call!(
757            self,
758            "get_invoice",
759            self.inner.get_invoice(get_invoice_request).await
760        )
761    }
762
763    async fn list_transactions(
764        &self,
765        start_secs: u64,
766        end_secs: u64,
767    ) -> Result<ListTransactionsResponse, LightningRpcError> {
768        tracked_call!(
769            self,
770            "list_transactions",
771            self.inner.list_transactions(start_secs, end_secs).await
772        )
773    }
774
775    fn create_offer(
776        &self,
777        amount: Option<Amount>,
778        description: Option<String>,
779        expiry_secs: Option<u32>,
780        quantity: Option<u64>,
781    ) -> Result<String, LightningRpcError> {
782        tracked_call!(
783            self,
784            "create_offer",
785            self.inner
786                .create_offer(amount, description, expiry_secs, quantity)
787        )
788    }
789
790    async fn pay_offer(
791        &self,
792        offer: String,
793        quantity: Option<u64>,
794        amount: Option<Amount>,
795        payer_note: Option<String>,
796    ) -> Result<Preimage, LightningRpcError> {
797        tracked_call!(
798            self,
799            "pay_offer",
800            self.inner
801                .pay_offer(offer, quantity, amount, payer_note)
802                .await
803        )
804    }
805
806    fn sync_wallet(&self) -> Result<(), LightningRpcError> {
807        tracked_call!(self, "sync_wallet", self.inner.sync_wallet())
808    }
809}
810
811/// Why one check of `wait_for_chain_sync` did not find the node synced.
812#[derive(Debug, Error)]
813enum ChainSyncAttemptError {
814    /// The node's info could not be read.
815    #[error(transparent)]
816    Info(#[from] LightningRpcError),
817
818    /// The node is not synced to the chain yet.
819    #[error("Not synced yet")]
820    NotSynced,
821}
822
823#[cfg(test)]
824mod tests;