Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream and fix preflight probing #2534

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions fuzz/src/chanmon_consistency.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ fn send_payment(source: &ChanMan, dest: &ChanMan, dest_chan_id: u64, amt: u64, p
channel_features: dest.channel_features(),
fee_msat: amt,
cltv_expiry_delta: 200,
maybe_announced_channel: true,
}], blinded_tail: None }],
route_params: None,
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
Expand Down Expand Up @@ -409,13 +410,15 @@ fn send_hop_payment(source: &ChanMan, middle: &ChanMan, middle_chan_id: u64, des
channel_features: middle.channel_features(),
fee_msat: first_hop_fee,
cltv_expiry_delta: 100,
maybe_announced_channel: true,
}, RouteHop {
pubkey: dest.get_our_node_id(),
node_features: dest.node_features(),
short_channel_id: dest_chan_id,
channel_features: dest.channel_features(),
fee_msat: amt,
cltv_expiry_delta: 200,
maybe_announced_channel: true,
}], blinded_tail: None }],
route_params: None,
}, payment_hash, RecipientOnionFields::secret_only(payment_secret), PaymentId(payment_id)) {
Expand Down
1 change: 1 addition & 0 deletions lightning-background-processor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1685,6 +1685,7 @@ mod tests {
channel_features: ChannelFeatures::empty(),
fee_msat: 0,
cltv_expiry_delta: MIN_CLTV_EXPIRY_DELTA as u32,
maybe_announced_channel: true,
}], blinded_tail: None };

$nodes[0].scorer.lock().unwrap().expect(TestResult::PaymentFailure { path: path.clone(), short_channel_id: scored_scid });
Expand Down
136 changes: 83 additions & 53 deletions lightning-invoice/src/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@
// You may not use this file except in accordance with one or both of these
// licenses.

//! Convenient utilities for paying Lightning invoices and sending spontaneous payments.
//! Convenient utilities for paying Lightning invoices.

use crate::Bolt11Invoice;
use crate::{Bolt11Invoice, Vec};

use bitcoin_hashes::Hash;

use lightning::chain;
use lightning::chain::chaininterface::{BroadcasterInterface, FeeEstimator};
use lightning::sign::{NodeSigner, SignerProvider, EntropySource};
use lightning::ln::PaymentHash;
use lightning::ln::channelmanager::{ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields};
use lightning::ln::channelmanager::{AChannelManager, ChannelManager, PaymentId, Retry, RetryableSendFailure, RecipientOnionFields, ProbeSendFailure};
use lightning::routing::router::{PaymentParameters, RouteParameters, Router};
use lightning::util::logger::Logger;

Expand All @@ -32,22 +32,12 @@ use core::time::Duration;
/// with the same [`PaymentHash`] is never sent.
///
/// If you wish to use a different payment idempotency token, see [`pay_invoice_with_id`].
pub fn pay_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
invoice: &Bolt11Invoice, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
pub fn pay_invoice<C: AChannelManager>(
invoice: &Bolt11Invoice, retry_strategy: Retry, channelmanager: &C
) -> Result<PaymentId, PaymentError>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
ES::Target: EntropySource,
NS::Target: NodeSigner,
SP::Target: SignerProvider,
F::Target: FeeEstimator,
R::Target: Router,
L::Target: Logger,
{
let payment_id = PaymentId(invoice.payment_hash().into_inner());
pay_invoice_with_id(invoice, payment_id, retry_strategy, channelmanager)
pay_invoice_with_id(invoice, payment_id, retry_strategy, channelmanager.get_cm())
.map(|()| payment_id)
}

Expand All @@ -61,22 +51,12 @@ where
/// [`PaymentHash`] has never been paid before.
///
/// See [`pay_invoice`] for a variant which uses the [`PaymentHash`] for the idempotency token.
pub fn pay_invoice_with_id<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
pub fn pay_invoice_with_id<C: AChannelManager>(
invoice: &Bolt11Invoice, payment_id: PaymentId, retry_strategy: Retry, channelmanager: &C
) -> Result<(), PaymentError>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
ES::Target: EntropySource,
NS::Target: NodeSigner,
SP::Target: SignerProvider,
F::Target: FeeEstimator,
R::Target: Router,
L::Target: Logger,
{
let amt_msat = invoice.amount_milli_satoshis().ok_or(PaymentError::Invoice("amount missing"))?;
pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager)
pay_invoice_using_amount(invoice, amt_msat, payment_id, retry_strategy, channelmanager.get_cm())
}

