Skip to content

Commit 20c842b

Browse files
committed
Add preflight probing capabilities
We add a `ChannelManager::send_preflight_probes` method that can be used to send pre-flight probes given some [`RouteParameters`]. Additionally, we add convenience methods in for spontaneous probes and send pre-flight probes for a given invoice. As pre-flight probes might take up some of the available liquidity, we here introduce that channels whose available liquidity is less than the required amount times `UserConfig::preflight_probing_liquidity_limit_multiplier` won't be used to send pre-flight probes. This commit is a more or less a carbon copy of the pre-flight probing code recently added to LDK Node.
1 parent c6a1a12 commit 20c842b

File tree

3 files changed

+192
-4
lines changed

3 files changed

+192
-4
lines changed

lightning-invoice/src/payment.rs

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99

1010
//! Convenient utilities for paying Lightning invoices.
1111
12-
use crate::Bolt11Invoice;
12+
use crate::{Bolt11Invoice, Vec};
1313

1414
use bitcoin_hashes::Hash;
1515

1616
use lightning::chain;
1717
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
1818
use lightning::sign::{NodeSigner, SignerProvider, EntropySource};
1919
use lightning::ln::PaymentHash;
20-
use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields};
20+
use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields, ProbeSendFailure};
2121
use lightning::routing::router::{PaymentParameters, RouteParameters, Router};
2222
use lightning::util::logger::Logger;
2323

@@ -163,6 +163,85 @@ fn pay_invoice_using_amount<P: Deref>(
163163
payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy)
164164
}
165165

166+
/// Sends payment probes over all paths of a route that would be used to pay the given invoice.
167+
///
168+
/// See [`ChannelManager::send_preflight_probes`] for more information.
169+
pub fn preflight_probe_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
170+
invoice: &Bolt11Invoice, channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>,
171+
liquidity_limit_multiplier: Option<u64>,
172+
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbingError>
173+
where
174+
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
175+
T::Target: BroadcasterInterface,
176+
ES::Target: EntropySource,
177+
NS::Target: NodeSigner,
178+
SP::Target: SignerProvider,
179+
F::Target: FeeEstimator,
180+
R::Target: Router,
181+
L::Target: Logger,
182+
{
183+
let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
184+
invoice_amount_msat
185+
} else {
186+
return Err(ProbingError::Invoice("Failed to send probe as no amount was given in the invoice."));
187+
};
188+
189+
let mut payment_params = PaymentParameters::from_node_id(
190+
invoice.recover_payee_pub_key(),
191+
invoice.min_final_cltv_expiry_delta() as u32,
192+
)
193+
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
194+
.with_route_hints(invoice.route_hints())
195+
.unwrap();
196+
197+
if let Some(features) = invoice.features() {
198+
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
199+
}
200+
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
201+
202+
channelmanager.send_preflight_probes(route_params, liquidity_limit_multiplier)
203+
.map_err(ProbingError::Sending)
204+
}
205+
206+
/// Sends payment probes over all paths of a route that would be used to pay the given zero-value
207+
/// invoice using the given amount.
208+
///
209+
/// See [`ChannelManager::send_preflight_probes`] for more information.
210+
pub fn preflight_probe_zero_value_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
211+
invoice: &Bolt11Invoice, amount_msat: u64, channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>,
212+
liquidity_limit_multiplier: Option<u64>,
213+
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbingError>
214+
where
215+
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
216+
T::Target: BroadcasterInterface,
217+
ES::Target: EntropySource,
218+
NS::Target: NodeSigner,
219+
SP::Target: SignerProvider,
220+
F::Target: FeeEstimator,
221+
R::Target: Router,
222+
L::Target: Logger,
223+
{
224+
if invoice.amount_milli_satoshis().is_some() {
225+
return Err(ProbingError::Invoice("amount unexpected"));
226+
}
227+
228+
let mut payment_params = PaymentParameters::from_node_id(
229+
invoice.recover_payee_pub_key(),
230+
invoice.min_final_cltv_expiry_delta() as u32,
231+
)
232+
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
233+
.with_route_hints(invoice.route_hints())
234+
.unwrap();
235+
236+
if let Some(features) = invoice.features() {
237+
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
238+
}
239+
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
240+
241+
channelmanager.send_preflight_probes(route_params, liquidity_limit_multiplier)
242+
.map_err(ProbingError::Sending)
243+
}
244+
166245
fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration {
167246
invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time()
168247
}
@@ -176,6 +255,15 @@ pub enum PaymentError {
176255
Sending(RetryableSendFailure),
177256
}
178257

258+
/// An error that may occur when sending a payment probe.
259+
#[derive(Clone, Debug, PartialEq, Eq)]
260+
pub enum ProbingError {
261+
/// An error resulting from the provided [`Bolt11Invoice`].
262+
Invoice(&'static str),
263+
/// An error occurring when sending a payment probe.
264+
Sending(ProbeSendFailure),
265+
}
266+
179267
/// A trait defining behavior of a [`Bolt11Invoice`] payer.
180268
///
181269
/// Useful for unit testing internal methods.

lightning/src/ln/channelmanager.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ use core::time::Duration;
7777
use core::ops::Deref;
7878

7979
// Re-export this for use in the public API.
80-
pub use crate::ln::outbound_payment::{PaymentSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
80+
pub use crate::ln::outbound_payment::{PaymentSendFailure, ProbeSendFailure, Retry, RetryableSendFailure, RecipientOnionFields};
8181
use crate::ln::script::ShutdownScript;
8282

8383
// We hold various information about HTLC relay in the HTLC objects in Channel itself:
@@ -3488,6 +3488,94 @@ where
34883488
outbound_payment::payment_is_probe(payment_hash, payment_id, self.probing_cookie_secret)
34893489
}
34903490

3491+
/// Sends payment probes over all paths of a route that would be used to pay the given
3492+
/// amount to the given `node_id`.
3493+
///
3494+
/// See [`ChannelManager::send_preflight_probes`] for more information.
3495+
pub fn send_spontaneous_preflight_probes(
3496+
&self, node_id: PublicKey, amount_msat: u64, final_cltv_expiry_delta: u32,
3497+
liquidity_limit_multiplier: Option<u64>,
3498+
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbeSendFailure> {
3499+
let payment_params =
3500+
PaymentParameters::from_node_id(node_id, final_cltv_expiry_delta);
3501+
3502+
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };
3503+
3504+
self.send_preflight_probes(route_params, liquidity_limit_multiplier)
3505+
}
3506+
3507+
/// Sends payment probes over all paths of a route that would be used to pay a route found
3508+
/// according to the given [`RouteParameters`].
3509+
///
3510+
/// This may be used to send "pre-flight" probes, i.e., to train our scorer before conducting
3511+
/// the actual payment. Note this is only useful if there likely is sufficient time for the
3512+
/// probe to settle before sending out the actual payment, e.g., when waiting for user
3513+
/// confirmation in a wallet UI.
3514+
///
3515+
/// Otherwise, there is a chance the probe could take up some liquidity needed to complete the
3516+
/// actual payment. Users should therefore be cautious and might avoid sending probes if
3517+
/// liquidity is scarce and/or they don't expect the probe to return before they send the
3518+
/// payment. To mitigate this issue, channels with available liquidity less than the required
3519+
/// amount times the given `liquidity_limit_multiplier` won't be used to send pre-flight
3520+
/// probes. If `None` is given as `liquidity_limit_multiplier`, it defaults to `3`.
3521+
pub fn send_preflight_probes(
3522+
&self, route_params: RouteParameters, liquidity_limit_multiplier: Option<u64>,
3523+
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbeSendFailure> {
3524+
let liquidity_limit_multiplier = liquidity_limit_multiplier.unwrap_or(3);
3525+
3526+
let payer = self.get_our_node_id();
3527+
let usable_channels = self.list_usable_channels();
3528+
let first_hops = usable_channels.iter().collect::<Vec<_>>();
3529+
let inflight_htlcs = self.compute_inflight_htlcs();
3530+
3531+
let route = self
3532+
.router
3533+
.find_route(&payer, &route_params, Some(&first_hops), inflight_htlcs)
3534+
.map_err(|e| {
3535+
log_error!(self.logger, "Failed to find path for payment probe: {:?}", e);
3536+
ProbeSendFailure::RouteNotFound
3537+
})?;
3538+
3539+
let mut used_liquidity_map = HashMap::with_capacity(first_hops.len());
3540+
3541+
let mut res = Vec::new();
3542+
for path in route.paths {
3543+
if path.hops.len() < 2 {
3544+
log_debug!(
3545+
self.logger,
3546+
"Skipped sending payment probe over path with less than two hops."
3547+
);
3548+
continue;
3549+
}
3550+
3551+
if let Some(first_path_hop) = path.hops.first() {
3552+
if let Some(first_hop) = first_hops.iter().find(|h| {
3553+
h.get_outbound_payment_scid() == Some(first_path_hop.short_channel_id)
3554+
}) {
3555+
let path_value = path.final_value_msat() + path.fee_msat();
3556+
let used_liquidity =
3557+
used_liquidity_map.entry(first_path_hop.short_channel_id).or_insert(0);
3558+
3559+
if first_hop.next_outbound_htlc_limit_msat
3560+
< (*used_liquidity + path_value) * liquidity_limit_multiplier
3561+
{
3562+
log_debug!(self.logger, "Skipped sending payment probe to avoid putting channel {} under the liquidity limit.", first_path_hop.short_channel_id);
3563+
continue;
3564+
} else {
3565+
*used_liquidity += path_value;
3566+
}
3567+
}
3568+
}
3569+
3570+
res.push(self.send_probe(path).map_err(|e| {
3571+
log_error!(self.logger, "Failed to send pre-flight probe: {:?}", e);
3572+
ProbeSendFailure::SendingFailed(e)
3573+
})?);
3574+
}
3575+
3576+
Ok(res)
3577+
}
3578+
34913579
/// Handles the generation of a funding transaction, optionally (for tests) with a function
34923580
/// which checks the correctness of the funding transaction given the associated channel.
34933581
fn funding_transaction_generated_intern<FundingOutput: Fn(&OutboundV1Channel<SP>, &Transaction) -> Result<OutPoint, APIError>>(

lightning/src/ln/outbound_payment.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ pub enum RetryableSendFailure {
391391
/// is in, see the description of individual enum states for more.
392392
///
393393
/// [`ChannelManager::send_payment_with_route`]: crate::ln::channelmanager::ChannelManager::send_payment_with_route
394-
#[derive(Clone, Debug)]
394+
#[derive(Clone, Debug, PartialEq, Eq)]
395395
pub enum PaymentSendFailure {
396396
/// A parameter which was passed to send_payment was invalid, preventing us from attempting to
397397
/// send the payment at all.
@@ -465,6 +465,18 @@ pub(super) enum Bolt12PaymentError {
465465
DuplicateInvoice,
466466
}
467467

468+
/// Indicates that we failed to send a payment probe. Further errors may be surfaced later via
469+
/// [`Event::ProbeFailed`].
470+
///
471+
/// [`Event::ProbeFailed`]: crate::events::Event::ProbeFailed
472+
#[derive(Clone, Debug, PartialEq, Eq)]
473+
pub enum ProbeSendFailure {
474+
/// We were unable to find a route to the destination.
475+
RouteNotFound,
476+
/// We failed to send the payment probes.
477+
SendingFailed(PaymentSendFailure),
478+
}
479+
468480
/// Information which is provided, encrypted, to the payment recipient when sending HTLCs.
469481
///
470482
/// This should generally be constructed with data communicated to us from the recipient (via a

0 commit comments

Comments
 (0)