/// Pays the given zero-value [`Bolt11Invoice`] using the given amount, retrying if needed based on
Expand All @@ -88,19 +68,9 @@ where
///
/// If you wish to use a different payment idempotency token, see
/// [`pay_zero_value_invoice_with_id`].
pub fn pay_zero_value_invoice<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
pub fn pay_zero_value_invoice<C: AChannelManager>(
invoice: &Bolt11Invoice, amount_msats: u64, retry_strategy: Retry, channelmanager: &C
) -> Result<PaymentId, PaymentError>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
ES::Target: EntropySource,
NS::Target: NodeSigner,
SP::Target: SignerProvider,
F::Target: FeeEstimator,
R::Target: Router,
L::Target: Logger,
{
let payment_id = PaymentId(invoice.payment_hash().into_inner());
pay_zero_value_invoice_with_id(invoice, amount_msats, payment_id, retry_strategy,
Expand All @@ -119,25 +89,16 @@ where
///
/// See [`pay_zero_value_invoice`] for a variant which uses the [`PaymentHash`] for the
/// idempotency token.
pub fn pay_zero_value_invoice_with_id<M: Deref, T: Deref, ES: Deref, NS: Deref, SP: Deref, F: Deref, R: Deref, L: Deref>(
pub fn pay_zero_value_invoice_with_id<C: AChannelManager>(
invoice: &Bolt11Invoice, amount_msats: u64, payment_id: PaymentId, retry_strategy: Retry,
channelmanager: &ChannelManager<M, T, ES, NS, SP, F, R, L>
channelmanager: &C
) -> Result<(), PaymentError>
where
M::Target: chain::Watch<<SP::Target as SignerProvider>::Signer>,
T::Target: BroadcasterInterface,
ES::Target: EntropySource,
NS::Target: NodeSigner,
SP::Target: SignerProvider,
F::Target: FeeEstimator,
R::Target: Router,
L::Target: Logger,
{
if invoice.amount_milli_satoshis().is_some() {
Err(PaymentError::Invoice("amount unexpected"))
} else {
pay_invoice_using_amount(invoice, amount_msats, payment_id, retry_strategy,
channelmanager)
channelmanager.get_cm())
}
}

Expand All @@ -163,6 +124,66 @@ fn pay_invoice_using_amount<P: Deref>(
payer.send_payment(payment_hash, recipient_onion, payment_id, route_params, retry_strategy)
}

/// Sends payment probes over all paths of a route that would be used to pay the given invoice.
///
/// See [`ChannelManager::send_preflight_probes`] for more information.
pub fn preflight_probe_invoice<C: AChannelManager>(
invoice: &Bolt11Invoice, channelmanager: &C, liquidity_limit_multiplier: Option<u64>,
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbingError>
{
let amount_msat = if let Some(invoice_amount_msat) = invoice.amount_milli_satoshis() {
invoice_amount_msat
} else {
return Err(ProbingError::Invoice("Failed to send probe as no amount was given in the invoice."));
};

let mut payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
.with_route_hints(invoice.route_hints())
.unwrap();

if let Some(features) = invoice.features() {
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
}
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };

channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier)
.map_err(ProbingError::Sending)
}

/// Sends payment probes over all paths of a route that would be used to pay the given zero-value
/// invoice using the given amount.
///
/// See [`ChannelManager::send_preflight_probes`] for more information.
pub fn preflight_probe_zero_value_invoice<C: AChannelManager>(
invoice: &Bolt11Invoice, amount_msat: u64, channelmanager: &C,
liquidity_limit_multiplier: Option<u64>,
) -> Result<Vec<(PaymentHash, PaymentId)>, ProbingError>
{
if invoice.amount_milli_satoshis().is_some() {
return Err(ProbingError::Invoice("amount unexpected"));
}

let mut payment_params = PaymentParameters::from_node_id(
invoice.recover_payee_pub_key(),
invoice.min_final_cltv_expiry_delta() as u32,
)
.with_expiry_time(expiry_time_from_unix_epoch(invoice).as_secs())
.with_route_hints(invoice.route_hints())
.unwrap();

if let Some(features) = invoice.features() {
payment_params = payment_params.with_bolt11_features(features.clone()).unwrap();
}
let route_params = RouteParameters { payment_params, final_value_msat: amount_msat };

channelmanager.get_cm().send_preflight_probes(route_params, liquidity_limit_multiplier)
.map_err(ProbingError::Sending)
}

fn expiry_time_from_unix_epoch(invoice: &Bolt11Invoice) -> Duration {
invoice.signed_invoice.raw_invoice.data.timestamp.0 + invoice.expiry_time()
}
Expand All @@ -176,6 +197,15 @@ pub enum PaymentError {
Sending(RetryableSendFailure),
}

/// An error that may occur when sending a payment probe.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ProbingError {
/// An error resulting from the provided [`Bolt11Invoice`].
Invoice(&'static str),
/// An error occurring when sending a payment probe.
Sending(ProbeSendFailure),
}

/// A trait defining behavior of a [`Bolt11Invoice`] payer.
///
/// Useful for unit testing internal methods.
Expand Down
Loading